WebSocket频繁断开主因是未显式保活,需成对设置ping_interval与ping_timeout(如45s/30s),并确保Nginx、内核conntrack等中间设备超时值匹配,同时维持recv循环或定时ping,避免静默关闭。

频繁断开不是代码写错了,而是没告诉连接“我还活着”——WebSocket 本身不保活,空闲超时后中间设备(Nginx、防火墙、内核 conntrack)会直接掐断 TCP 连接。
ping_interval 和 ping_timeout 必须成对设置
只设 ping_interval=20 不够,websockets 库默认 ping_timeout=20,但网络延迟或服务端响应慢时,pong 回不来就会触发强制关闭(错误码 1011 或直接 ConnectionClosedError)。
-
ping_timeout必须 > 网络 RTT + 服务端处理时间,生产环境建议设为30; -
ping_interval要比中间设备的空闲超时小至少 10 秒,比如 Nginx 默认proxy_read_timeout=60,那就设45; - 两者都得显式传给
connect(),不能只靠run_forever()(那是旧版websocket-client的用法); - asyncio 版必须用
await websocket.ping()手动发,且要包在try/except里,否则一次失败就崩协程。
recv() 循环不能漏,空连接撑不过 5 秒
asyncio 下的 WebSocket 不是“连上就自动活着”,底层依赖持续有协程在 await 消息或 ping。如果业务只发不收,又没启动接收循环,连接会在几秒内被静默关闭(日志常见 code=1001)。
- 哪怕不处理消息,也得写一个最小 recv 占位:
async for _ in websocket:或while True: await websocket.recv(); - 若确定永不收消息,改用
await asyncio.sleep(1)配合定时ping(),但 sleep 时间别超过ping_interval; - 绝对不要在 recv 循环外另起线程调
send(),会破坏 event loop,引发RuntimeError: There is no current event loop或 CPU 暴涨。
重连不能裸 try/except ConnectionClosedError
捕获到异常后立刻 await connect(),会导致旧连接残留、socket 句柄泄漏、重试风暴打挂服务端。
- 先检查
websocket.closed,再调await websocket.close()并await完成; - 重连前必须
await asyncio.sleep(1),避免高频重试; - 退避策略用
min(30, 2 ** attempt),上限卡死 30 秒,否则弱网下越等越长; - 重连逻辑要用
asyncio.create_task()启动,别await它,否则阻塞主连接循环。
nginx 和内核 conntrack 是隐藏断连元凶
客户端和 Python 服务端代码全对,连接仍每 60 秒断一次?八成是 nginx 或 Linux 内核在背后动手。
- Nginx 默认
proxy_read_timeout 60,需在 location 块加:proxy_read_timeout 86400;和proxy_send_timeout 86400;; - Linux 内核对“非标准流”连接(如长 WebSocket)常缩短
nf_conntrack_tcp_timeout_established到 300–600 秒,查当前值:sysctl net.netfilter.nf_conntrack_tcp_timeout_established,临时调高:sudo sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=7200; - 所有代理层(CDN、云 WAF)都要确认是否支持 WebSocket 长连接,有些会静默丢弃 ping 帧。
真正难搞的从来不是代码逻辑,而是你根本看不到的中间链路——ping/pong 能跑通不代表连接能活过 60 秒,得一层层确认从 Python 协程、到系统 socket、再到反向代理、最后到云厂商的超时配置,全都对齐了才行。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











