connectionclosed本质是操作时序错乱,因中间设备静默断开空闲连接且客户端未保活;须用recv循环、ping机制、合理超时配置、安全重连及重订阅来保障连接持续可信。

ConnectionClosed 不是网络故障,是你在对一个已关闭的连接调用 send() 或 recv() —— 本质是操作时序错乱。真正原因往往藏在中间设备(Nginx、防火墙、内核 conntrack)静默掐断空闲连接,而你的代码没告诉它“我还活着”。
必须启动 recv 循环或定时 ping()
asyncio 版 websockets 不会自动保活。哪怕你只发不收,也得维持一个协程持续监听,否则连接几秒内就会被中间设备关掉(日志常见 code=1001)。
- 推荐写法:
async for message in websocket:,哪怕只pass - 若真不处理消息,改用
while True: await websocket.recv()+except websockets.ConnectionClosedOK:跳出 - 若连
recv()都不想调,就用await asyncio.sleep(1)配合手动await websocket.ping(),但sleep时间必须 ping_interval - 绝对不要在
recv循环外另起协程或线程调send(),会触发RuntimeError: There is no current event loop
ping_interval 和 ping_timeout 必须成对显式设置
只设 ping_interval=45 是无效的。默认 ping_timeout=20,一旦网络抖动或服务端响应慢,pong 回不来,连接就被强制关闭(ConnectionClosedError,状态码 1011)。
-
ping_timeout应 > 实际 RTT + 服务端处理时间,生产环境建议30 -
ping_interval必须比中间设备空闲超时小至少 10 秒:比如 Nginx 的proxy_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 后立刻 await connect(),会导致 socket 句柄泄漏、TIME_WAIT 堆积、重试风暴打挂服务端。
- 重连前先检查:
if not websocket.closed: await websocket.close(),并await完成 - 首次重试延迟
1秒,之后用min(30, 2 ** attempt)指数退避 - 最大重试次数建议 ≤ 5,超限后应抛异常或告警,而非无限循环
- 重连逻辑要用
asyncio.create_task()启动,别await它,否则阻塞主循环 - 别在
on_close回调里直接重连——回调可能不在 event loop 中
订阅状态丢失是静默断连最危险的坑
连接重建成功 ≠ 行情恢复。很多量化脚本重连后仍收不到数据,是因为没重发订阅指令。服务端不会主动推送未订阅的标的,进程看起来正常,实则数据流已中断。
- 必须在连接建立后,读取本地缓存的完整订阅列表(如
["AAPL", "TSLA"]),逐条重新send()订阅请求 - 订阅参数要持久化(如写入 JSON 文件或内存 dict),不能只存在连接对象生命周期内
- 多标的场景下,遗漏任意一项都会导致对应行情缺失,实盘与回测结果无法对齐
- 建议在重连完成、心跳稳定后,再批量提交订阅,避免因顺序或并发引发服务端拒绝
真正难的不是连上,而是让连接“被系统信任地活着”——这要求你同时协调客户端代码、中间设备配置、服务端策略和业务状态管理。漏掉任意一环,都可能在凌晨三点安静地丢掉一笔行情。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











