준비: 새 폴더 채팅방을 만들고 터미널에 다음 명령을 입력합니다. npm 단계를 따릅니다. (이전에 설치하지 않은 경우 공식 웹 사이트로 이동하여 node 및 npm을 설치합니다.) 자동으로 생성됨
express 및 소켓.io
설치
package.json 파일은 다음과 같습니다.
//package.json { "name": "chatroom", "version": "1.0.0", "description": "A simple chatroom", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { "type": "git", "url": "git+https://github.com/ddvdd008/chatroom.git" }, "keywords": [ "chatroom", "nodejs", "express" ], "author": "ddvdd", "license": "ISC", "bugs": { "url": "https://github.com/ddvdd008/chatroom/issues" }, "homepage": "https://github.com/ddvdd008/chatroom#readme" }
express 및 소켓.io 설치
npm install express --save npm install socket.io --save
package.json은 자동으로 종속성을 추가합니다.
"dependencies": { "express": "^4.16.2", "socket.io": "^2.0.4" }
왜냐하면 우리는 express를 사용하기 때문입니다. 프레임워크가 작성된 후 End service, 소켓.io를 사용합니다(Socket.io는 실제로 WebSocket의 상위 집합입니다. Socket.io는 WebSocket 및 폴링과 같은 메서드를 캡슐화합니다. 메서드를 선택합니다. 상황에 따라 통신합니다.) 통신을 용이하게 하기 위한 지속적인 링크를 설정합니다.
준비 작업이 거의 완료되었습니다. 단계별로 구현해 보겠습니다.
express 생성 서비스
노드를 배운 학생들은 익숙할 것입니다. 이번에는 express를 사용하여 서비스를 생성합니다. 프로젝트 루트 디렉터리에 app.js를 만듭니다.
/** * Created by ddvdd on 2018-02-07. */ const express = require('express'); const app = express(); // 创建express实例,赋值给app。 const fs = require('fs'); // 这个是node的文件读取模块,用于读取文件 const path = require('path'); // 这是node的路径处理模块,可以格式化路径 app.listen(3000,()=>{ console.log("server running at 127.0.0.1:3000"); // 代表监听3000端口,然后执行回调函数在控制台输出。 }); /** * app.get(): express中的一个中间件,用于匹配get请求,说的简单点就是node处理请求的路由,对于不同url请求,让对应的不同app.get()去处理 * '/': 它匹配get请求的根路由 '/'也就是 127.0.0.1:3000/就匹配到它了 * req带表浏览器的请求对象,res代表服务器的返回对象 */ app.get('/',(req,res)=>{ res.redirect('/chat.html'); // express的重定向函数。如果浏览器请求了根路由'/',浏览器就给他重定向到 '127.0.0.1:3000/chat.html'路由中 }); /** * 这里匹配到的是/chat.html就是上面重定向到的路径。 */ app.get('/chat.html',(req,res)=>{ fs.readFile(path.join(__dirname,'./public/chat.html'),function(err,data){ //读取文件,readFile里传入的是文件路径和回调函数,这里用path.join()格式化了路径。 if(err){ console.error("读取chat.html发生错误",err); //错误处理 res.send('4 0 4'); //如果发生错误,向浏览器返回404 } else { res.end(data); //这里的data就是回调函数的参数,在readFile内部已经将读取的数据传递给了回调函数的data变量。 } //我们将data传到浏览器,就是把html文件传给浏览器 }) });
이 글을 읽고 나면 Express 프레임워크가 그렇게 간단해 보이지는 않을 것입니다. 단일 페이지를 보내는 가장 간단한 방법은 노드와 함께 제공되는 http.createServer와 크게 다르지 않지만 꽤 번거롭습니다. . 현재의 관점에서 볼 때 이는 실제로 여러분의 이해를 돕기 위해 이 작업을 수행하는 것이 아닙니다. Express는 정적 리소스 파일을 호스팅하는 데 도움이 되는 매우 강력한 미들웨어를 제공합니다.
app.use('/',express.static(path.join(__dirname,'./public'))); //一句话就搞定。
:
app.get('/chat.html',(req,res)=>{ fs.readFile(path.join(__dirname,'./public/chat.html'),function(err,data){ if(err){ console.error("读取chat.html发生错误",err); res.send('4 0 4'); } else { res.end(data); } }) });
__dirname은 현재 파일이 있는 절대 경로를 나타내므로 path.join을 사용하여 app.js의 절대 경로를 추가하고 public을 사용하여 public의 절대 경로를 얻습니다. Path.join은 ././public과 같은 이상한 경로를 방지하는 데 사용됩니다. express.static은 공용 폴더에서 정적 리소스를 호스팅하는 데 도움이 됩니다. 127.0.0.1:3000/XXX/AAA 경로가 있는 한 공용 폴더로 이동하여 XXX 폴더에서 AAA 파일을 찾은 다음 브라우저로 보냅니다.
이제 이 코드가 매우 입문적인지 살펴보겠습니다. app.use()가 무엇을 하는지 구체적으로 아는 학생들은 여기
socket.io로 가서 클라이언트와 서버 사이의 링크를 설정할 수 있습니다
위의 서비스를 생성한 후, 클라이언트와 서버가 장기 링크를 설정할 수 있도록 하려면 소켓.io를 참조해야 합니다. app.js를 다음과 같이 변환합니다.
/** * Created by ddvdd on 2018-02-07. */ const express = require('express'); const app = express(); // 创建express实例,赋值给app。 const server = require('http').Server(app); const io = require('socket.io')(server); //将socket的监听加到app设置的模块里。这两句理解不了的可以去socket.io官网去看 const path = require('path'); // 这是node的路径处理模块,可以格式化路径 server.listen(3000,()=>{ console.log("server running at 127.0.0.1:3000"); // 代表监听3000端口,然后执行回调函数在控制台输出。 }); ... ... app.use('/',express.static(path.join(__dirname,'./public'))); //一句话就搞定。 /*socket*/ io.on('connection',(socket)=>{ //监听客户端的连接事件 });
o.on은 이벤트가 발생하면 콜백 함수가 트리거되는 것을 의미합니다. 'Connection'은 이벤트 이름으로 정의되어 있으며 사용자가 연결되어 있는 동안 실행됩니다. 이제 app.js가 기본적으로 완료되었으므로 루트 디렉터리에서 실행합니다.
node app.js
>
이제 http://127.0.0.1:3000/static/chat.html을 방문하세요.
안녕? 아무것도 아님. . . 말도 안되는 소리가 아닙니다! URL 요청에 해당하는 정적 리소스가 없습니다!
정적 html 추가
프로젝트 루트 디렉터리에 공용 폴더를 만들고 공용 폴더에 새 chat.html 파일을 만듭니다.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>聊天室</title> </head> <body> 这是我们的聊天室 </body> </html>
이제 페이지를 새로 고치면 페이지가 나타나는 것을 볼 수 있습니다.
>
이 시점에서 가장 간단한 브라우저와 웹 서버 협업 프로젝트가 완료되었습니다. 향후 계속해서 페이지를 개선하고 서버 백엔드에 비즈니스 기능을 추가하여 다인 채팅방을 실현할 예정입니다.
기본 기능 구현
로그인 기능, 사용자 이름이 필요합니다(비밀번호 필요 없음). 사용자 이름은 클라이언트 서버에 저장되어야 합니다. 정보를 전송할 때마다 기본적으로 사용자 이름을 포함해야 합니다. 그렇지 않으면 정보를 보낸 사람이 누구인지 알 수 없습니다.
그룹 채팅 기능을 위해서는 우리 자신과 서로의 정보를 구별해야 합니다
로그인 기능 구현
로그인 페이지 재구성
가장 기본적인 로그인 인터페이스는 사용자 이름 입력 상자와 로그인 버튼으로 구성됩니다.
//chat.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>聊天室</title> <style> *{ margin:0; padding:0; box-sizing: border-box; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; } .container{ position: absolute; top:0; left:0; right:0; bottom:0; background-color: grey; padding: 50px; } .container .title{ width:300px; margin: 0 auto; font-size: 30px; font-family: 'Franklin Gothic Medium'; font-weight: bold; text-align: center; margin-bottom:50px; } .container .login-wrap{ width:400px; padding: 20px; border: 1px solid #000; margin: 0 auto; text-align: center; } .login-wrap .user-ipt{ width:360px; text-align: center; vertical-align: middle; } .login-wrap .login-button{ width:60px; height:24px; line-height:20px; font-size: 14px; padding: 2px 0; border-radius: 5px; margin-top:10px; } </style> </head> <body> <p class="container"> <p class="title">欢迎来到ddvdd聊天室</p> <p class="login-wrap"> <p class="user-ipt"> <span class="user-name">用户名:</span> <input id="name" class="name-ipt" type="text" /> </p> <button id="loginbutton" class="login-button">登陆</button> </p> </p> </body> </html>
몇 가지 스타일만 추가하면 정적 페이지가 완성됩니다. 페이지를 새로고침해 보겠습니다.
로그인 페이지 상호 작용
어제 오후에 작성을 절반쯤 했습니다. . . 부서에서 갑자기 팀 빌딩 파티에 가야 해서 서둘러 코드를 제출하고 극복할 수 밖에 없습니다. 오늘 아침 일찍 회사에 와서 다들 코딩을 계속했어요
더 이상 고민하지 말고 바로 로그인 상호 작용으로 넘어가겠습니다. 사용자가 서버에 접속하여 성공적으로 로그인하면 온라인 로그인 횟수로 계산됩니다. 사용자가 로그인하면 서버는 사용자 정보를 배열로 서버에 저장합니다. 여기서 주목해야 할 점은 서버가 로그인한 사용자 이름을 확인하고 확인 결과를 반환한다는 것입니다. 클라이언트는 확인 결과를 통해 현재 페이지의 채팅 페이지 진입 여부를 변경하게 됩니다.
上面的服务器和客户端交互都是通过socket.io来实现通讯的,前端的业务交互我们这里就采用jquery来实现,在public文件夹下新建js文件夹,下载jquery-3.2.1.min.js、新建main.js。然后对chat.html引入需要的sdk:
<script src="js/jquery-3.2.1.min.js"></script> <script src="js/main.js"></script> //socket.io官网要求这么引入 <script src="/socket.io/socket.io.js"></script>
引入完sdk,我们对main的js添加登录功能:
//main.js /** * Created by ddvdd on 2018-02-08. */ $(function(){ const url = 'http://127.0.0.1:3000'; let _username = ''; let _$inputname = $('#name'); let _$loginButton = $('#loginbutton'); let socket = io.connect(url); //设置用户名,当用户登录的时候触发 let setUsername = () => { _username = _$inputname.val().trim(); //得到输入框中用户输入的用户名 //判断用户名是否存在 if(_username) { socket.emit('login',{username: _username}); //如果用户名存在,就代表可以登录了,我们就触发登录事件,就相当于告诉服务器我们要登录了 } else{ alert('请输入用户名!'); } }; /*前端事件*/ _$loginButton.on('click',function (event) { //监听按钮的点击事件,如果点击,就说明用户要登录,就执行setUsername函数 setUsername(); }); /*socket.io部分逻辑*/ socket.on('loginResult',(data)=>{ /** * 如果服务器返回的用户名和刚刚发送的相同的话,就登录 * 否则说明有地方出问题了,拒绝登录 */ if(data.code === 0) { // 登陆成功,切换至聊天室页面 } else if(data.code ===1){ alert('用户已登录!'); } else{ alert('登录失败!'); } }) }); //app.js /** * Created by ddvdd on 2018-02-07. */ const express = require('express'); const app = express(); // 创建express实例,赋值给app。 const server = require('http').Server(app); const io = require('socket.io')(server); //将socket的监听加到app设置的模块里。这两句理解不了的可以去socket.io官网去看 const path = require('path'); // 这是node的路径处理模块,可以格式化路径 const users = []; //用来保存所有的用户信息 let usersNum = 0; //统计在线登录人数 server.listen(3000,()=>{ console.log("server running at 127.0.0.1:3000"); // 代表监听3000端口,然后执行回调函数在控制台输出。 }); /** * app.get(): express中的一个中间件,用于匹配get请求,说的简单点就是node处理请求的路由,对于不同url请求,让对应的不同app.get()去处理 * '/': 它匹配get请求的根路由 '/'也就是 127.0.0.1:3000/就匹配到它了 * req带表浏览器的请求对象,res代表服务器的返回对象 */ app.get('/',(req,res)=>{ res.redirect('/static/chat.html'); // express的重定向函数。如果浏览器请求了根路由'/',浏览器就给他重定向到 '127.0.0.1:3000/chat.html'路由中 }); /** * __dirname表示当前文件所在的绝对路径,所以我们使用path.join将app.js的绝对路径和public加起来就得到了public的绝对路径。 * 用path.join是为了避免出现 ././public 这种奇怪的路径 * express.static就帮我们托管了public文件夹中的静态资源。 * 只要有 127.0.0.1:3000/XXX/AAA 的路径都会去public文件夹下找XXX文件夹下的AAA文件然后发送给浏览器。 */ app.use('/static',express.static(path.join(__dirname,'./public'))); //一句话就搞定。 /*socket*/ io.on('connection',(socket)=>{ //监听客户端的连接事件 socket.on('login',(data)=>{ if(checkUserName(data)){ socket.emit('loginResult',{code:1}); //code=1 用户已登录 } else{ //将该用户的信息存进数组中 users.push({ username: data.username, message: [] }); socket.emit('loginResult',{code:0}); //code=0 用户登录成功 usersNum = users.length; console.log(`用户${data.username}登录成功,进入ddvdd聊天室,当前在线登录人数:${usersNum}`); } }); //断开连接后做的事情 socket.on('disconnect',()=>{ //注意,该事件不需要自定义触发器,系统会自动调用 usersNum = users.length; console.log(`当前在线登录人数:${usersNum}`); }); }); //校验用户是否已经登录 const checkUserName = (data) => { let isExist = false; users.map((user) => { if(user.username === data.username){ isExist = true; } }); return isExist; }
上面代码大家需要了解以下几点:
socket.on 表示监听事件,后面接一个回调函数用来接收emit发出事件传递过来的对象。
socket.emit 用来触发事件,传递对象给on监听事件。
我们socket连接之后的监听触发事件都要写在io.on('connection')的回调里面,因为这些事件都是连接之后发生的,就算是断开连接的事件 disconnect 也是在连接事件中发生的,没有正在连接的状态,哪来的断开连接呢?
理解虽然服务器端只有app.js一个文件,但是不同的客户端连接后信息是不同的,所以我们必须要将一些公用的信息,比如说,储存所有登录用户的数组,所有用户发送的所有信息存储在外部,一定不能存储在connecion里
效果展示:
群聊功能实现
写完简单的登录功能,现在我们来写这项目最重要的功能群聊。首先我们先来处理下页面,因为功能简单,所以不单独建立html来显示聊天室,就直接写在login页面,通过class名称的变化来切换登录后,聊天室的显示。
聊天室页面重构
下面我们对chat.html进行整改:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>聊天室</title> <script src="js/jquery-3.2.1.min.js"></script> <script src="js/main.js"></script> <script src="/socket.io/socket.io.js"></script> <style> *{ margin:0; padding:0; box-sizing: border-box; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; } .container{ position: absolute; top:0; left:0; right:0; bottom:0; background-color: darkgrey; padding: 50px; overflow-y: scroll; } .container .title{ margin: 0 auto; font-size: 30px; font-family: 'Franklin Gothic Medium'; font-weight: bold; text-align: center; margin-bottom:20px; } .container .login-wrap{ width:400px; padding: 20px; border: 1px solid #000; margin: 0 auto; text-align: center; } .login-wrap .user-ipt{ width:360px; text-align: center; vertical-align: middle; } .login-wrap .login-button{ width:60px; height:24px; line-height:20px; font-size: 14px; padding: 2px 0; border-radius: 5px; margin-top:10px; } .chat-wrap .chat-content{ width:100%; height:600px; background-color: whitesmoke; padding:10px; } .chat-wrap .send-wrap{ margin-top: 20px; } .message-ipt{ width: 200px; height: 100px; padding: 0 5px; vertical-align: bottom; } .chat-content p{ display: block; margin-bottom: 10px; } .chat-content p .msg{ display: inline-block; padding: 8px 11px; border-radius:6px; } .chat-content .self-message .msg{ background-color:#d0e7ff; border: 1px solid #c9dfff; } .chat-content .other-message .msg{ background-color:white; border: 1px solid #eee; } .chat-content .self-message{ text-align:right; } .chat-content .other-message{ text-align-last:left; } </style> </head> <body> <p class="container"> <p id="loginbox" class="login-wrap"> <p class="title">登录</p> <p class="user-ipt"> <span class="user-name">用户名:</span> <input id="name" class="name-ipt" type="text" /> </p> <button id="loginbutton" class="login-button">登录</button> </p> <p id="chatbox" class="chat-wrap" style="display:none"> <p id="content" class="chat-content"> <!-- 聊天内容 --> </p> <p class="send-wrap"> <textarea rows="3" cols="20" id="chatmessage" class="message-ipt" type="textarea" placeholder="请输入要发送的信息内容"></textarea> </p> </p> </p> </body> </html>
新增chatbox容器来作为聊天室,里面有一个群聊的聊天框,和一个发送消息的文本框。通过上面loginResult回调,对loginbox进行隐藏,显示chatbox:
//显示聊天室界面 let showChatRoom = () => { /** * 1.隐藏登录框,取消它绑定的事件 * 2.显示聊天界面 */ $('#loginbox').hide('slow'); _$loginButton.off('click'); /** * 显示聊天界面,并显示一行文字,欢迎用户 */ $(`<p class="title">欢迎${_username}来到ddvdd聊天室</p>`).insertBefore($("#content")); $("#chatbox").show('slow'); }
消息事件发送监听机制
聊天一定是客户端触发的,所以发送信息是客户端触发,服务器监听。
服务器监听到发送信息的事件后会存储信息,然后触发发送信息成功事件广播给所有客户端,将信息传给所有客户端。
发送消息sendMessage事件
//main.js //发送消息 let sendMessage = function () { /** * 得到输入框的聊天信息,如果不为空,就触发sendMessage * 将信息和用户名发送过去 */ let _message = _$chattextarea.val(); if(_message) { socket.emit('sendMessage',{username: _username, message: _message}); } else{ alert('请输入发送消息!'); } }; ... /*聊天事件*/ _$chattextarea.on('keyup',function (event) { if(event.keyCode === 13) { sendMessage(); _$chattextarea.val(''); } });
服务器端监听sendMessage事件
//app.js /** * 监听sendMessage,我们得到客户端传过来的data里的message,并存起来。 */ socket.on('sendMessage',(data)=>{ for(let _user of users) { if(_user.username === data.username) { _user.message.push(data.message); //信息存储之后触发receiveMessage将信息发给所有浏览器-广播事件 io.emit('receiveMessage',data); break; } } });
我们是遍历服务器端的用户数组,找到该用户,将发送的信息存起来,然后触发receiveMessage事件广播到所有浏览器,sendMessage是写在connection里,login之外的,为什么这么做大家一定要理解,发送消息是连接时候做的事情,而不是登录时做的事情。
注意的是,我使用的是io.emit,他是真正的广播到所有浏览器,socket.broadcast.emit则不会广播到自己的浏览器。
客户端监听receiveMessage事件
//main.js socket.on('receiveMessage',(data)=>{ /** * * 监听服务器广播的消息 */ showMessage(data); }) //显示消息 let showMessage = function (data) { //先判断这个消息是不是自己发出的,然后再以不同的样式显示 if(data.username === _username){ $("#content").append(`<p class='self-message'><span class='msg'>${data.message}</span><span class='name'> :${data.username}</span></p>`); }else { $("#content").append(`<p class='other-message'><span class='name'>${data.username}: </span><span class='msg'>${data.message}</span></p>`); } };
写到这边,我们的聊天室基本功能已经完成了,来看看效果吧!打开三个浏览器,分别登录老大、老二、老三,发一句“大噶好~,我是渣渣辉!”。
相关推荐:
위 내용은 Nodejs와 Express는 다인용 채팅방을 구축합니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!