websockets 库强制使用 async/await,所有核心接口仅接受协程函数;handler 必须为 async def,recv/send/close 均需 await;path 是原始握手路径,不解析查询参数;断连时 recv 抛 connectionclosedok 或 connectionclosederror,须显式捕获;避免同步阻塞以保 event loop 健康。

websockets 库的 async/await 是强制的,不是可选项
用 websockets 写服务端,所有核心接口(比如 serve、connect、recv、send)都只接受协程函数。直接写同步逻辑会报 TypeError: expected a coroutine,不是语法错,是运行时拒绝执行。
常见错误:把 def handle_client(websocket, path) 写成普通函数,或者漏掉 async 关键字;又或者在 recv() 后面直接接 print() 而不加 await —— 这会导致连接卡死或立即断开。
实操建议:
- 入口必须用
async def main()+await websockets.serve(...) - 每个客户端处理函数必须是
async def handler(websocket, path) - 所有
websocket.recv()、websocket.send()、websocket.close()都要加await - 想在 handler 里调用普通函数?可以,但别让它阻塞——CPU 密集型任务用
loop.run_in_executor,IO 密集型尽量改造成异步
path 参数不是 URL 路径,而是连接时客户端传的原始路径字符串
websockets.serve(handler, host, port) 的 handler 函数签名是 async def handler(websocket, path),这里的 path 是客户端 WebSocket 握手请求中 GET /chat?v=1 的 /chat?v=1 全部内容,不是解析后的路由或 query dict。
它不自动解析查询参数,也不做路径匹配。如果你写 if path == "/api",那客户端必须连 ws://localhost:8000/api 才命中;连 ws://localhost:8000/api?token=abc,path 就是 /api?token=abc,条件就失效了。
实操建议:
- 用标准库
urllib.parse.urlparse(path)提取.path和.query - 不要依赖
path做权限校验——它可被任意伪造,认证逻辑应放在消息体或 header(如websocket.request_headers.get("Authorization")) - 如果要做多路复用(比如一个服务同时支持聊天和通知),建议统一用 JSON 消息体中的
"type"字段分发,而不是靠path
客户端断连时,recv() 抛 ConnectionClosedOK 或 ConnectionClosedError,不是 EOF
很多人以为 await websocket.recv() 断连时会返回 None 或空字符串,实际它会直接抛异常:websockets.exceptions.ConnectionClosedOK(正常关闭)或 ConnectionClosedError(异常中断)。不捕获就会导致协程崩溃、日志刷屏、连接资源不释放。
仅使用 Python 将 Claw Code 虚拟化框架与 OpenClaw 集成,提供 184 种工具和 200 多条命令,用于安全分析和代码质量...
更隐蔽的问题是:即使你写了 try/except,如果只 catch Exception,可能吞掉其他真正该暴露的错误(比如 JSON 解析失败);但如果只 catch 关闭类异常,又容易漏掉网络抖动引发的临时 TimeoutError。
实操建议:
- 必须显式 import
from websockets.exceptions import ConnectionClosedOK, ConnectionClosedError - 典型结构是:
try: ... await websocket.recv() ... except (ConnectionClosedOK, ConnectionClosedError): break - 如果需要清理资源(如从全局 clients 集合中移除),把
break换成return或finally块处理 - 不要用
while True+recv()无条件循环——务必带异常处理,否则服务端扛不住异常连接冲击
并发量上不去?检查 event loop 是否被同步代码拖垮
websockets 默认跑在 asyncio 主 loop 上,但很多开发者会在 handler 里不小心调用同步阻塞操作:比如 time.sleep(1)、requests.get()、json.loads() 处理超大字符串、甚至 os.system()。这些会让整个 event loop 卡住,所有其他连接全部停滞。
现象是:单连接正常,2 个连接开始延迟,5 个以上几乎无响应——这不是 websockets 的问题,是异步编程的基本约束没守住。
实操建议:
- 绝对禁用
time.sleep(),用await asyncio.sleep() - HTTP 请求换
aiohttp或httpx.AsyncClient,别用requests - 大 JSON 解析如果实在无法避免同步阻塞,用
loop.run_in_executor(None, json.loads, data)卸载到线程池 - 用
asyncio.create_task()启动后台任务时,注意是否持有对websocket的引用——未清理的任务可能导致连接对象无法 GC,内存缓慢增长
真正难的不是写通第一个 echo 服务,而是当 client 数量涨到几百、消息频率升到每秒千级时,还能稳定区分哪些是网络层抖动、哪些是业务逻辑阻塞、哪些是异常未捕获。这时候 path 的语义、recv() 的异常类型、event loop 的纯异步边界,就全变成关键判断依据了。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










