使用Node.js+Socket.IO搭建WebSocket 实现多人群聊

来源:互联网 发布:淘宝750海报图片 编辑:程序博客网 时间:2024/05/17 07:14

今天我们做的就是无刷新实时多人聊天,最终效果我们可以看下:

Node.js

Node.js采用C++语言编写而成,它不是Javascript应用,而是一个Javascript的运行环境,据Node.js创始人Ryan Dahl回忆,他最初希望采用Ruby来写Node.js,但是后来发现Ruby虚拟机的性能不能满足他的要求,后来他尝试采用V8引擎,所以选择了C++语言。

Node.js支持的系统包括*nux、Windows,这意味着程序员可以编写系统级或者服务器端的Javascript代码,交给Node.js来解释执行。Node.js的Web开发框架Express,可以帮助程序员快速建立web站点,从2009年诞生至今,Node.js的成长的速度有目共睹,其发展前景获得了技术社区的充分肯定。

Socket.IO

Socket.IO是一个开源的WebSocket库,它通过Node.js实现WebSocket服务端,同时也提供客户端JS库。Socket.IO支持以事件为基础的实时双向通讯,它可以工作在任何平台、浏览器或移动设备。

Socket.IO支持4种协议:WebSocket、htmlfile、xhr-polling、jsonp-polling,它会自动根据浏览器选择适合的通讯方式,从而让开发者可以聚焦到功能的实现而不是平台的兼容性,同时Socket.IO具有不错的稳定性和性能。

编码实现:

1.安装Node.js

根据自己的操作系统,去Node.js官网下载安装即可。我的是nginx:

yum install nodejsyum install npm 
如果成功安装。在命令行输入node -vnpm -v应该能看到相应的版本号。

node -v  v0.10.26  npm -v  1.4.6

搭建WebSocket服务端

这个环节我们尽可能的考虑真实生产环境,把WebSocket后端服务搭建成一个线上可以用域名访问的服务,如果你是在本地开发环境,可以换成本地ip地址,或者使用一个虚拟域名指向本地ip。

进入到工作目录,/usr/local/nginx/html,新建一个名为package.json的文件,内容

{  "name": "realtime-server",  "version": "0.0.1",  "description": "my first realtime server",  "dependencies": {}}

接下来使用npm命令安装expresssocket.io

12
npm install --save expressnpm install --save socket.io

安装成功后,应该可以看到工作目录下生成了一个名为node_modules的文件夹,里面分别是expresssocket.io,接下来可以开始编写服务端的代码了,新建一个文件:index.js

1234567891011
var app = require('express')();var http = require('http').Server(app);var io = require('socket.io')(http);app.get('/', function(req, res){res.send('<h1>Welcome Realtime Server</h1>');});http.listen(3000, function(){console.log('listening on *:3000');});

命令行运行node index.js,如果一切顺利,你应该会看到返回的listening on *:3000字样,这说明服务已经成功搭建了。此时浏览器中打开http://localhost:3000应该可以看到正常的欢迎页面。

如果你想要让服务运行在线上服务器,并且可以通过域名访问的话,可以使用Nginx做代理,在nginx.conf中添加如下配置,然后将域名(比如:realtime.plhwin.com)解析到服务器IP即可。

