【原创】node+express+socket搭建一个实时推送应用
技术背景
Web领域的实时推送技术,也被称作Realtime技术。这种技术要达到的目的是让用户不需要刷新浏览器就可以获得实时更新。
应用场景:
- 监控系统:后台硬件热插拔、LED、温度、电压发生变化
- 即时通信系统:其它用户登录、发送信息
- 即时报价系统:后台数据库内容发生变化
技术实现方案:ajax long polling(ajax长轮询),comet(http长连接)、socket
这里有篇文章介绍了这几种技术,可以看一下。
http://www.ibm.com/developerworks/cn/web/wa-lo-comet/
http://jingyan.baidu.com/article/08b6a591e07ecc14a80922f1.html
websocket简介
HTTP是一种基于消息(message)的请求(request )/应答(response)协议。当我们在网页中点击一条链接(或者提交一个表单)的时候,浏览器给服务器发一个request message,然后服务器算啊算,答复一条response message。主动发起TCP连接的是client,接受TCP连接的是server。HTTP消息只有两种:request和response。client只能发送request message,server只能发送response message。一问一答,因此按HTTP协议本身的设计,服务器不能主动的把消息推给客户端。
因此,如果让服务器端也可以主动发送信息到客户端,就可以很大程度改进这些不足。WebSocket就是一个实现这种双向通信的新协议。
WebSocket是基于HTTP的功能追加协议
WebSocket最初由html5提出,但现在已经发展为一个独立的协议标准。WebSocket可以分为协议( Protocol )和 API 两部分,分别由 IETF 和W3C制定了标准。
先来看看WebSocket协议的建立过程。
为了实现WebSocket通信,首先需要客户端发起一次普通HTTP请求(也就是说,WebSocket的建立是依赖HTTP的)。请求报文可能像这样:
GET ws://websocket.example.com/ HTTP/1.1 Host: websocket.example.com Upgrade: websocket Connection: Upgrade Origin: http://example.com Sec-WebSocket-Key:pAloKxsGSHtpIHrJdWLvzQ== Sec-WebSocket-Version:13
其中HTTP头部字段 Upgrade: websocket
和 Connection: Upgrade
很重要,告诉服务器通信协议将发生改变,转为WebSocket协议。支持WebSocket的服务器端在确认以上请求后,应返回状态码为 101 Switching Protocols
的响应:
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: nRu4KAPUPjjWYrnzxDVeqOxCvlM=
其中字段 Sec-WebSocket-Accept
是由服务器对前面客户端发送的 Sec-WebSocket-Key
进行确认和加密后的结果,相当于一次验证,以帮助客户端确信对方是真实可用的WebSocket服务器。
验证通过后,这个握手响应就确立了WebSocket连接,此后,服务器端就可以主动发信息给客户端了。此时的状态比较像服务器端和客户端接通了电话,无论是谁有什么信息想告诉对方,开口就好了。
一旦建立了WebSocket连接,此后的通信就不再使用HTTP了,改为使用WebSocket独立的数据帧
整个过程像这样:
开始码砖
1.建立项目文件,安装node 和express框架
socket.io http://socket.io/docs/
服务器端安装socket.io
$ npm install socket.io
客户端下载socket.io.js
client是客户端文件 server是服务器端文件。
2.写界面
我的界面是这样的
html
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>聊天室</title> <link rel="stylesheet" type="text/css" href="css/index.css"/> </head> <body> <div class="main"> <div class="main-top"> socket.io demo </div> <div class="main-body"> <section class="chatRoomInfo"> <div class="info">当前共有<span class="chatNum">0</span>人在线。在线列表: <span class="chatList"></span></div> </section> <!--<section class="chatRoomTip"> <div>子木加入到聊天室</div> </section> <section class="user clearfix"> <span>子木</span> <div> 测试测试测试测试测试测试测试测试测试试测试测试测试测试测试测试测试测试测试测试测试 </div> </section> <section class="server clearfix"> <span>子木</span> <div> 测试测试测试 </div> </section>--> </div> <div class="main-footer clearfix"> <div class="input"> <input type="text" name="msg" id="msg" value="" /> </div> <button type="button" class="send">发送</button> </div> </div> <script src="js/jquery-2.1.0.js" type="text/javascript" charset="utf-8"></script> <script src="js/socket.io.js" type="text/javascript" charset="utf-8"></script> <script> //do something</script> </body> </html>
css
* {
margin: 0;
padding: 0;
}
.clearfix {
zoom: 1;
}
.clearfix:after {
clear: both;
content: '.';
display: block;
width: 0;
height: 0;
visibility: hidden;
}
.main {
width: 100%;
height: 100%;
font-size: 14px;
}
.main-top {
height: 30px;
background-color: #3d3d3d;
text-indent: 15px;
color: #ffffff;
font-size: 16px;
line-height: 30px;
}
.main-body {
background-color: #efeff4;
position: absolute;
top: 30px;
bottom: 50px;
width: 100%;
overflow-y: scroll;
scrollbar-3dlight-color: ;
}
.chatRoomInfo {
padding: 10px;
font-size: 12px;
color: #666;
}
.chatRoomTip {
text-align: center;
padding: 10px;
font-size: 12px;
color: #444;
}
.user {
width: 100%;
min-height: 38px;
min-width: 36px;
margin-bottom: 15px;
}
.user span {
float: right;
}
.user div {
float: right;
min-height: 38px;
min-width: 38px;
max-width: 70%;
line-height: 38px;
padding: 0 15px;
color: #FFFFFF;
margin-right: 10px;
word-break: break-all;
background-color: #007aff;
position: relative;
border-radius: 5px;
}
.user div:after {
content: "";
position: absolute;
right: -5px;
top: 4px;
width: 0;
height: 0;
border-top: solid transparent;
border-left: 7px solid #007aff;
border-bottom: 4px solid transparent;
}
.server {
width: 100%;
min-height: 38px;
min-width: 36px;
margin-bottom: 15px;
}
.server span {
float: left;
}
.server div {
float: left;
min-height: 38px;
min-width: 38px;
max-width: 70%;
line-height: 38px;
padding: 0 15px;
color: #FFFFFF;
margin-left: 10px;
word-break: break-all;
background-color: #007aff;
position: relative;
border-radius: 5px;
}
.server div:after {
content: "";
position: absolute;
left: -5px;
top: 4px;
width: 0;
height: 0;
border-top: solid transparent;
border-right: 7px solid #007aff;
border-bottom: 4px solid transparent;
}
.main-footer{
position: absolute;
bottom: 0;
width: 100%;
height: 50px;
}
.input{
float: left;
width: 80%;
height: 40px;
margin-top: 5px;
margin-left: 1%;
margin-right: 1%;
border: 1px solid #666666;
}
.input input{
width: 100%;
height: 40px;
outline: none;
border: none;
font-size: 14px;
color: #333;
}
.send{
float: left;
width: 16%;
height: 40px;
margin-top: 5px;
margin-left: 1%;
border: none;
background-color: #e8e8e8;
color: #007aff;
outline: none;
}
现在开始写逻辑
客户端代码实现
/*按钮点击效果*/ $('.send').mousedown(function(){ $(this).css({'background':"#007aff",'color':"#ffffff"}); }) $('.send').mouseup(function(){ $(this).css({'background':"#e8e8e8",'color':"#ffffff"}); }) /*socket*/ window.onload=function () { var username=prompt('请输入您的姓名'); if (!username){ alert('姓名必填'); history.go(0); } // username="子木"; userId=genUid(); var userInfo={ 'userid':userId, 'username':username }; //连接socket后端服务器 var socket=io.connect("ws://127.0.0.1:4000"); //通知用户有用户登录 socket.emit('login',userInfo); //监听新用户登录 socket.on('login',function (o) { updateMsg(o, 'login'); }); //监听用户退出 socket.on('logout',function (o) { updateMsg(o, 'logout'); }); //发送消息 socket.on('message',function (obj) { if(obj.userid==userId) { var MsgHtml='<section class="user clearfix">' +'<span>'+obj.username+'</span>' +'<div>'+obj.content+'</div>' +'</section>'; }else{ var MsgHtml='<section class="server clearfix">' +'<span>'+obj.username+'</span>' +'<div>'+obj.content+'</div>' +'</section>'; } $('.main-body').append(MsgHtml); $('.main-body').scrollTop(99999); }) $('.send').click(function () { var content=$('input[name="msg"]').val(); if (content){ var obj={ 'userid':userId, 'username':username, 'content':content } socket.emit('message',obj); $('input[name="msg"]').val(""); } }) } /*用户id生成*/ function genUid() { return new Date().getTime()+""+Math.floor(Math.random()*899+100); } function logout(){ socket.disconnect(); location.reload(); } /*监听函数*/ function updateMsg(o,action) { //当前在线列表 var onlineUser=o.onlineUser; //当前在线数 var onlineCount=o.onlineCount; //新加用户 var user=o.user; //更新在线人数 var userList=''; var separator = ''; for(key in onlineUser){ userList+=separator+onlineUser[key]; separator = '、'; } //跟新房间信息 $('.chatNum').text(onlineCount); $('.chatList').text(userList); //系统消息 if(action=='login'){ var sysHtml='<section class="chatRoomTip"><div>'+user.username+'进入聊天室</div></section>'; } if(action=="logout"){ var sysHtml='<section class="chatRoomTip"><div>'+user.username+'退出聊天室</div></section>'; } $(".main-body").append(sysHtml); $('.main-body').scrollTop(99999); }
服务器代码实现 app.js
var app = require('express')(); var http=require('http').Server(app); var io=require('socket.io')(http); app.get('/socket/client/index.html',function (req,res) { res.send('<h1>welcome</h1>'); }) //在线用户 var onlineUser={}; var onlineCount=0; io.on('connection',function (socket) { console.log('新用户登录'); //监听新用户加入 socket.on('login',function (obj) { socket.name=obj.userid; //检查用户在线列表 if(!onlineUser.hasOwnProperty(obj.userid)){ onlineUser[obj.userid]=obj.username; //在线人数+1 onlineCount++; } //广播消息 io.emit('login',{onlineUser:onlineUser,onlineCount:onlineCount,user:obj}); console.log(obj.username+"加入了聊天室"); }) //监听用户退出 socket.on('disconnect',function () { //将退出用户在在线列表删除 if(onlineUser.hasOwnProperty(socket.name)){ //退出用户信息 var obj={userid:socket.name, username:onlineUser[socket.name]}; //删除 delete onlineUser[socket.name]; //在线人数-1 onlineCount--; //广播消息 io.emit('logout',{onlineUser:onlineUser,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(4000, function(){ console.log('listening on *:4000'); });
代码全部贴上来
源码地址:https://github.com/zimuqi/socketChat
下载后安装好socket.io express后进入到server 目录下 直接node app.js。然后打开项目主页就可以看到效了。可以多打开几个窗口互动一下。
有兴趣的可以再去研究一下WebIM系统,实现类似微信,qq的功能,客户端可以看到好友在线状态,在线列表,添加好友,删除好友,新建群组等,消息的发送除了支持基本的文字外,还能支持表情、图片和文件。