目录

如何实现具备自动重连与心跳检测的WebSocket客户端

如何实现具备自动重连与心跳检测的WebSocket客户端

本文介绍如何通过原生WebSocket API封装一个具备自动重连、心跳检测、错误恢复等能力的稳健客户端。适用于需要长连接的实时通讯场景(如聊天室、实时数据监控等)。

核心功能亮点

  1. 自动重连机制 - 指数退避策略重连
  2. 心跳保活 - 双向检测连接活性
  3. 消息可靠性 - 失败消息自动重发
  4. 异常处理 - 错误分类处理机制
  5. 状态管理 - 精准控制连接生命周期
  6. 关键优化点说明

  7. 事件监听优化
  • 改用addEventListener替代onopen等属性赋值,允许多监听器共存
  • 新增错误边界处理,防止初始化失败导致程序崩溃
  1. 智能重连策略
  • 避免网络恢复后仍要等待过长的延迟时间
  1. 状态管理改进
  • 通过属性访问器清晰管理连接状态
  1. 消息可靠性增强
  • 独立队列存储未发送消息
  • 连接恢复时自动重发
  1. 配置与状态分离
  • 独立config对象管理配置参数
  • state对象清晰反映运行时状态
  1. /**
  • 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); };