摘要:用伪代码来模拟下长轮询的过程前端利用下面函数进行请求后端代码做如下更改利用随机数的大小来模拟是否有新数据有新数据来了长轮询的确减少了请求的次数,但是它也有着很大的问题,那就是耗费服务器的资源。
写在前面
最近由于利用node重构某个项目,项目中有一个实时聊天的功能,于是就研究了一下聊天室,在线demo|源码,欢迎大家反馈。这个聊天室的主要利用到了socket.io和express。这个聊天室支持群聊,私聊,支持发送图片(PS:大家在体验时最好开启两个浏览器,自问自答)。下面就来和大家分享下实现过程:
WebSocketHTML5一种新的协议。它实现了浏览器与服务器全双工通信。
为了更好的理解WebSocket,需要了解一下在没有WebSocket阶段是如何写聊天室这种实时系统的:
基于http协议浏览器可以实现单向通信,只能由浏览器发起请求(Request),服务器进行响应(Response),一个请求对应一个响应。由于服务器不能主动向客户端推送消息,于是普遍采用的方式就是轮询(polling),轮询实现起来非常简单,就是定时的利用ajax向服务器端进行请求。如果服务器有新的数据就返回新的数据,如果没有数据就返回空响应。用代码来模拟下就是这个样子的:
// 前端请求代码 function update (fn) { var xhr = new XMLHttpRequest(); xhr.open("get", "./update.php"); xhr.onreadystatechange = function(){ if(xhr.readyState === 4){ if(xhr.status == 200){ const res = JSON.parse(xhr.response); if (res.flag) { // 进行相应操作 // fn为接到响应后的处理函数 fn && fn(fn); } } } }; xhr.send(); } function polling () { update(); } setInterval(polling, 2000); // 后台响应代码 true, "data" => "有新数据来了" )); } else { echo json_encode(array( "flag" => false )); } ?>
这种定时请求的方式的关键在于间隔时间的选取,依据我在上面代码做的模拟,很少概率能拿到下真正的数据,多半的ajax请求是无效的,于是又有前辈基于轮询提出来了Comet(服务器推),这种技术可以通过长轮询(long polling)实现(还可以利用iframe),长轮询也是靠ajax实现客户端的请求,其流程为:客户端发起请求,服务器挂起请求,假若有新的数据返回,服务器响应客户端刚才的请求,客户端得到响应后继续请求服务器。用伪代码来模拟下长轮询的过程:
// 前端利用下面函数进行请求 function longPolling () { update(update); } longpolling(); // 后端代码做如下更改 true, "data" => "有新数据来了" )); break; } } ?>
长轮询的确减少了请求的次数,但是它也有着很大的问题,那就是耗费服务器的资源。
无论是轮询还是长轮询,还有着一个问题就是http并不是支持长连接很多人会说keep-alive不就是做到了长连接吗?然而并非如此,keep-alive是重用一个TCP连接,就是说http 1.1做到了一个TCP连接可以发送多个http请求,然而每个http请求还需要发送Request Header,每个请求的响应还会带着Response Header。对于轮询和长轮询来说伴随着真实数据的交换,还有进行的就是大量的http header的交换。
基于这些问题,WebSocket被提出,WebSocket可以理解为对http的一个补丁包,WebSocket使http变成了一个真正的长连接,握手阶段利用http协议,之后就不会再发起http请求了。下面来看下WebSocket握手的过程:
客户端的请求头比一般的http请求多出来几个字段:
Upgrade: websocket,Connection: Upgrade,利用这两个字段来告诉服务器,我要将协议升级为websocket。
Sec-WebSocket-Version: 13,来告诉服务器我想要使用的WebSocket的版本。
Sec-WebSocket-Key,其值采用base64编码的随机16字节长的字符序列,这个值会在响应头中回应。
Sec-WebSocket-Extensions,提供了一个客户端支持的协议扩展列表来供服务器选择,服务器只能选择一个,并且会将选择的扩展写入响应头的Sec-WebSocket-Extensions。
Sec-WebSocket-Protocol,与Sec-WebSocket-Extensions原理相似,用于协商应用子协议。
再来看看响应头:
Status Code,值为101,表示已经升级到WebSocket协议
Sec-WebSocket-Extensions告诉客户端服务器选择的协议扩展
Sec-WebSocket-Protocol告诉客户端服务器选择的子协议
Sec-WebSocket-Accept经服务器确认并且加密后的Sec-WebSocket-Key
还有一点值得关注的就是协议头由http/https换成了ws/wss,也标识真http完成了其使命,接下来的事情由WebSocket来负责啦!
socket.io由于写原生的WebSocket在处理低版本浏览器的兼容性上的困难,所以一般在写实时交互的这种项目时一般会利用到socket.io。socket.io并不仅仅是WebSocket,还包含着AJAX long polling,AJAX multipart streaming,JSONP Polling等。socket.io可以看做是基于engine.io的二次开发。通过emit和on可以轻松地实现服务器与客户端之间的双向通信,emit来发布事件,on来订阅事件。
用户登录/登出下面开始来写代码,我利用的构建工具是gulp,模板语言是jade,css预处理语言是less,假若也需要使用到这些,可以关注下我所在团队搭建的一个小的脚手架,先从app.js开始:
const users = {}, app = express(), server = require("http").createServer(app), io = require("socket.io").listen(server); // 将socket.io绑定到服务器上,使得任何连接到服务器的客户端都具有实时通信的功能 // 服务器来监听客户端 io.on("connection", (socket) => { // socket是返回的连接对象,两端的交互就是通过这个对象 });
需要创建一个对象(users)来存储在线用户,键值为用户昵称,为用户登录来订阅个事件:
socket.on("login", (nickname) => { if (users[nickname] || nickname === "system") { socket.emit("repeat"); } else { socket.nickname = nickname; users[nickname] = { name: nickname, socket: socket, lastSpeakTime: nowSecond() }; socket.emit("loginSuccess"); UsersChange(nickname, true); } }); socket.on("disconnect", () => { if (socket.nickname && users[socket.nickname]) { delete users[socket.nickname]; UsersChange(socket.nickname, false); } }); function UsersChange (nickname, flag) { io.sockets.emit("system", { nickname: nickname, size: Object.keys(users).length, flag: flag }); } function nowSecond () { return Math.floor(new Date() / 1000); }
用户登录时需要验证其昵称是否含有,假若函数,则触发在客户端的js代码中注册的repeat事件,反之触发loginSuccess事件并且登录成功后需要向所有的客户端来广播,所以利用了io.sockets.emit。repeat,loginSuccess,system,在src/js/index.js中进行注册,主要用于页面的显示,也就是一些dom操作,所以在这里没有什么好讲的。用户退出,直接调用默认事件disconnect就好,并将该用户从用户对象中移除。
心跳检测在用户的状态上的坑还是不少的,因为WebSocket中间过程比较复杂,经常会出现一些异常的情况,所以需要进行心跳检测,我采用的方式是服务端定时遍历用户列表,假若用户最后的发言时间与现在相比超过了5分钟,就将其视为掉线,从而避免了"用户undefined退出群聊"的这种情况。
function pong () { const now = nowSecond(); for (let k in users) { if (users[k].lastSpeakTime + MAX_LEAVE_TIME < now) { var socket = users[k].socket; users[k].socket.emit("disconnect"); socket.emit("nouser", "由于长时间未说话,您已经掉线,请重新刷新页面"); socket = null; } } } // 心跳检测 setInterval(pong, PONG_TIME); function UsersChange (nickname, flag) { io.sockets.emit("system", { nickname: nickname, size: Object.keys(users).length, flag: flag }); }写在最后
其实socket.io的使用真的非常简单,很容易就会上手,所以其余功能不再一一演示,大家可以看代码的实现(写的比较差,还请见谅),客户端代码中大量用到了L,相当于zepto的$,特别需要处理的是在私信和发送图片的处理上,私信需要处理不同消息框,到底把消息添加到那个消息框中,我利用了一个对象来存储这些信息(cache),cache的键名为用户的昵称(因为在注册时判断了其是否唯一,所以可以将其视为唯一的);键值为对象,对象属性如下图所示:
具体实现大家还是到源码中去看吧!
感谢王哇勇大神的HiChat和小胡子哥的blogChat
由于本人水平有限,如有错误,欢迎大家指出!
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/91020.html
摘要:简易版聊天室技术栈功能实现实时聊天创建房间表情包完善私聊效果登录服务端判断之前是否登录过聊天室,如果是则直接进入聊天室,否则跳转到登录页面。客户端发送创建房间和切换房间的事件给服务端。 Chat 简易版聊天室 技术栈 express socket.io 功能 实现 实时聊天 创建房间 表情包 完善 私聊 效果 登录 showImg(https://segmentfa...
摘要:项目简介主要是通过做一个多人在线多房间群聊的小项目来练手全栈技术的结合运用。编译运行开启服务,新建命令行窗口启动服务端,新建命令行窗口启动前端页面然后在浏览器多个窗口打开,注册不同账号并登录即可进行多用户多房间在线聊天。 项目简介 主要是通过做一个多人在线多房间群聊的小项目、来练手全栈技术的结合运用。 项目源码:chat-vue-node 主要技术: vue2全家桶 + socket....
摘要:云新闻云新闻收藏的使用需要注意的地方提交的是,而不是直接的状态变更可以包含任意异步操作。的使用利用实现了简单的聊天功能,在同一个服务器下。 title: Socket.io+vue打造新闻社区date: 2017-06-12 20:19:05 tags: [vue.js,javascript,socket.io] vue2.0 + socket.io打造一个DIY新闻社区(web第一...
摘要:异步最佳实践避免回调地狱前端掘金本文涵盖了处理异步操作的一些工具和技术和异步函数。 Nodejs 连接各种数据库集合例子 - 后端 - 掘金Cassandra Module: cassandra-driver Installation ... 编写 Node.js Rest API 的 10 个最佳实践 - 前端 - 掘金全文共 6953 字,读完需 8 分钟,速读需 2 分钟。翻译自...
摘要:但是需要注意的一点是协议是建立在协议基础之上的,需要经过一次握手。所以连接的发起方仍是客户端。是一个简洁而灵活的应用框架提供一系列强大特性帮助你创建各种应用。这也是为什么要采用协议来实现聊天室的原因。 从开始写到完善差不多断断续续差不多半个月时间,虽然还没有打到想要的效果但还是阶段性总结一下。(下一步加入打算视频通讯功能)本文默认你已掌握 node 相关基础知识 GitHub地址:ht...
阅读 3524·2021-11-25 09:43
阅读 3110·2021-10-08 10:04
阅读 1572·2019-08-26 12:20
阅读 2019·2019-08-26 12:09
阅读 549·2019-08-23 18:25
阅读 3533·2019-08-23 17:54
阅读 2284·2019-08-23 17:50
阅读 750·2019-08-23 14:33