结论:WebSocket断线重连需结合readyState状态判断与心跳超时兜底,业务级心跳包须服务端明确响应;onclose中不可立即重连,应延迟检查readyState===CLOSED;心跳必须自定义+超时检测+服务端ACK;重连需指数退避、防抖限频、网络状态感知及服务端协同。

直接说结论:断线重连不能只靠 onclose 触发重连,必须结合 readyState 状态判断 + 心跳超时兜底;心跳也不能只发 PING 帧,得用业务级心跳包 + 服务端明确响应。
WebSocket.onclose 触发时,readyState 很可能还是 CLOSING
很多开发者一看到控制台报 WebSocket is already CLOSED 就立刻在 onclose 里调 new WebSocket(),结果频繁报错。这是因为:
-
onclose是事件回调,不是状态同步点——它可能在readyState === CLOSING时就触发,此时 socket 还没真正释放 - 手动调用
ws.close()后,readyState不会立刻变成CLOSED,中间有延迟(尤其弱网下可达数秒) - 如果在
CLOSING状态下立即新建连接,旧 socket 可能还在发帧,导致服务端混乱或拒绝新连接
正确做法是:在 onclose 中清空定时器、标记重连意向,然后用 setTimeout 延迟 100–300ms 再执行重连,并先检查 ws.readyState === CLOSED。
心跳检测必须用业务消息,别依赖原生 PING/PONG 帧
浏览器 WebSocket API 对 PING 帧是静默处理的:onmessage 不会收到,onping 事件根本不存在。服务端即使回了 PONG,客户端也感知不到是否成功送达。
- 所以必须自己定义心跳格式,比如
{ type: "heartbeat", ts: Date.now() } - 客户端发心跳后启动一个
setTimeout,比如 5s 超时;服务端收到后必须原样或带ack: true回复,客户端收到才清除超时定时器 - 连续 2–3 次心跳无响应,就主动
ws.close(),再走重连流程(而不是等onclose自然触发)
示例关键逻辑:
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
startHeartbeat() {
this.heartbeatTimer = setInterval(() => {
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({ type: 'heartbeat' }));
this.heartbeatTimeout = setTimeout(() => {
console.warn('heartbeat timeout');
this.ws?.close();
}, 5000);
}
}, 10000);
}
<p>this.ws.onmessage = (e) => {
const data = JSON.parse(e.data);
if (data.type === 'heartbeat_ack') {
clearTimeout(this.heartbeatTimeout);
}
};
</p>
重连要防抖、限频、退避,不能无脑 setTimeout(..., 2000)
固定间隔重连(如每次都等 2s)在真实网络中极易引发雪崩:大量客户端同时重连,打满服务端连接数或认证接口。
- 必须实现指数退避:第 1 次等 1s,第 2 次等 2s,第 3 次等 4s……上限建议 30s
- 加随机偏移(jitter),比如
Math.random() * 1000,避免集群重连同步 - 设置最大重试次数(如 5 次),失败后应降级为轮询或提示用户手动重试
- 重连前检查网络状态:
navigator.onLine为false时暂停重连,监听online事件恢复
服务端没配合,客户端心跳重连就是单机表演
客户端发了 10 次心跳,服务端没响应或响应不及时,重连逻辑照样失效。服务端至少要做到:
- 收到心跳包后必须同步回传确认(不能异步、不能进队列延迟)
- 对每个连接维护最后心跳时间戳,
idle_timeout(如 30s)无心跳则主动close - 关闭连接时,
close帧的code推荐用4001(自定义心跳超时),客户端可据此区分是主动关闭还是异常断开
最容易被忽略的一点:服务端返回的确认消息,必须走和业务消息相同的序列化/校验逻辑(比如同样用 JSON + 字段校验),否则客户端解析失败,心跳响应就等于没发。










