因为websockets.serve()返回协程对象,必须在asyncio事件循环中运行,不能直接传给threading.Thread,否则报RuntimeError: no running event loop。

为什么 websockets 的 serve() 不能直接用多线程启动
因为 websockets.serve() 返回的是一个协程对象,不是普通函数;直接丢给 threading.Thread 会报 RuntimeError: no running event loop。它必须在 asyncio 事件循环中运行,且通常应由 asyncio.run() 或 loop.run_until_complete() 驱动。
常见错误写法:
import threading import websockets <h1>❌ 错误:协程没被 await,也没进 event loop</h1><p>def start_server(): websockets.serve(echo, "localhost", 8765)</p><p>threading.Thread(target=start_server).start() </p>
- 正确做法是让整个服务生命周期由 asyncio 管理,不手动开线程
- 若需与同步代码(如 Flask)共存,应使用
asyncio.create_task()或asyncio.ensure_future()把 WebSocket 服务注册进已有 loop - 生产环境推荐用
uvloop替换默认 loop,吞吐量可提升 2–3 倍
如何处理多个客户端并发连接而不阻塞
websockets 默认就是异步的,每个连接由独立的 websocket 实例处理,但业务逻辑里容易写出同步阻塞操作——比如调用 time.sleep()、requests.get()、或未加 await 的数据库查询。
典型陷阱示例:
import requests
<p>async def handle_client(websocket):</p><h1>❌ requests 是同步库,会阻塞整个 event loop</h1><pre class="brush:php;toolbar:false;">resp = requests.get("https://httpbin.org/delay/2")
await websocket.send(resp.text)
图片提示词生成器?不止如此。 马甲系统 —— 把脑海中的画面,翻译成AI能理解的专业表达。 用得越多,它越懂你:首次需要多问几句确认方向,用久了几乎一说就懂。 用得越多,它越快:缓存机制让后续对话越来越省。 RAG进化:成功案例持续入库,越跑越聪明。 输入「新手指南」查看完整功能介绍
- 改用异步 HTTP 客户端,如
aiohttp.ClientSession或httpx.AsyncClient - 耗时 I/O 操作(文件读写、DB 查询)务必确认所用驱动支持 async/await,例如
aiosqlite、asyncpg - CPU 密集型任务(如解析大 JSON、图像处理)必须用
loop.run_in_executor()脱离 event loop 执行
websockets.exceptions.ConnectionClosed 怎么安全捕获
客户端主动断开、网络中断、心跳超时都会触发该异常,但它不是单一类型,而是基类,实际抛出的是 ConnectionClosedOK 或 ConnectionClosedError。直接 except ConnectionClosed 可以,但区分状态更利于日志和清理。
推荐写法:
from websockets.exceptions import ConnectionClosed, ConnectionClosedOK, ConnectionClosedError
<p>async def echo(websocket):
try:
async for message in websocket:
await websocket.send(f"echo: {message}")
except ConnectionClosedOK:
print("client closed cleanly")
except ConnectionClosedError:
print("client disconnected unexpectedly")
except ConnectionClosed:
print("generic close — handle cleanup here")
finally:</p><h1>这里做资源释放,比如从全局 clients set 中移除 websocket</h1><pre class="brush:php;toolbar:false;"> if websocket in clients:
clients.remove(websocket)
- 不要忽略
finally块,否则连接泄漏会导致内存持续增长 - 避免在 except 块里再 await 或调用可能抛异常的异步方法,防止二次崩溃
- 如果用了
websockets.broadcast()推送消息,注意目标列表要实时过滤掉已关闭连接
如何让 WebSocket 服务支持 HTTPS 和反向代理(Nginx)
websockets 本身不内置 TLS,需靠 ssl.SSLContext 包装;而 Nginx 反向代理时,若未正确透传升级头和协议,会返回 426 Upgrade Required 或直接断连。
- 启动服务时传入
ssl_context参数,证书路径必须为绝对路径,相对路径在 daemon 模式下极易失效 - Nginx 配置关键项:必须设置
proxy_http_version 1.1、proxy_set_header Upgrade $http_upgrade、proxy_set_header Connection "upgrade" - 若用 Let’s Encrypt,注意证书自动续期后要 reload websockets 服务(或监听文件变化后热重载 ssl context)
- 开发阶段可用
mkcert生成本地可信证书,避免浏览器拦截 wss 连接
WebSocket 的并发能力不取决于“开了多少协程”,而在于你有没有把所有 I/O 都真正异步化。最容易被忽略的是第三方库的同步调用,以及忘记清理断连后的引用——这两点会让服务跑着跑着就变慢、OOM、或无法新建连接。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










