theme: channing-cyan
前言
学习一下 WebSocket 协议,在这之前我使用 nodejs-websocket 这个包解决,使用比较简单,但是我发现那样不利于自己对该协议的理解,于是我打算用比较官方的依赖 ws 包来进行 WebSocket 协议实践。
创建项目下载依赖
$ mkdir project && cd project $ npm init -y $ npm i ws @types/ws
服务端
首先我们先编服务端代码 webSocket.js。
引入我们的 ws 和协议包 http,并创建 WebSocket 服务。
import {WebSocketServer} from 'ws' import {createServer} from 'http' const httpServer = createServer() // 创建 WebSocket 服务 const wss = new WebSocketServer({ noServer: true })
然后我们编写握手事件 upgrade ,我们这里选择当接收到的请求头属性 sec-websocket-protocol 为 conn 时进行连接。
import {WebSocketServer} from 'ws' import {createServer} from 'http' const httpServer = createServer() // 创建 WebSocket 服务 const wss = new WebSocketServer({ noServer: true }) // 握手事件 httpServer.on('upgrade', (req, socket, head) => { if (req.headers['sec-websocket-protocol'] === 'conn') { wss.handleUpgrade(req, socket, head, (ws) => { wss.emit('connection', ws, req) }) } })
然后我们编写连接事件,当连接成功时,我们可以往客户端发送一个连接的标志。
import {WebSocketServer} from 'ws' import {createServer} from 'http' const httpServer = createServer() // 创建 WebSocket 服务 const wss = new WebSocketServer({ noServer: true }) // 握手事件 httpServer.on('upgrade', (req, socket, head) => { if (req.headers['sec-websocket-protocol'] === 'conn') { wss.handleUpgrade(req, socket, head, (ws) => { wss.emit('connection', ws, req) }) } }) // 连接事件 wss.on('connection', (ws) => { // 往客户端发送 connected 事件,我们使用 type 来进行事件标识,这样方便客户端处理 ws.send(JSON.stringify({type: 'connected'})) })
当然,连接完成我们同时可以在连接事件中添加对客户端传来数据的监听,就实现了客户端往服务端发送事件。
wss.on('connection', (ws) => { ws.send(JSON.stringify({type: 'connected'})) // message 监听事件事件,客户端传来的消息都走这里。 ws.on('message', (data, isBinary) => { // 由于我们无法确定传过来的数据类型,因此要用 isBinary 区分 buffer 转化为字符串 const receiveData = isBinary ? data : data.toString() console.log(receiveData) }) })
在加个对异常错误 error 的监听。
wss.on('error', (e) => { const {code} = e if (code !== 'EADDRINUSE') { console.error( `WebSocket server error:\n${e.stack || e.message}`, ); } });
这样我们就完成了 httpServer 的 WebSocket 协议服务端内容,但是假如我们有多个 httpServer 呢?我们不如把 WebSocket 协议内容封装成一个函数,方便复用。
import {WebSocketServer} from 'ws' import {createServer} from 'http' const createWebSocketServer = (httpServer) => { const wss = new WebSocketServer({ noServer: true }) // 握手事件 httpServer.on('upgrade', (req, socket, head) => { if (req.headers['sec-websocket-protocol'] === 'conn') { wss.handleUpgrade(req, socket, head, (ws) => { wss.emit('connection', ws, req) }) } }) wss.on('connection', (ws) => { ws.send(JSON.stringify({type: 'connected'})) ws.on('message', (data, isBinary) => { const receiveData = isBinary ? data : data.toString() console.log(receiveData) }) }) wss.on('error', (e) => { const {code} = e if (code !== 'EADDRINUSE') { console.error( `WebSocket server error:\n${e.stack || e.message}`, ); } }); // 返回 发送信息的方法 、 关闭的方法 还有 对应的服务 wss return { send(message) { wss.clients.forEach((ws) => { if (ws.readyState === 1) { ws.send(message) } }) }, close() { wss.close() }, wss } }
然后我们就可以很容易的复用了。
const httpServer1 = createServer() const httpServer2 = createServer() const ws1 = createWebSocketServer(httpServer1) const ws2 = createWebSocketServer(httpServer2)
我们暂时只需要启动一个服务,我们将它启动在 3000 端口,然后顺便写一个定时器,不停向服务端发送消息来进行测试。
最终服务端完整代码:
import {WebSocketServer} from 'ws' import {createServer} from 'http' const createWebSocketServer = (httpServer) => { const wss = new WebSocketServer({ noServer: true }) // 握手事件 httpServer.on('upgrade', (req, socket, head) => { if (req.headers['sec-websocket-protocol'] === 'conn') { wss.handleUpgrade(req, socket, head, (ws) => { wss.emit('connection', ws, req) }) } }) // 连接事件 wss.on('connection', (ws) => { // 往客户端发送 connected 事件,我们使用 type 来进行事件标识,这样方便客户端处理 ws.send(JSON.stringify({type: 'connected'})) // message 监听事件事件,客户端传来的消息都走这里。 ws.on('message', (data, isBinary) => { // 由于我们无法确定传过来的数据类型,因此要用 isBinary 区分 buffer 转化为字符串 const receiveData = isBinary ? data : data.toString() console.log(receiveData) }) }) wss.on('error', (e) => { const {code} = e if (code !== 'EADDRINUSE') { console.error( `WebSocket server error:\n${e.stack || e.message}`, ); } }); // 返回 发送信息的方法 、 关闭的方法 还有 对应的服务 wss return { send(message) { wss.clients.forEach((ws) => { if (ws.readyState === 1) { ws.send(message) } }) }, wss, close() { wss.close() } } } const httpServer = createServer() const ws = createWebSocketServer(httpServer) const sendMessage = (type, data) => { ws.send(JSON.stringify({type, data})) } setInterval(() => { sendMessage('console', `服务端定时向客户端发送消息 时间:${new Date().toLocaleString()}`) }, 3000) httpServer.listen(3000, () => { console.log('服务器开启') })
$ node webSocket.js 服务器开启
客户端
首先我们需要检测当前客户端浏览器是否支持 WebSocket ,如果支持我们尝试连接。
http 协议下我们使用 ws: 连接,https 可能需要使用 wss: 。
还记得我们在服务端设置了请求头属性 sec-websocket-protocol 为 conn 时进行连接,所以我们要传递一个字符串 conn
<body> <script> if ('WebSocket' in window) { const ws = new WebSocket('ws://localhost:3000', 'conn'); } </script> </body>
完成连接的步骤之后就是加入对服务端事件的监听,该监听事件为 message ,还可以顺便加上对连接关闭 close 的监听。
<body> <script> if ('WebSocket' in window) { const ws = new WebSocket('ws://localhost:3000', 'conn'); let pingTimer = null ws.addEventListener('message', async ({data}) => { const json = JSON.parse(data) // 我们通过和服务端商量的使用 type 的方式处理不同的事件。 if (json.type === 'connected') { console.log('[webSocket] connected.') pingTimer = setInterval(() => ws.send('心跳'), 30000) } // 定时对服务端发送消息告诉服务端客户端还 “活着” if (json.type === 'console') { console.log(json.data) } }) ws.addEventListener('close', async () => { if (pingTimer) clearInterval(pingTimer); console.info('[webSocket] disconnected.'); }); } </script> </body>
我们还可以加一个主动向服务端发送消息的测试按钮。
最后完整的客户端代码:
<body> <button onclick="send()"> 向服务器发送消息 </button> <script> if ('WebSocket' in window) { const ws = new WebSocket('ws://localhost:3000', 'conn'); let pingTimer = null ws.addEventListener('message', async ({data}) => { const json = JSON.parse(data) // 我们通过和服务端商量的使用 type 的方式处理不同的事件。 if (json.type === 'connected') { console.log('[webSocket] connected.') pingTimer = setInterval(() => ws.send('心跳'), 30000) } // 定时对服务端发送消息告诉服务端客户端还 “活着” if (json.type === 'console') { console.log(json.data) } }) ws.addEventListener('close', async () => { if (pingTimer) clearInterval(pingTimer); console.info('[webSocket] disconnected.'); }); function send() { ws.send(`客户端向服务端发送消息 时间:${new Date().toLocaleString()}`) } } </script> </body>
进行测试
尾言
如果觉得文章对你有帮助的话,欢迎点赞收藏哦,有什么错误或者意见建议也可以留言,感谢~