websocket断连时on_close未必触发,需主动心跳探测:设ping_interval=30秒、ping_timeout=5秒,捕获socket.timeout等异常并指数退避重连。

WebSocket 连接突然断开,on_close 没触发?
不是所有断连都会走 on_close —— 网络闪断、NAT 超时、代理静默丢包时,客户端可能收不到 FIN 包,连接就卡在 ESTABLISHED 状态,但服务端早已关闭。这时候靠监听 on_close 做重连,会一直等下去。
真正可靠的做法是:自己发心跳 + 主动探测连接状态。
- 用
ping_interval(如websocket-client库)或手动定时调用send_ping(),强制触发底层 TCP keepalive 或应用层探测 - 配合
ping_timeout,超时未收到 pong 就判定为断连,立刻 close 并触发重连逻辑 - 别依赖操作系统默认的 TCP keepalive(通常 2 小时才触发),它对 WebSocket 场景完全没用
用 websocket-client 实现带心跳的自动重连
这个库本身不自动重连,也不默认开心跳;必须显式配置,且参数名容易写错。
-
ping_interval=30表示每 30 秒发一次 ping,单位是秒(不是毫秒) -
ping_timeout=5表示发完 ping 后 5 秒内没收到 pong 就报WebSocketConnectionClosedException - 重连不能只靠 try/except
WebSocketConnectionClosedException—— 要捕获socket.timeout、OSError和ConnectionRefusedError,这些才是网络层真实失败信号 - 重连前加
time.sleep(1),避免密集重连被服务端限流;指数退避可选,但至少别立即重试
ws = websocket.WebSocketApp(
"wss://example.com/ws",
on_open=lambda ws: print("connected"),
on_message=lambda ws, msg: handle_msg(msg),
on_error=lambda ws, err: print(f"error: {err}"),
on_close=lambda ws, code, reason: print("closed")
)
ws.run_forever(ping_interval=30, ping_timeout=5)
服务端没回 pong?检查 websocket 库版本和配置
Python 服务端用 websocket(非 websockets)时,老版本(
立即学习“Python免费学习笔记(深入)”;
-
websocket库 10.0+ 默认开启enable_ping_pong=True,但若手动传了enable_ping_pong=False,客户端 ping 就永远得不到 pong -
websockets库则相反:默认响应 ping,但如果你覆盖了process_request或用了自定义 protocol,可能意外禁用了 ping handler - 用
wscat -c wss://example.com/ws手动连上去,输入{"type":"ping"}看是否返回{"type":"pong"},快速验证服务端行为
心跳间隔设成 30 秒,为什么还是频繁断连?
30 秒看着合理,但在公网环境下常被中间设备(如运营商 NAT、企业防火墙、CDN)主动 kill 掉空闲连接 —— 它们超时阈值往往在 60–120 秒之间,但有些廉价云 LB 只有 45 秒。
- 保守起见,心跳间隔建议 ≤ 30 秒,且
ping_timeout≤ 10 秒,留出网络抖动余量 - 如果服务端是 Nginx 代理 WebSocket,必须配
proxy_read_timeout 60,否则 Nginx 在 60 秒无数据时直接断连,客户端还蒙在鼓里 - 移动网络下更敏感:iOS 后台 App 的 WebSocket 会被系统休眠,此时仅靠心跳不够,得结合
onclose后延迟重连 + 前台唤醒逻辑
ping_interval 就完事;它和网络路径上的每一层设备、每一行代理配置、每一次异常中断都有关联。最容易忽略的是:你看到的“连接正常”,可能只是 TCP 层没断,而应用层早已失联。










