前言
我们知道服务器是一种应答模式,也就是说服务器只能被动提供服务,而不会主动推送信息给客户端。
传统网站为了实现类似在线聊天的功能都是不断的给服务器发送信息询问是否有新消息也就是所谓的
轮询
。
这种方式有很明显的弊端:大量耗费服务器内存和宽带资源,因为不停的请求服务器,很多时候 并没有新的数据更新,因此绝大部分请求都是无效请求。
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
浏览器和服务器建立了webSocket连接后就可持续连接并双向数据传输。
浏览器可发送数据给服务器,服务器有信息后也可主动推送信息给浏览器
效果
项目地址:
github地址
预览地址:
预览地址
请用不同的浏览器测试两个账号
聊天功能
-
用户上/下线时提示聊天组所有的成员用户已上/下线 -
聊天室可发送图片和表情进行聊天 -
仿制qq的聊天时间格式
实现
前端涉及到的依赖有
react、antd、redux
以及
braft-editor富文本编辑器
后端涉及到的依赖有
koa
和
nodejs-websocket
。
这里我只会介绍大致的逻辑,详细的实现细节可以参考我的
源码
。
流程:
- 后台首先搭建websocket服务器
- 前后端建立websocket连接
- 前后端各自监听消息事件
- 开始聊天
我使用的是
nodejs-websocket
来搭建的websocket服务
var ws = require("nodejs-websocket")
// Scream server example: "hi" -> "HI!!!"
var server = ws.createServer(function (conn) {
console.log("New connection")
//监听浏览器发送过来的信息
conn.on("text", function (str) {
console.log("Received "+str)
conn.sendText(str)
})
//websocket连接关闭的事件
conn.on("close", function (code, reason) {
console.log("Connection closed")
})
}).listen(8081)
上面几行代码就已经在8081端口实现了一个websocket服务(
nodejs-websocket
更详细的api请参考官网)
浏览器与服务器建立websocket连接
//实例化websocket对象
const ws = new WebSocket("ws://"+window.location.hostname+":8081")
//建立连接时触发
ws.onopen = function(){
//发送数据到服务器
ws.send("第一次建立连接")
}
//监听服务器信息
ws.onmessage = function(event){
console.log(event.data)
}
注意:不管是服务器还是浏览器我们发送的信息都是
字符串类型的数据
。对于其他类型的数据请参考
nodejs-websocket
官网
这里我将服务器和浏览器发送的消息设置为两种类型:聊天消息、上线消息。
这样在浏览器和服务器才能对不同的消息类型做不同的处理。
const msgType = {
onlineInfo: 0, //关于在线列表
chatInfo: 1 //关于聊天内容
}
用户上线
- 浏览器第一次和服务器建立websocket连接时将用户信息发送给服务器
- 服务器在收到消息后将用户信息添加到在线列表中去,并在连接中保存当前用户信息
- 服务器将用户上线消息广播到所有与服务器成功建立连接的浏览器(消息类型为0)
- 浏览器收到服务器推送的消息后更新在线列表
这里我只做了一个聊天室,所以这里广播到所有客户端,如果想要实现多个聊天室或私人聊天室,只需将聊天目标作为信息一起传给服务器,服务器根据聊天目标去过滤广播。
用户聊天
- 前端将用户聊天内容(可以是图片、文字、表情)发送给服务器
- 服务器收到消息后将聊天内容保存到数据库并广播给所有客户端(消息类型为1)
- 浏览器收到服务器推送的消息后更新聊天列表
用户下线
- 服务器的在close事件中监听websocket连接关闭
- 用户关闭浏览器
- 服务器将下线用户弹出在线列表并广播给所有客户端(消息类型为0)
- 浏览器收到服务器推送的消息后更新在线列表
用户信息更改
- 用户信息更改时,发送新用户信息到服务器
- 服务器收到消息后更新当前用户信息(包括数据库的更新)
- 浏览器发现用户信息更新后,重新请求聊天列表和聊天记录
这里我就不放具体的实现代码了,建议先了解
websocket
和
nodejs-websocket
后再参考我的
源码
。
之所以用到
redux
是因为我想在用户登录成功后就建立
websocket
连接,所以放到了redux中去了。
另外我还仿制了qq聊天的时间格式,当聊天消息间隔3分钟时,在消息上方显示时间,这个时间我利用了
moment
进行转换
//处理时间
handleTime = (time, small) => {
if (!time) {
return ''
}
const HHmm = moment(time).format('HH:mm')
//不在同一年,就算时间差一秒都要显示完整时间
if (moment().format('YYYY') !== moment(time).format('YYYY')) {
return moment(time).format('YYYY-MM-DD HH:mm:ss')
}
//判断时间是否在同一天
if (moment().format('YYYY-MM-DD') === moment(time).format('YYYY-MM-DD')) {
return HHmm
}
//判断时间是否是昨天。不在同一天又相差不超过24小时就是昨天
if (moment().diff(time, 'days') === 0) {
return `昨天 ${HHmm}`
}
//判断时间是否相隔一周
if (moment().diff(time, 'days') < 7) {
const weeks = ['日', '一', '二', '三', '四', '五', '六']
return `星期${weeks[moment(time).weekday()]} ${HHmm}`
}
if (small) {
return moment(time).format('MM-DD HH:mm')
} else {
return moment(time).format('M月D日 HH:mm')
}
}