结论:别手写重连逻辑,优先用成熟库;自研必须处理readystate跃迁、错误码分类、指数退避和心跳同步四件事。websocket.onclose后需确认readystate为closed(3)再新建连接,区分正常关闭(1000/1001)与异常(1006/1011/无code);reconnectingwebsocket应配置指数退避而非固定间隔;go中须精准判错、解耦心跳与重连、及时关闭旧连接并设writedeadline。

直接说结论:别自己手写重连逻辑,优先用成熟库;真要自研,必须处理 readyState 状态跃迁、错误码分类、退避策略和心跳同步这四件事。
WebSocket.onclose 触发后为什么不能直接 new WebSocket()?
因为 onclose 回调里 ws.readyState 可能还是 CLOSING(2),此时调用 send() 会抛 "WebSocket is already CLOSED",而立即 new WebSocket() 又可能撞上服务端限流或握手失败,导致雪崩式重试。
- 必须先检查
ws.readyState === CLOSED(3)再发起下一次连接 - 若
event.code是1000(正常关闭)或1001(离开页面),通常不应自动重连 - 若
event.code是1006(异常关闭)、1011(服务器错误)或无code(网络中断),才进入重连流程 - 手动
ws.close()后,onclose也会触发——得靠this.isForcedClose这类标志位区分
reconnecting-websocket 库的重连间隔参数怎么设才不翻车?
ReconnectingWebSocket 的 reconnectInterval 默认是 1000ms,但生产环境直接用这个值等于主动制造 DDOS。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 固定间隔(如 3000)适合内网稳定环境,公网必须改用指数退避
- 推荐配置:
{ reconnectInterval: 1000, maxReconnectInterval: 30000, backoffRate: 1.5 },即从 1s 开始,每次乘 1.5,上限 30s - 注意:它不会自动发心跳,
timeoutInterval只控制连接建立超时,不是保活心跳 - 若服务端要求 ping/pong,你仍需额外起定时器调用
ws.send()发PingMessage
Go 用 gorilla/websocket 实现重连时最常漏掉的三件事
Go 的 conn.ReadMessage() 一断就报错,不像 JS 那样有事件回调,容易写出“静默失联”代码。
- 没在
ReadMessage()的error分支里用websocket.IsCloseError(err, websocket.CloseAbnormalClosure)做精准判断,而是只写if err != nil { return } - 重连逻辑写在读 goroutine 内部,形成
for { dial(); time.Sleep() }死循环,既阻塞又无法响应context.Cancel - 重连成功后没调用旧
conn.Close(),导致文件描述符泄漏,Linux 下很快 hitulimit -n - 漏设
conn.SetWriteDeadline(),WriteMessage()在连接已断时可能永久阻塞
心跳和重连必须解耦,否则重连成功也收不到消息
很多实现把心跳定时器(ping)和重连控制器绑死,结果重连成功后,旧的 time.Ticker 还在往已关闭的 conn 发 ping,或者新连接没启新的心跳 goroutine,服务端 30 秒没收到 pong 就单方面 close。
- 心跳必须绑定到当前有效连接,每次重连成功后,先
stopOldHeartbeat(),再startNewHeartbeat(conn) - 心跳发送必须带
conn.SetWriteDeadline(),避免因网络卡顿堆积多个未发出的 ping - 服务端返回的
PongMessage不需要业务处理,但客户端要确保它能被ReadMessage()捕获并丢弃,否则可能阻塞主读循环
真正难的不是“怎么连上”,而是“连上之后怎么确认它真的可用”——状态同步、订阅恢复、消息去重这些事,都在重连完成之后才刚开始。










