📋 关键逻辑详解
指数退避重连:每次重连延迟加倍(baseDelay * 2^n),并设置上限(maxDelay),避免无限增长和网络风暴。
bG9pajNqLmNvbQ== # bv.uxhl7c.cn#gjasp?gsgjop-kk#asd
心跳保活:定期发送ping,若未收到pong则主动断开触发重连,及时清理“僵尸连接”。
bG9pajNqLmNvbQ== # gn.uxhl7c.cn#gjasp?gsgjop-kk#asd
状态管理:明确的连接状态(CONNECTING, CONNECTED, DISCONNECTED, CLOSED)是逻辑清晰的基石。
bG9pajNqLmNvbQ== # rx.uxhl7c.cn#gjasp?gsgjop-kk#asd
消息队列:在断开时缓存待发送消息,连接恢复后自动补发,防止数据丢失(注意:对于严格有序或过时无效的消息,需额外设计)。
bG9pajNqLmNvbQ== # wq.uxhl7c.cn#gjasp?gsgjop-kk#asd
连接健康检查:除了心跳,也可监听 onclose 事件,根据关闭码(如 1000 为正常关闭,1006 为异常断开)决定是否重连。
bG9pajNqLmNvbQ== # mv.uxhl7c.cn#gjasp?gsgjop-kk#asd
flowchart TD
A[“初始化 WebSocket 管理器”] --> B[“创建连接
(初始为CONNECTING状态)”]
B --> C{连接成功?}
C -- 是 --> D[“状态: CONNECTED
重置重连计数,开始心跳”]
D --> E{“运行期间...”}
E -- “收到消息” --> F[“处理业务消息,
重置心跳计时”]
E -- “心跳计时器超时” --> G[“主动断开,触发重连”]
C -- 否/网络错误 --> H[“状态: DISCONNECTED
进入重连逻辑”]
subgraph H [重连逻辑]
H1[“计算退避延迟
(指数退避 + 随机抖动)”]
H2[“延迟等待”]
H3[“尝试创建新连接”]
end
H3 --> I{“重连成功?”}
I -- 是 --> D
I -- 否 --> H
E -- “连接异常关闭/错误” --> H
E -- “手动调用 close()” --> J[“状态: CLOSED
清理资源,停止所有定时器”]
🛠️ 核心实现方案
这里提供一个封装了完整重连逻辑的 ReconnectingWebSocket 类,你可以直接使用或参考其思想。
bG9pajNqLmNvbQ== # lp.uxhl7c.cn#gjasp?gsgjop-kk#asd
class ReconnectingWebSocket {
constructor(url, protocols = []) {
this.url = url;
this.protocols = protocols;
this.ws = null;
// 状态管理
this.status = 'DISCONNECTED'; // 'CONNECTING', 'CONNECTED', 'DISCONNECTED', 'CLOSED'
// 重连控制
this.reconnectCount = 0;
this.maxReconnectAttempts = Infinity; // 最大重连次数,可设置
this.baseDelay = 1000; // 基础重连延迟(毫秒)
this.maxDelay = 30000; // 最大重连延迟
// 心跳控制
this.heartbeatInterval = 30000; // 心跳间隔
this.heartbeatTimer = null;
this.pongReceived = true; // 是否收到上一次心跳的回复
// 消息队列(在断开时缓存消息)
this.messageQueue = [];
this.connect();
}
connect() {
if (this.status === 'CONNECTED' || this.status === 'CONNECTING') {
return;
}
this.status = 'CONNECTING';
console.log(`[WebSocket] 连接中... 尝试次数: ${this.reconnectCount + 1}`);
try {
this.ws = new WebSocket(this.url, this.protocols);
this.setupEventHandlers();
} catch (error) {
console.error('[WebSocket] 创建连接失败:', error);
this.handleDisconnect();
}
}
setupEventHandlers() {
this.ws.onopen = () => {
console.log('[WebSocket] 连接已建立');
this.status = 'CONNECTED';
this.reconnectCount = 0; // 连接成功,重置重连计数
this.startHeartbeat();
// 连接建立后,发送积压的消息
this.flushMessageQueue();
};
this.ws.onmessage = (event) => {
// 收到任何消息都认为连接是活跃的,重置心跳标志
this.pongReceived = true;
// 在这里处理业务消息
this.handleMessage(event.data);
};
this.ws.onclose = (event) => {
console.log(`[WebSocket] 连接关闭,代码: ${event.code}, 原因: ${event.reason}`);
this.cleanup();
this.handleDisconnect();
};
this.ws.onerror = (error) => {
console.error('[WebSocket] 连接错误:', error);
// onerror 之后通常会触发 onclose,所以主要逻辑放在 onclose 里
this.ws.close(); // 确保触发 onclose
};
}
handleDisconnect() {
if (this.status === 'CLOSED') {
return; // 如果是手动关闭,则不重连
}
this.status = 'DISCONNECTED';
this.stopHeartbeat();
// 检查是否超过最大重连次数
if (this.reconnectCount >= this.maxReconnectAttempts) {
console.error(`[WebSocket] 已达最大重连次数 (${this.maxReconnectAttempts}),停止重连`);
return;
}
// 计算下一次重连的延迟(指数退避 + 随机抖动)
const delay = Math.min(
this.baseDelay * Math.pow(2, this.reconnectCount),
this.maxDelay
) + Math.random() * 1000; // 增加最多1秒的随机抖动,避免客户端同时重连
console.log(`[WebSocket] ${delay.toFixed(0)}ms 后尝试第 ${this.reconnectCount + 1} 次重连`);
setTimeout(() => {
this.reconnectCount++;
this.connect();
}, delay);
}
// 心跳机制
startHeartbeat() {
this.pongReceived = true;
if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
this.heartbeatTimer = setInterval(() => {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
if (!this.pongReceived) {
console.warn('[WebSocket] 心跳无响应,主动断开连接');
this.ws.close(); // 触发重连逻辑
return;
}
this.pongReceived = false;
// 发送心跳ping(这里使用简单的文本,需与后端约定)
this.ws.send('__ping__');
}
}, this.heartbeatInterval);
}
stopHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
}
// 消息处理
send(data) {
if (this.status === 'CONNECTED' && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(data);
} else {
console.warn('[WebSocket] 连接未就绪,消息已加入队列:', data);
this.messageQueue.push(data); // 缓存消息,待连接恢复后发送
}
}
flushMessageQueue() {
while (this.messageQueue.length > 0) {
const message = this.messageQueue.shift();
this.ws.send(message);
}
}
handleMessage(data) {
// 过滤心跳回复
if (data === '__pong__') {
return;
}
// TODO: 在这里处理你的业务消息
console.log('[WebSocket] 收到消息:', data);
}
cleanup() {
this.stopHeartbeat();
if (this.ws) {
this.ws.onopen = null;
this.ws.onclose = null;
this.ws.onerror = null;
this.ws.onmessage = null;
this.ws = null;
}
}
// 手动关闭,不再重连
close() {
this.status = 'CLOSED';
if (this.ws) {
this.ws.close(1000, 'Manual closure');
}
this.cleanup();
console.log('[WebSocket] 连接已手动关闭');
}
}
// 使用示例
const wsClient = new ReconnectingWebSocket('wss://your-server.com/ws');
wsClient.send(JSON.stringify({ type: 'hello' }));
🚀 生产环境进阶考虑
网络状态感知:可结合 navigator.onLine API,在离线时暂停重连尝试。
bG9pajNqLmNvbQ== # xf.uxhl7c.cn#gjasp?gsgjop-kk#asd
认证重连:如果连接需要认证,重连时可能需要重新携带Token,可在 onopen 中发送认证消息。
bG9pajNqLmNvbQ== # ia.uxhl7c.cn#gjasp?gsgjop-kk#asd
重连成功回调:可暴露 onReconnected 事件,通知应用层恢复数据同步。
bG9pajNqLmNvbQ== # vz.uxhl7c.cn#gjasp?gsgjop-kk#asd
监控与日志:将重连次数、断开原因上报到监控系统,便于分析稳定性。
bG9pajNqLmNvbQ== # ev.uxhl7c.cn#gjasp?gsgjop-kk#asd