connectionclosed异常源于操作时序错乱而非网络故障,recv()缺失会导致连接被中间设备静默断开而触发connectionclosedok;必须启动接收循环或定时ping()保活,并配对设置ping_interval与ping_timeout以匹配中间设备超时。

ConnectionClosed 异常不是连接“出错了”,而是你正在对一个已关闭的连接调用 send() 或 recv() —— 本质是操作时序错乱,不是网络问题本身。
为什么 recv() 漏了会导致 ConnectionClosedOK
asyncio 版 websockets 不会自动维持连接活性。只要没有协程在 await websocket.recv() 或 await websocket.ping(),底层 TCP 连接就会被中间设备(Nginx、防火墙、内核 conntrack)静默掐断,几秒后你再发消息就触发 ConnectionClosedOK(状态码 1000/1001)。
- 哪怕你只发不收,也必须启动一个最小接收循环:
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
ping_interval 和 ping_timeout 必须成对显式设置
只设 ping_interval=45 不够。websockets 默认 ping_timeout=20,但网络抖动或服务端响应慢时,pong 回不来,连接就被强制关闭(错误码 1011 或 ConnectionClosedError)。
-
ping_timeout要 > 网络 RTT + 服务端处理时间,生产环境建议设为30 -
ping_interval要比中间设备空闲超时小至少 10 秒:比如 Nginxproxy_read_timeout=60,那就设45 - 两个参数都得显式传给
connect(),例如:await websockets.connect(uri, ping_interval=45, ping_timeout=30) - 旧版
websocket-client的run_forever()不适用,asyncio 下必须手动await websocket.ping()
重连不能裸 try/except ConnectionClosedError
捕获到 ConnectionClosedError 后立刻 await connect(),会导致旧连接 socket 句柄未释放、TCP TIME_WAIT 堆积、重试风暴打挂服务端。
- 重连前先确保旧连接已彻底关闭:
if websocket.open: await websocket.close() - 加退避策略:首次重试延迟 1s,之后指数退避(如 2s、4s、8s),上限建议 30s
- 限制最大重试次数(如 5 次),超限后抛出异常或通知告警,避免无限循环
- 不要在
on_close回调里直接重连——asyncio 下回调可能不在 event loop 中,要用asyncio.create_task()
ConnectionClosedOK 和 ConnectionClosedError 的区别要盯住状态码
日志里看到 sent 1000 (OK); then received 1000 是 ConnectionClosedOK,说明对方主动发起优雅关闭;而 ConnectionClosedError 通常伴随非 1000/1001 的状态码(如 1006、1011),代表异常中断。
- 1000:正常关闭,检查业务逻辑是否主动调了
close() - 1001:对端离开(如页面关闭、进程退出),客户端应清理资源
- 1006:无关闭帧,大概率是中间设备强制断连,重点查 Nginx / 防火墙 / conntrack 设置
- 1011:服务器内部错误,需结合服务端日志定位
真正难调的不是“怎么连上”,而是“怎么让连接不被静默杀死”——所有保活动作(ping、recv、timeout 设置)必须和中间设备的超时值对齐,差 1 秒都可能失效。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











