WebSocket的介绍:
WebSocket是一种在单个TCP连接上进行全双工通信的协议,它允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。
WebSocket 的优势:
较少的控制开销:连接创建后,数据交换时协议头较小。
更强的实时性:由于协议是全双工的,所以服务器可以随时主动发送数据给客户端。
保持连接状态:与HTTP不同,WebSocket需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息。
更好的二进制支持:WebSocket定义了二进制帧,可以更轻松地处理二进制数据。
支持扩展:开发者可以扩展协议、实现部分自定义的子协议。
项目需求:
由于最近项目上的大屏接口要换成WebSocket进行实时数据展示,于是需要对WebSocket进行封装,主要目标是要实现与后端持续保持心跳连接,不要让WebSocket中断;例如每10秒就要和后端通讯一次,发送和后端沟通好固定的心跳标识符,后端拿到心跳标识符就会持续保持连接,如果一直没有收到前端返回的心跳标识符,那么就会中断WebSocket的连接。
源代码
const DEFAULT_OPTIONS = { // 大屏通常需要长期保持连接,默认无限重连;可设置为 0 禁用重连。 maxReconnectAttempts: Infinity, reconnectDelay: 1000, maxReconnectDelay: 30000, reconnectDecay: 2, protocols: undefined, onOpen: null, onClose: null, onError: null, onReconnect: null, }; export default class WebSocketClient { /** * @param {string} url WebSocket 服务地址 * @param {Function|Object} callback 接收消息的回调,或配置对象 * @param {Object} options 重连及生命周期配置 */ constructor(url, callback, options = {}) { if (callback && typeof callback === 'object') { options = callback; callback = options.onMessage; } this.url = url; this.callback = typeof callback === 'function' ? callback : null; this.options = { ...DEFAULT_OPTIONS, ...options }; this.ws = null; this.reconnectAttempts = 0; this.reconnectTimer = null; this.connectPromise = null; this.shouldReconnect = false; } /** * 开启连接。重复调用时会复用正在建立的连接,不会创建多个 WebSocket。 * @returns {Promise<Event|undefined>} */ start() { this.shouldReconnect = true; this.#clearReconnectTimer(); if (this.ws?.readyState === WebSocket.OPEN) { return Promise.resolve(); } if (this.ws?.readyState === WebSocket.CONNECTING && this.connectPromise) { return this.connectPromise; } return this.#connect(); } /** start 的语义化别名。 */ open() { return this.start(); } /** * 发送原始 WebSocket 数据。 * @returns {boolean} 是否发送成功 */ send(message) { if (!this.isConnected) { console.warn('WebSocket 未连接,消息发送失败'); return false; } try { this.ws.send(message); return true; } catch (error) { this.#emitError(error); return false; } } /** 动态设置接收消息的回调。 */ setMessageHandler(callback) { this.callback = typeof callback === 'function' ? callback : null; return this; } /** * 主动关闭连接。主动关闭后不会重连,且会清除尚未执行的重连任务。 */ close(code = 1000, reason = 'Closed by client') { this.shouldReconnect = false; this.reconnectAttempts = 0; this.#clearReconnectTimer(); const socket = this.ws; if (!socket) return; if ( socket.readyState === WebSocket.CONNECTING || socket.readyState === WebSocket.OPEN ) { socket.close(code, reason); } else if (socket.readyState === WebSocket.CLOSED) { this.ws = null; } } get isConnected() { return this.ws?.readyState === WebSocket.OPEN; } get readyState() { return this.ws?.readyState ?? WebSocket.CLOSED; } #connect() { let socket; try { socket = this.options.protocols ? new WebSocket(this.url, this.options.protocols) : new WebSocket(this.url); } catch (error) { this.#emitError(error); this.#scheduleReconnect(); return Promise.reject(error); } this.ws = socket; this.connectPromise = new Promise((resolve, reject) => { let settled = false; const resolveConnection = (event) => { if (settled) return; settled = true; resolve(event); }; const rejectConnection = (error) => { if (settled) return; settled = true; reject(error); }; socket.onopen = (event) => { if (socket !== this.ws) return; this.reconnectAttempts = 0; this.connectPromise = null; resolveConnection(event); this.#invoke(this.options.onOpen, event); }; socket.onmessage = (event) => { if (socket !== this.ws) return; this.#invoke(this.callback, event.data, event); }; socket.onerror = (event) => { if (socket !== this.ws) return; const error = new Error('WebSocket 连接或通信异常'); error.event = event; rejectConnection(error); this.#emitError(error, event); }; socket.onclose = (event) => { if (socket !== this.ws) return; this.ws = null; this.connectPromise = null; rejectConnection(new Error(`WebSocket 连接已关闭(code: ${event.code})`)); this.#invoke(this.options.onClose, event); if (this.shouldReconnect) { this.#scheduleReconnect(); } }; }); return this.connectPromise; } #scheduleReconnect() { if (!this.shouldReconnect || this.reconnectTimer) return; const maxAttempts = this.options.maxReconnectAttempts; if (this.reconnectAttempts >= maxAttempts) { console.warn('WebSocket 已达到最大重连次数,停止重连'); return; } this.reconnectAttempts += 1; const delay = Math.min( this.options.reconnectDelay * this.options.reconnectDecay ** (this.reconnectAttempts - 1), this.options.maxReconnectDelay, ); this.#invoke(this.options.onReconnect, this.reconnectAttempts, delay); this.reconnectTimer = setTimeout(() => { this.reconnectTimer = null; if (!this.shouldReconnect) return; // 重连由类内部维护,捕获失败,避免产生未处理的 Promise rejection。 this.#connect().catch(() => {}); }, delay); } #clearReconnectTimer() { if (!this.reconnectTimer) return; clearTimeout(this.reconnectTimer); this.reconnectTimer = null; } #emitError(error, event) { if (typeof this.options.onError === 'function') { this.#invoke(this.options.onError, error, event); return; } console.error('WebSocket 异常:', error); } #invoke(callback, ...args) { if (typeof callback !== 'function') return; try { callback(...args); } catch (error) { console.error('WebSocket 回调执行异常:', error); } } }用法
import WebSocketClient from '@/request/ws' export default { data(){ return{ wsClient:null } }, mounted(){ this.wsClient = new WebSocketClient('ws://192.168.31.21:8090/ws', this.getWSMessage); this.wsClient.start() }, methods:{ getWSMessage(data){ console.log(data,'ddddddddddddddd') }, sendMessage(){ let obj = { type: "555", data: "111111" } this.wsClient.send(JSON.stringify(obj)) } }, beforeDestroy() { this.wsClient.close() } }