核心解法是主动心跳探测而非依赖on_close:因中间设备静默断连导致on_close不触发、readystate仍为open,须通过ping_interval=20秒、ping_timeout=3秒的心跳机制配合超时重连与状态兜底来保障连接有效性。

WebSocket长连接断开不是“连不上”,而是“连着但已失效”——on_close 不触发、ws.readyState 还是 OPEN、发消息无声无息,这才是真麻烦。核心解法就一条:别等通知,主动探测。
为什么on_close经常不触发
这不是你的回调没写对,而是中间设备(Nginx、云LB、运营商NAT)静默砍掉了连接,TCP状态卡在 ESTABLISHED,服务端早把 socket 关了,但客户端完全不知情。依赖 on_close 等同于等一个永远不会来的信号。
- 真实断连信号来自底层异常:
socket.timeout、OSError、ConnectionRefusedError -
websocket-client的ping_interval=30是秒级单位,不是毫秒;设成30就是每 30 秒发一次 Ping 帧 - 默认 TCP keepalive(通常 2 小时)对 WebSocket 完全无效,必须自己实现
心跳参数怎么设才不白配
心跳间隔不是拍脑袋定的,它必须比你链路中最短的空闲超时阈值更激进。常见中间件超时值:Nginx 默认 60 秒、CDN/云 LB 多为 45–90 秒、移动网 NAT 可低至 30 秒。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 生产环境建议统一用
ping_interval=20、ping_timeout=3 - 若服务端是 Spring Boot,默认未开启原生 Ping/Pong 响应,需显式配置
enablePingPong=true - 用
wscat -c wss://your.domain/ws手动连上,输入{"type":"ping"}看是否返回{"type":"pong"},快速验证服务端行为 - 别写成
ping_interval_ms或漏掉ping_timeout,这两个参数缺一不可
重连逻辑不能只靠 try/except WebSocketConnectionClosedException
这个异常只在显式 send/fail 时抛出,而静默断连下它根本不会出现。重连必须由心跳超时驱动,并配合状态兜底。
- 每次心跳发送前,必须检查
ws.readyState === WebSocket.OPEN,否则会报WebSocket is already in CLOSING or CLOSED state - 重连前加
time.sleep(1),避免密集重试被服务端限流 - 手动关闭连接(如用户点击“断开”)要标记
isManualClose = True,防止触发自动重连 - 所有定时器(
setInterval、setTimeout)必须在ws.close()后显式clearInterval,否则内存泄漏+重复发包
移动端切后台后连接必断,怎么办
iOS/Android 系统会强制回收后台页面的网络资源,这不是 bug,是设计使然。不能靠“保活”,只能靠“快恢复”。
- 监听
document.visibilityState:值为hidden时暂停心跳定时器,但不要调ws.close()(可能已断,再调会报错) - 切回前台时,先检查
ws.readyState !== WebSocket.OPEN,立即触发重连,不复用旧实例 - 重连采用指数退避 + 随机抖动:
1s → 2s → 4s → 8s,每次乘以0.8 ~ 1.2的随机因子 - 业务消息(
onmessage)收到时,必须重置心跳超时计时器——这是最容易被忽略的优化点
真正难的不是写心跳定时器,而是让心跳、连接状态、业务消息、重连策略四者耦合起来不打架。比如收到业务消息却忘了重置心跳超时,或者重连还没完成就又启动了新心跳定时器——这类竞态问题在线上很难复现,但一旦发生就是连接雪崩。










