WebSocket 浅实现一个简易聊天室
01 WebSocket 特点
WebSocket 协议,是全双工协议。它的特点:
控制开销
实时通讯
ws(wss)://
支持文本,二进制传输
基于 TCP 之下,服务端实现比较简单
02 实现想法
03 客户端的关键实现细节
前端浏览器,本身自带有 WebSocket
对象 ,所以通过 new
的形式,就可以得到 ws
实例
| this.ws.onopen = () => { console.log('onopen', this.ws.readyState) this.roomOpen = true this.ws.send(JSON.stringify({ userId: this.userName, userName: this.nickname, roomId: item.roomId, roomName: item.name, event: 'login', }))
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| this.ws.onmessage = (message) => { const data = JSON.parse(message.data) this.onlineNum = data.num if (data.event === 'login') { this.msgList.push({ content: `欢迎${data.userName}进入${data.roomName}房间~`, }) } else if (data.event === 'logout') { console.log('logout', data) this.msgList.push({ content: `${data.userName}离开房间`, }) } else { const self = this.userId === data.userId if (self) return this.msgList.push({ name: data.userName, self: false, content: data.content, }) } }
|
| this.ws.onclose = () => { this.removeAllTimeJob() Toast('您已离开房间') this.roomOpen = false this.msgList = [] this.onlineNum = 0 }
|
04 服务端的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
| const Websocket = require('ws')
const wss = new Websocket.Server({ port: 9001 })
const group = {}
const heartBeatTime = 50000
wss.on('connection', function (ws) { ws.isAlive = true ws.heartBeatTimeIntervalObj = -1 ws.heartBeatTimeoutObj = -1
const setHeartBeatTimeout = () => { if (ws.heartBeatTimeoutObj !== -1) { clearTimeout(ws.heartBeatTimeoutObj) ws.heartBeatTimeoutObj = -1 } ws.heartBeatTimeoutObj = setTimeout(() => { ws.close() ws.isAlive = false }, heartBeatTime) } setHeartBeatTimeout()
ws.on('message', function (message) { console.log('server receive message: ', message.toString()) const data = JSON.parse(message.toString()) setHeartBeatTimeout() if (data.event === 'login') { ws.enterInfo = data }
if (data.event === 'heartBeat' && data.content === 'ping') { ws.isAlive = true
if (ws.heartBeatTimeoutObj === -1) { clearTimeout(ws.heartBeatTimeoutObj) ws.heartBeatTimeoutObj = -1 } setHeartBeatTimeout()
ws.send( JSON.stringify({ event: 'heartBeat', content: 'pong', }) ) return }
if (typeof ws.roomId === 'undefined' && data.roomId) { ws.roomId = data.roomId if (typeof group[ws.roomId] === 'undefined') { group[ws.roomId] = 1 } else { group[ws.roomId]++ } }
data.num = group[ws.roomId]
wss.clients.forEach((client) => { if (client.readyState === Websocket.OPEN && client.roomId === ws.roomId) { client.send(JSON.stringify(data)) } }) })
ws.on('close', function (message) { group[ws.roomId]-- console.log('server close: ', message)
wss.clients.forEach(function each(client) { if ( client !== ws && client.readyState === Websocket.OPEN && client.roomId === ws.roomId ) { client.send( JSON.stringify({ ...ws.enterInfo, event: 'logout', num: group[ws.roomId], }) ) } }) }) })
|
04 心跳机制
引入心跳机制,是因为于在长连接的场景下,客户端和服务端并不是一直处于通信状态,如果双方长期没有沟通则双方都不清楚对方目前的状态,所以需要发送一段很小的报文告诉对方“我还活着”。
其他目的
服务端检测到某个客户端迟迟没有心跳过来可以主动关闭通道,让它下线;
客户端检测到某个服务端迟迟没有响应心跳也能重连获取一个新的连接。
| const setHeartBeatTimeout = () => { if (ws.heartBeatTimeoutObj !== -1) { clearTimeout(ws.heartBeatTimeoutObj) ws.heartBeatTimeoutObj = -1 } ws.heartBeatTimeoutObj = setTimeout(() => { ws.close() ws.isAlive = false }, heartBeatTime) }
setHeartBeatTimeout()
|
仓库地址“黄华峰 / WebSocket-chatroom · GitLab (kkgroup.cn)