Uvicorn不自动发送WebSocket心跳包,需在应用层手动实现ping()与receive()超时控制,并配合反代配置和连接监控。

Uvicorn本身不自动发送WebSocket心跳包
Uvicorn作为ASGI服务器,只负责转发WebSocket协议帧(包括Ping/Pong),但不会主动构造或定时发送心跳。是否发、何时发、超时多久,全由你的应用逻辑控制。很多开发者误以为启动Uvicorn就自带保活,结果在Nginx反代或云厂商SLB后,连接被静默断开。
- WebSocket的Ping/Pong帧是协议层机制,操作码为
0x9/0xA,Uvicorn透传但不生成 - 真实心跳必须在
while True:接收循环里手动await websocket.ping() - 若用
websocket.receive_text()等阻塞式接收,没机会插入手动ping,连接极易被中间设备kill
FastAPI中正确实现心跳的写法
核心是把ping()和receive()放到同一个asyncio.wait_for()上下文里做超时控制,避免单边等待导致假死。
Uvicorn 0.52.1 是 Python ASGI Web 服务器,提供异步应用服务及 WebSocket 支持,常用于 FastAPI、Starlette 等应用的开发和部署。本下载为官方 PyPI 通用 wheel 包,可使用 pip 安装,并由包管理器处理所需依赖。
- 不要写
await websocket.receive_text()后才ping——收不到消息时就卡住,根本轮不到ping - 推荐用
asyncio.wait_for(websocket.receive(), timeout=20)包裹接收,并在每次成功接收后立即await websocket.ping() - 捕获
asyncio.TimeoutError和websockets.exceptions.ConnectionClosed,主动await websocket.close() - 注意:
websocket.ping()不抛异常不代表连接有效,它只是发一帧;真正验证得靠下一次receive()是否成功
生产环境必须配合连接监控与优雅关闭
光有心跳不够,Uvicorn worker崩溃、内存泄漏、文件描述符耗尽,都会让心跳失效却无提示。
- 用
psutil.Process().memory_info().rss每30秒打点,RSS突增50%以上就告警 - Gunicorn + Uvicorn组合时,
-w 4是安全起点;盲目加-w会导致每个worker独占事件循环,反而加剧上下文切换和fd竞争 - 收到SIGTERM时,Uvicorn默认会等后台任务完成,但WebSocket长连接不被视为“任务”——需在
@app.on_event("shutdown")里遍历保存的websocket实例并调用close()
反向代理场景下的常见断连陷阱
Nginx、ALB、CLB等默认60秒无数据就断连,而Uvicorn不发空Pong帧,仅靠应用层ping可能被中间设备过滤或忽略。
- Nginx配置必须显式开启WebSocket支持:
proxy_set_header Upgrade $http_upgrade;和proxy_set_header Connection "upgrade"; - 设置
proxy_read_timeout 300;(建议≥心跳间隔×2.5) - 某些云厂商SLB会丢弃纯Ping帧,此时需改用应用层心跳:服务端定期
await websocket.send_json({"type": "heartbeat"}),客户端响应确认 - 浏览器端
WebSocket对象没有暴露底层Ping能力,只能靠服务端驱动,别指望前端定时ws.ping()










