iterator.throw()不适用于WebSocket异步长连接错误捕获,因其属迭代器协议,而websockets基于协程;正确做法是分层捕获ConnectionClosedError、TimeoutError等异常,结合finally清理资源,并由独立协程驱动退避重连。

iterator.throw() 并不适用于 WebSocket 异步长连接的错误捕获——它属于 Python 迭代器协议(__throw__ 方法),用于向生成器内部抛入异常以中断其执行,而 WebSocket 的 websockets 库基于协程(async for / await recv()),其生命周期由事件循环管理,不暴露原生迭代器的 throw() 接口,也不应被手动调用 throw() 来处理断连。
真正需要的是:在协程上下文中,对异步迭代过程中的异常做分层识别、分类响应与状态收敛。以下是可落地的实践路径:
明确哪些异常来自 async for 迭代本身
async for message in websocket: 表面简洁,但底层实际等价于持续调用 await websocket.recv()。该调用可能抛出以下关键异常,需分别对待:
-
websockets.exceptions.ConnectionClosedOK
→ 客户端或服务端主动调用close(),属预期中止,无需重连,清理资源即可 -
websockets.exceptions.ConnectionClosedError
→ 连接非正常中断(如网络闪断、RST、代理切断),是重连主触发信号 -
asyncio.TimeoutError(配合asyncio.wait_for()使用时)
→ 心跳超时或消息接收卡住,说明链路已不可靠,应视同ConnectionClosedError处理 -
OSError/ConnectionResetError/BrokenPipeError
→ 底层 TCP 异常,多见于 NAT 超时、防火墙干预,需纳入重连范畴
⚠️ 注意:
async for默认不抛异常——客户端静默断开时,recv()可能挂起数秒甚至更久,必须配合显式超时(wait_for)或心跳机制才能及时感知。
在 try/except 中按类型分支处理,避免“一锅炖”
不要写 except Exception: 吞掉所有错误。应结构化捕获,并触发对应动作:
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
try:
async for message in websocket:
handle_message(message)
except asyncio.TimeoutError:
logger.warning("Receive timeout — triggering heartbeat failure path")
await on_heartbeat_fail()
except websockets.exceptions.ConnectionClosedOK:
logger.info("Clean close received")
await cleanup(websocket)
return # 正常退出,不重连
except websockets.exceptions.ConnectionClosedError as e:
logger.error("Unexpected disconnect: %s", e)
await schedule_reconnect(attempt=1) # 启动退避重连
except OSError as e:
if "broken pipe" in str(e).lower():
await schedule_reconnect(attempt=1, reason="broken-pipe")
else:
raise # 其他 OSError 可能需告警而非重试利用 finally + 状态标记,确保连接集合干净
异常捕获只是起点,关键在资源归还时机可控:
-
async for循环退出(无论正常 or 异常)后,finally块一定会执行 - 在
finally中移除连接、取消关联任务、关闭数据库引用,比依赖on_close更可靠
connected_clients.add(websocket)
try:
async for msg in websocket:
...
except ...:
...
finally:
connected_clients.discard(websocket) # set.remove() 安全,不存在不报错
if heartbeat_task and not heartbeat_task.done():
heartbeat_task.cancel()不要用 throw() 模拟重连逻辑——那是反模式
有人试图在 async for 循环内捕获后调用 iterator.throw(ConnectionClosedError) 想“重启迭代”,这会导致:
-
StopAsyncIteration提前终止,后续无法再进入循环 - 协程状态混乱,
websocket对象可能已失效,再次recv()抛RuntimeError: WebSocket is closed - 完全绕过事件循环调度,丧失退避、并发控制能力
✅ 正确做法:捕获异常后,create_task(reconnect_coroutine()) 启动独立协程,由状态机驱动重连节奏。
本质上,WebSocket 断线处理不是“怎么 throw”,而是“在哪 catch、怎么分、何时 clean、由谁 retry”。核心不在语法技巧,而在异常语义识别 + 资源生命周期绑定 + 异步任务解耦。

















