websockets.connect()无法自动重连因其返回一次性协程,断开即终止;需手动捕获connectionclosed等异常,结合指数退避循环重试,并用asyncio.queue缓存待发消息、独立sender协程发送,同时配置ping_interval/ping_timeout或自定义心跳保活。

为什么直接用 websockets 的 connect() 无法自动重连
因为 websockets.connect() 返回的是一个一次性协程,连接断开后它就结束了,不会自己重启。你调用一次,它只尝试连一次;失败或断开后,协程退出,后续逻辑不会自动触发重连——除非你手动捕获异常、加循环、重调度。
常见错误现象:ConnectionClosedError 或 OSError: [Errno 61] Connection refused 后程序静默退出,消息收发中断。
- 必须显式用
try/except捕获ConnectionClosed、ConnectionRefusedError、TimeoutError等 - 重连前建议加退避延迟(如指数退避),避免高频重试被服务端限流
- 不要在重连循环里无条件
await connect(...),否则首次失败就会卡住整个协程
用 asyncio.create_task() + 循环封装重连逻辑
核心思路是把 WebSocket 连接和收发封装成一个可重启的协程任务,由外层循环控制生命周期。不依赖第三方库,仅用标准 websockets 和 asyncio。
示例关键结构:
import asyncio
import websockets
import random
<p>async def connect_with_reconnect(uri, max_retries=5):
for attempt in range(max_retries + 1):
try:
async with websockets.connect(uri) as ws:
print(f"Connected (attempt {attempt + 1})")
await handle_messages(ws) # 自定义收发逻辑
break # 正常关闭则退出循环
except (websockets.ConnectionClosed, ConnectionRefusedError, OSError, asyncio.TimeoutError) as e:
if attempt == max_retries:
raise e
delay = min(2 ** attempt + random.uniform(0, 1), 30)
print(f"Reconnecting in {delay:.2f}s...")
await asyncio.sleep(delay)
</p>
-
handle_messages(ws)必须是纯协程,内部用await ws.recv()和await ws.send(...) - 指数退避上限设为 30 秒,防止无限拉长等待;加随机抖动避免多客户端同步重试
- 若需支持连接后自动发认证消息,把它放在
async with块开头,而非重连循环外
如何安全地在重连期间暂存待发送消息
如果业务要求“不丢消息”,不能让 send() 在连接断开时直接抛异常然后丢弃数据。得把待发消息缓存在内存队列中,等重连成功后再批量 flush。
推荐用 asyncio.Queue,配合一个独立的 sender 协程:
send_queue = asyncio.Queue() <p>async def sender(ws): while True: try: msg = await send_queue.get() await ws.send(msg) send_queue.task_done() except websockets.ConnectionClosed: break</p><p>async def handle_messages(ws): asyncio.create_task(sender(ws)) # 启动 sender 协程 async for msg in ws:</p><h1>处理接收消息</h1><pre class="brush:php;toolbar:false;"><code> ...</code>
- 发送方调用
await send_queue.put(json.dumps({...})),不关心当前是否连通 -
sender协程只在连接有效时发,断开即退出;重连后重新启动它 - 注意:
asyncio.Queue是内存队列,进程崩溃会丢数据;生产环境需考虑持久化方案
连接状态管理和心跳保活容易被忽略的点
很多服务端会在空闲 30–60 秒后主动断开 WebSocket 连接。单纯靠重连不够,得主动发 ping。
-
websockets默认启用了 ping/pong,但超时参数需显式设置:ping_interval=20, ping_timeout=10 - 不要依赖
ws.closed判断连接状态——它可能为False但实际已断;应以recv()抛异常为准 - 若服务端要求自定义心跳包(如发
{"type":"ping"}),需在handle_messages中起一个定时 task 主动发送,并监听对应pong响应 - 重连后,记得重置心跳计时器或重新注册心跳逻辑,否则可能漏发
重连不是“换个 socket 重来”那么简单,状态清理、消息缓冲、心跳续订这三块没对齐,就会出现假连接、消息堆积、服务端单边断连却无感知等问题。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