12345678
server{  listen       80;  server_name  realtime.plhwin.com;  location / {    proxy_pass http://127.0.0.1:3000;  }}

完成以上步骤,http://realtime.plhwin.com:3000的后端服务就正常搭建了。

服务端代码实现

前面讲到的index.js运行在服务端,之前的代码只是一个简单的WebServer欢迎内容,让我们把WebSocket服务端完整的实现代码加入进去,整个服务端就可以处理客户端的请求了。完整的index.js代码如下:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
var app = require('express')();var http = require('http').Server(app);var io = require('socket.io')(http);app.get('/', function(req, res){res.send('<h1>Welcome Realtime Server</h1>');});//在线用户var onlineUsers = {};//当前在线人数var onlineCount = 0;io.on('connection', function(socket){console.log('a user connected');//监听新用户加入socket.on('login', function(obj){//将新加入用户的唯一标识当作socket的名称,后面退出的时候会用到socket.name = obj.userid;//检查在线列表,如果不在里面就加入if(!onlineUsers.hasOwnProperty(obj.userid)) {onlineUsers[obj.userid] = obj.username;//在线人数+1onlineCount++;}//向所有客户端广播用户加入io.emit('login', {onlineUsers:onlineUsers, onlineCount:onlineCount, user:obj});console.log(obj.username+'加入了聊天室');});//监听用户退出socket.on('disconnect', function(){//将退出的用户从在线列表中删除if(onlineUsers.hasOwnProperty(socket.name)) {//退出用户的信息var obj = {userid:socket.name, username:onlineUsers[socket.name]};//删除delete onlineUsers[socket.name];//在线人数-1onlineCount--;//向所有客户端广播用户退出io.emit('logout', {onlineUsers:onlineUsers, onlineCount:onlineCount, user:obj});console.log(obj.username+'退出了聊天室');}});//监听用户发布聊天内容socket.on('message', function(obj){//向所有客户端广播发布的消息io.emit('message', obj);console.log(obj.username+'说:'+obj.content);});  });http.listen(3000, function(){console.log('listening on *:3000');});

客户端代码实现

进入客户端工作目录/workspace/wwwroot/plhwin/demo.plhwin.com/chat,新建一个index.html:

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
<!DOCTYPE html><html>    <head>        <meta charset="utf-8">        <meta name="format-detection" content="telephone=no"/>        <meta name="format-detection" content="email=no"/><meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=0" name="viewport">        <title>多人聊天室</title>        <link rel="stylesheet" type="text/css" href="./style.css" />        <!--[if lt IE 8]><script src="./json3.min.js"></script><![endif]-->        <script src="http://realtime.plhwin.com:3000/socket.io/socket.io.js"></script>    </head>    <body>        <div id="loginbox">            <div style="width:260px;margin:200px auto;">                请先输入你在聊天室的昵称                <br/>                <br/>                <input type="text" style="width:180px;" placeholder="请输入用户名" id="username" name="username" /><input type="button" style="width:50px;" value="提交" onclick="CHAT.usernameSubmit();"/>            </div>        </div>        <div id="chatbox" style="display:none;">            <div style="background:#3d3d3d;height: 28px; width: 100%;font-size:12px;">                <div style="line-height: 28px;color:#fff;">                    <span style="text-align:left;margin-left:10px;">Websocket多人聊天室</span>                    <span style="float:right; margin-right:10px;"><span id="showusername"></span> | <a href="javascript:;" onclick="CHAT.logout()" style="color:#fff;">退出</a></span>                </div>            </div>            <div id="doc">                <div id="chat">                    <div id="message" class="message"><div id="onlinecount" style="background:#EFEFF4; font-size:12px; margin-top:10px; margin-left:10px; color:#666;"></div>                    </div>                    <div class="input-box">                        <div class="input"><input type="text" maxlength="140" placeholder="请输入聊天内容,按Ctrl提交" id="content" name="content">                        </div>                        <div class="action">                            <button type="button" id="mjr_send" onclick="CHAT.submit();">提交</button>                        </div>                    </div>                </div>            </div>        </div>        <script type="text/javascript" src="./client.js"></script>    </body></html>

上面的html内容本身没有什么好说的,我们主要看看里面的4个文件请求:
1、realtime.plhwin.com:3000/socket.io/socket.io.js
2、style.css
3、json3.min.js
4、client.js

第1个JS是Socket.IO提供的客户端JS文件,在前面安装服务端的步骤中,当npm安装完socket.io并搭建起WebServer后,这个JS文件就可以正常访问了。

第2个style.css文件没什么好说的,就是样式文件而已。

第3个JS只在IE8以下版本的IE浏览器中加载,目的是让这些低版本的IE浏览器也能处理json,这是一个开源的JS,详见:http://bestiejs.github.io/json3/

第4个client.js是完整的客户端的业务逻辑实现代码,它的内容如下:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
(function () {var d = document,w = window,p = parseInt,dd = d.documentElement,db = d.body,dc = d.compatMode == 'CSS1Compat',dx = dc ? dd: db,ec = encodeURIComponent;w.CHAT = {msgObj:d.getElementById("message"),screenheight:w.innerHeight ? w.innerHeight : dx.clientHeight,username:null,userid:null,socket:null,//让浏览器滚动条保持在最低部scrollToBottom:function(){w.scrollTo(0, this.msgObj.clientHeight);},//退出,本例只是一个简单的刷新logout:function(){//this.socket.disconnect();location.reload();},//提交聊天消息内容submit:function(){var content = d.getElementById("content").value;if(content != ''){var obj = {userid: this.userid,username: this.username,content: content};this.socket.emit('message', obj);d.getElementById("content").value = '';}return false;},genUid:function(){return new Date().getTime()+""+Math.floor(Math.random()*899+100);},//更新系统消息,本例中在用户加入、退出的时候调用updateSysMsg:function(o, action){//当前在线用户列表var onlineUsers = o.onlineUsers;//当前在线人数var onlineCount = o.onlineCount;//新加入用户的信息var user = o.user;//更新在线人数var userhtml = '';var separator = '';for(key in onlineUsers) {        if(onlineUsers.hasOwnProperty(key)){userhtml += separator+onlineUsers[key];separator = '、';}    }d.getElementById("onlinecount").innerHTML = '当前共有 '+onlineCount+' 人在线,在线列表:'+userhtml;//添加系统消息var html = '';html += '<div class="msg-system">';html += user.username;html += (action == 'login') ? ' 加入了聊天室' : ' 退出了聊天室';html += '</div>';var section = d.createElement('section');section.className = 'system J-mjrlinkWrap J-cutMsg';section.innerHTML = html;this.msgObj.appendChild(section);this.scrollToBottom();},//第一个界面用户提交用户名usernameSubmit:function(){var username = d.getElementById("username").value;if(username != ""){d.getElementById("username").value = '';d.getElementById("loginbox").style.display = 'none';d.getElementById("chatbox").style.display = 'block';this.init(username);}return false;},init:function(username){/*客户端根据时间和随机数生成uid,这样使得聊天室用户名称可以重复。实际项目中,如果是需要用户登录,那么直接采用用户的uid来做标识就可以*/this.userid = this.genUid();this.username = username;d.getElementById("showusername").innerHTML = this.username;this.msgObj.style.minHeight = (this.screenheight - db.clientHeight + this.msgObj.clientHeight) + "px";this.scrollToBottom();//连接websocket后端服务器this.socket = io.connect('ws://realtime.plhwin.com:3000');//告诉服务器端有用户登录this.socket.emit('login', {userid:this.userid, username:this.username});//监听新用户登录this.socket.on('login', function(o){CHAT.updateSysMsg(o, 'login');});//监听用户退出this.socket.on('logout', function(o){CHAT.updateSysMsg(o, 'logout');});//监听消息发送this.socket.on('message', function(obj){var isme = (obj.userid == CHAT.userid) ? true : false;var contentDiv = '<div>'+obj.content+'</div>';var usernameDiv = '<span>'+obj.username+'</span>';var section = d.createElement('section');if(isme){section.className = 'user';section.innerHTML = contentDiv + usernameDiv;} else {section.className = 'service';section.innerHTML = usernameDiv + contentDiv;}CHAT.msgObj.appendChild(section);CHAT.scrollToBottom();});}};//通过“回车”提交用户名d.getElementById("username").onkeydown = function(e) {e = e || event;if (e.keyCode === 13) {CHAT.usernameSubmit();}};//通过“回车”提交信息d.getElementById("content").onkeydown = function(e) {e = e || event;if (e.keyCode === 13) {CHAT.submit();}};})();

至此所有的编码开发工作全部完成了,在浏览器中打开demo.plhwin.com/chat/就可以看到效果了。上面所有的客户端和服务端的代码可以从Github上获得,点这里跳转到Github项目主页,或者在命令行将代码Clone到本地。

git clone https://github.com/plhwin/nodejs-socketio-chat.git

下载本地后有两个文件夹 client  serverclient文件夹是客户端源码,可以放在Nginx/Apache的WebServer中,也可以放在Node.js的WebServer中。后面的server文件夹里的代码是websocket服务端代码,放在Node.js环境中,使用npm安装完 express  socket.io 后,node index.js 启动后端服务就可以了。






0 0