本文介绍如何通过原生WebSocket API封装一个具备自动重连、心跳检测、错误恢复等能力的稳健客户端。适用于需要长连接的实时通讯场景(如聊天室、实时数据监控等)。
核心功能亮点
- 自动重连机制 - 指数退避策略重连
- 心跳保活 - 双向检测连接活性
- 消息可靠性 - 失败消息自动重发
- 异常处理 - 错误分类处理机制
- 状态管理 - 精准控制连接生命周期
关键优化点说明
事件监听优化
- 改用
addEventListener
替代onopen
等属性赋值,允许多监听器共存 - 新增错误边界处理,防止初始化失败导致程序崩溃
- 改用
智能重连策略
- 避免网络恢复后仍要等待过长的延迟时间
状态管理改进
- 通过属性访问器清晰管理连接状态
消息可靠性增强
- 独立队列存储未发送消息
- 连接恢复时自动重发
配置与状态分离
- 独立config对象管理配置参数
- state对象清晰反映运行时状态
-
/** * WebSocket客户端实现 * @param {string} url - 连接地址 * @param {string|string[]} [protocols] - 可选协议 */ class WebSocketClient { // 默认配置(可通过构造函数覆盖) static config = { HEARTBEAT_INTERVAL: 30000, // 心跳间隔30s HEARTBEAT_TIMEOUT: 10000, // 心跳超时10s MAX_RECONNECT_ATTEMPTS: 5, // 最大重连次数 BASE_RECONNECT_DELAY: 1000, // 基础重连延迟 MAX_RECONNECT_DELAY: 30000, // 最大重连延迟30s MAX_PENDING_MESSAGES: 50 // 最大消息队列长度 }; // 运行时状态 state = { ws: null, reconnectCount: 0, //重新连接计数 isUserClosed: false, //用户自己关闭 pendingMessages: [], //待发送的消息队列 heartbeatTimer: null, //心跳计时器 heartbeatTimeoutTimer: null //消息超时计时器 }; constructor(url, protocols, options = {}) { // 合并配置 this.config = { ...WebSocketClient.config, ...options }; this.url = url; this.protocols = protocols; this.connect(); } /** 初始化连接 */ connect() { this.dispose(); // 清理旧连接 try { // 创建WebSocket实例 this.state.ws = this.protocols ? new WebSocket(this.url, this.protocols) : new WebSocket(this.url); this.bindEventListeners(); } catch (error) { console.error('WebSocket初始化失败:', error); this.handleReconnect(); } } /** 清理旧连接和定时器 */ dispose() { if (this.state.ws) { this.state.ws.close(); this.state.ws = null; } this.clearHeartbeat(); } /** 绑定事件监听器 */ bindEventListeners() { const { ws } = this.state; ws.addEventListener('open', (event) => { console.log('连接已建立', event); /** 重置重连状态 */ this.resetReconnect(); //开始心跳 this.startHeartbeat(); /** 发送队列中的消息 */ this.flushPendingMessages(); }); ws.addEventListener('error', (error) => { console.error('连接异常:', error); this.handleReconnect(); }); ws.addEventListener('close', (event) => { console.log('连接关闭:', event); this.handleClose(event); }); ws.addEventListener('message', (event) => { this.handleIncomingMessage(event); }); } /** 心跳管理 */ startHeartbeat() { this.clearHeartbeat(); this.state.heartbeatTimer = setInterval(() => { if (this.isConnected) { this.sendHeartbeat(); this.monitorHeartbeatResponse(); } }, this.config.HEARTBEAT_INTERVAL); } /** 发送心跳包 */ sendHeartbeat() { this.send(JSON.stringify({ type: 'ping', timestamp: Date.now() })); } /** 监测心跳响应 */ monitorHeartbeatResponse() { this.state.heartbeatTimeoutTimer = setTimeout(() => { console.warn('心跳响应超时,执行断开'); this.state.ws.close(); }, this.config.HEARTBEAT_TIMEOUT); } /** 处理接收消息 */ handleIncomingMessage(event) { try { const data = JSON.parse(event.data); if (data.type === 'ping') { //关闭超时计时器 clearTimeout(this.state.heartbeatTimeoutTimer); return; } this.onMessage?.(data); } catch (error) { console.error('消息解析失败:', error); this.onError?.(error); } } /** 重连控制 */ handleReconnect() { if (this.shouldReconnect) { const delay = this.calculateReconnectDelay(); console.log(`将在 ${delay}ms 后重试...`); setTimeout(() => { this.state.reconnectCount++; this.connect(); }, delay); } } /** 智能重连延迟计算 */ calculateReconnectDelay() { return Math.min( this.config.BASE_RECONNECT_DELAY * (2 ** this.state.reconnectCount), this.config.MAX_RECONNECT_DELAY ); } /** 发送消息(带队列控制) */ send(data) { if (this.isConnected) { this.state.ws.send(data); } else { if (this.state.pendingMessages.length >= this.config.MAX_PENDING_MESSAGES) { console.warn('消息队列已满,丢弃最旧消息'); this.state.pendingMessages.shift(); } this.state.pendingMessages.push(data); } } /** 清空心跳定时器 */ clearHeartbeat() { clearInterval(this.state.heartbeatTimer); clearTimeout(this.state.heartbeatTimeoutTimer); this.state.heartbeatTimer = null; this.state.heartbeatTimeoutTimer = null; } /** 重置重连状态 */ resetReconnect() { this.state.reconnectCount = 0; this.state.isUserClosed = false; } /** 处理连接关闭事件 */ handleClose(event) { this.clearHeartbeat(); if (!this.state.isUserClosed) { this.handleReconnect(); } } /** 发送队列中的消息 */ flushPendingMessages() { while (this.state.pendingMessages.length > 0 && this.isConnected) { this.state.ws.send(this.state.pendingMessages.shift()); } } // 公开方法 //关闭 close() { this.state.isUserClosed = true; this.dispose(); } //重链接 reconnect() { this.close(); this.state.isUserClosed = false; this.connect(); } // 状态判断 //判断socket是开启状态吗 get isConnected() { return this.state.ws?.readyState === WebSocket.OPEN; } //判断可以重连吗 get shouldReconnect() { return !this.state.isUserClosed && this.state.reconnectCount < this.config.MAX_RECONNECT_ATTEMPTS; } } // 使用示例 // const ws = new WebSocketClient('wss://echo.websocket.org'); // ws.onMessage = (data) => console.log('收到消息:', data); // ws.send('Hello World');
使用示例
// 初始化客户端 const client = new WebSocketClient('wss://api.example.com/ws'); // 监听消息 client.onMessage = (data) => { console.log('收到消息:', data); }; // 发送消息 document.getElementById('sendBtn').addEventListener('click', () => { client.send(JSON.stringify({ type: 'chat', content: 'Hello World' })); }); // 异常处理 client.onError = (error) => { console.error('连接异常:', error); };