WebSocket是一种网络传输协议,可以在单个TCP连接上进行全双工通信,位于OSI模型的应用层。 WebSocket使得客户端和服务器之间的数据交换变得更加简单,服务器可以主动向客户端发送消息。在WebSocket API中,浏览器和服务器只需要完成一个握手,就可以建立持久性的连接,并进行双向数据传输。
在WebSocket之前,浏览器(客户端)和服务器之间进行实时通信有以下几种方式:
这些方式都不是很好的方式,在这种情况下,HTML5定义了WebSocket协议,它能更好地节省服务器资源和带宽,并且能够更实时地进行通信
WebSocket 是独立的、建立在TCP上的协议。它建立连接的过程(handshake)如下:
其中Connection: Upgrade用于告知服务器进行协议升级,Upgrade: websocket用于告知想要升级的协议是Websocket,Set-Websocket-Key是一段随机生成的base64码,用于服务器返回响应结果时验证
我们可以使用Node.js编写一个简单的WebSocket服务器,代码如下:
const { WebSocketServer } = require("ws");const wss = new WebSocketServer({ port: 3003 }, () => { console.log("websocket服务在3003端口开启了");});wss.on("connection", (socket) => { socket.on("message", (data) => { console.log("接收到了客户端的消息", data.toString()); socket.send("resp" + data.toString()); });});
对于客户端(浏览器)的代码,我们可以直接使用WebSocket API进行编写:
<div> <input type="text" class="message"> <button class="send-btn">发送消息</button></div><script> const ws = new WebSocket('ws://localhost:3003') ws.addEventListener('open', (e) => { console.log('websocket连接已建立', e); }) ws.addEventListener('message', (e) => { console.log('接收到的消息内容', e.data); }) ws.addEventListener('close', (e) => { console.log('websocket连接关闭了', e); }) ws.onerror = (e) => { console.log('websocket连接出错', e); } const messageEl = document.querySelector('.message') const sendBtnEl = document.querySelector('.send-btn') sendBtnEl.addEventListener('click', () => { ws.send(messageEl.value) })</script>
注意到WebSocket继承自EventTarget接口,因此我们可以使用addEvenListener的方式对WebSocket实例进行事件监听,从而对WebSocket实例的open、message、close、error事件进行监听处理,它们的触发时机如下:
在浏览器与服务器建立WebSocket连接之后,我们可以在浏览器控制台中查看具体发送和接收到的消息:
在上面,我们实现了一个简单的WebSocket通信案例,但是在实际使用中,我们还需要考虑以下几种异常情况:
针对上面的问题,我们可以采用以下的方案:
对于心跳包的实现,存在两种方式:
这里,我们来对方式一进行实现: 首先,我们要知道在什么时候开始发送心跳包?
那么要如何进行一次心跳检测呢?
对于重连操作,由于浏览器原生的WebSocket API并没有提供reconnect操作,因此,我们需要自己实现它。具体的做法为:重新创建一个WebSocket实例,并重新添加用户自定义的事件监听
完整的代码实现如下:
class `WebSocketHeartbeat` { constructor(url) { this.url = url; this.pingTimeout = 10000; // 心跳消息的发送间隔 this.pongTimeout = 1000; // pong消息允许的最大延时 this.pingTimeoutId = null; // 用于发送心跳消息的延时器 this.pongTimeoutId = null; // 用于检测服务器是否及时返回pong消息的延时器 this.isReconnecting = false; // 用于记录当前是否处于重连的状态 this.reconnectTimeout = 1000; // 尝试重连的时间间隔 this.isForbiddenReconnect = false; // 用于标记是否要尝试重连 this.onopen = () => {}; this.onclose = () => {}; this.onmessage = () => {}; this.onerror = () => {}; this.createWebSocket(); } // 创建一个websocket实例 createWebSocket() { this.ws = new WebSocket(this.url); this.init(); } // 添加事件监听 init() { this.ws.onopen = (e) => this.handleOpen(e); this.ws.onclose = (e) => this.handleClose(e); this.ws.onerror = (e) => this.handleError(e); this.ws.onmessage = (e) => this.handleMessage(e); } handleOpen(e) { console.log("websocket连接已建立", e); this.onopen(e); // 开始进行心跳检测 this.checkHeartbeat(); } handleMessage(e) { console.log("接收到的消息内容", e.data); this.onmessage(e); // 收到消息后,再次进行心跳检测 this.checkHeartbeat(); } handleClose(e) { console.log("websocket连接关闭了", e); this.onclose(e); // 尝试重连操作 this.reconnect(); } handleError(e) { console.log("websocket连接出错", e); this.onerror(e); this.reconnect(); } send(data) { this.ws.send(data); } // 用于用户主动断开连接 close() { this.ws.close(); this.resetHeartbeat(); this.isForbiddenReconnect = true; } // 原生的WebSocket并没有提供reconnect APi, // 因此我们此处reconnect实际上做的操作是重新创建一个WebSocket实例 reconnect() { console.log("进行重连~"); // 在重新创建连接的时候应该确保一次只创建一个,并且不要频繁创建 if (this.isReconnecting || this.isForbiddenReconnect) return; this.isReconnecting = true; setTimeout(() => { this.createWebSocket(); this.isReconnecting = false; }, this.reconnectTimeout); } checkHeartbeat() { this.resetHeartbeat(); this.tryHeartbeat(); } resetHeartbeat() { clearTimeout(this.pingTimeoutId); clearTimeout(this.pongTimeoutId); } tryHeartbeat() { this.pingTimeoutId = setTimeout(() => { this.send("heartbeat"); // 如果超过一定时间(约定的pong返回时间)还没有接收到消息,此时需要进行重连操作 this.pongTimeoutId = setTimeout(() => { console.log("需要进行重连操作"); this.reconnect(); }, this.pongTimeout); }, this.pingTimeout); }}export { WebSocketHeartbeat };// 心跳消息(ping)的发送时机:// 首先是发送间隔,这个应该是可以设置的// 在建立websocket连接之后就开始尝试发送一次心跳消息// 服务端会返回pong消息或者其它消息给客户端,此时再次尝试发送一次心跳消息
用户的使用案例:
import { WebSocketHeartbeat } from './websocket-heartbeat.js'const ws = new WebSocketHeartbeat('ws://localhost:3003')ws.onmessage = ({ data }) => { console.log('用户处理消息', data);}const messageEl = document.querySelector('.message')const sendBtnEl = document.querySelector('.send-btn')const closeBtnEl = document.querySelector('.close-btn')sendBtnEl.addEventListener('click', () => { ws.send(messageEl.value)})closeBtnEl.addEventListener('click', () => { ws.close()})
作者:loftyamb
链接:
https://juejin.cn/post/7359900973991067685
来源:稀土掘金