生产环境优先选websockets库,因其轻量、无依赖、RFC6455兼容性好、文档清晰、错误提示直接,是asyncio生态中事实标准的WebSocket服务端实现;tornado.websocket仅适用于已全栈使用Tornado且需功能共存的场景。

直接说结论:生产环境优先选 websockets,别碰 tornado.websocket 做纯 WebSocket 服务;除非你 already 在用 Tornado 做 Web 服务且需要共存。
为什么 websockets 是当前 Python 最稳妥的选择
它不是“又一个库”,而是 asyncio 生态里事实标准的服务端实现:轻量、无额外依赖、协议兼容性好、文档清晰、错误提示直接。所有资料里出现的“10 行启动服务器”案例,几乎都来自它。
- 它不绑定任何 Web 框架 —— 你可以把它嵌进 FastAPI、独立运行、甚至和 Flask 共存(只要不抢事件循环)
- 所有核心逻辑基于
async/await,和现代 Python 异步编程范式完全对齐,不会出现“Tornado 的gen.coroutine和 asyncio 混用崩溃”这类隐性坑 - 连接管理干净:每个客户端对应一个
websocket对象,生命周期明确,async for message in websocket这种写法天然防漏收、防阻塞 - 错误信息直给,比如
ConnectionClosedOK或ConnectionClosedError,不用翻半天源码猜是握手失败还是心跳超时
tornado.websocket 什么情况下还能用
它只适合一种场景:你整个后端已经是 Tornado 驱动,且 WebSocket 功能只是其中一小块(比如管理后台加个实时日志流),同时你不想引入第二个异步运行时。
- 它的
WebSocketHandler必须挂载在 Tornado 的Application路由下,无法单独启一个 WebSocket 端口 - 广播逻辑得自己维护
clients = set(),没有内置连接池或健康检查,容易在on_close漏删导致内存泄漏 - 如果你用
async def on_message,底层仍走 Tornado 自己的 IOLoop,和标准 asyncio 事件循环不互通 —— 想调用aiohttp或asyncpg就得小心适配 - 官方文档已明确标注:“For new projects, consider using the
websocketslibrary instead.”
新手最容易踩的三个硬坑
不是语法问题,而是设计习惯错位导致服务一跑就崩。
立即学习“Python免费学习笔记(深入)”;
-
漏写
await websocket.recv()或误写成websocket.recv():前者是协程,后者是同步阻塞调用,会卡死整个事件循环,表现就是“连上就断”,日志里没报错但连接秒关 -
用
for client in clients:+await client.send(...)广播:这是串行发送,100 个客户端就要等 100 次网络往返。正确做法是await asyncio.wait([client.send(...) for client in clients])并发发 -
把中文字符串当 bytes 发送:比如
await websocket.send(b"\xe4\xbd\xa0"),浏览器收到会解码失败。必须传str,库内部自动 encode;反过来,recv()返回的永远是str,不是bytes
跨域、心跳、上线通知这些事怎么处理
它们不是“高级功能”,而是连接稳定性的基础开关,不设就等于裸奔。
- 跨域:浏览器 WebSocket 不受同源策略限制,但如果你服务端校验了
Origin头(比如用了反向代理或某些中间件),就得显式放行:if origin not in ["https://myapp.com", "http://localhost:3000"]:→raise websockets.exceptions.ConnectionClosedError - 心跳:默认没心跳,客户端断网或 NAT 超时后连接会“假活”。加
ping_interval=20, ping_timeout=10到serve()参数里,服务端会自动发 ping、等 pong - 上线通知:不要在
open回调里直接send,因为此时握手刚完成,客户端可能还没准备好收消息。改用asyncio.create_task()延迟一帧再发,或者等第一个recv()成功后再响应
真正难的从来不是写通第一行 await websocket.send(),而是让 500 个连接同时在线时不丢消息、不爆内存、不互相干扰 —— 这些细节藏在 asyncio.wait 的并发控制、try/except 的异常捕获粒度、以及连接集合的线程安全操作里。


















