fastapi websocket连接立即断开主因是未显式调用await websocket.accept()或重复调用:前者致客户端报403或1006错误,后者抛“already accepted”异常;必须且仅在接收/发送前调用一次accept()。

WebSocket连接为什么总在建立后立即断开
FastAPI的WebSocket对象必须显式调用accept(),否则客户端会收到1006错误并断连。常见写法是把accept()写在while True循环里——这会导致第二次accept()失败,直接断开连接。
正确做法是只调用一次,在接收/发送逻辑之前:
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept() # ← 必须且仅此一次
try:
while True:
data = await websocket.receive_text()
await websocket.send_text(f"echo: {data}")
except WebSocketDisconnect:
pass
- 漏掉
await websocket.accept()→ 客户端报错403 Forbidden或直接关闭 - 在循环内重复调用
accept()→ 触发RuntimeError: Already accepted - 没包
try/except WebSocketDisconnect→ 断连时抛未捕获异常,日志刷屏
如何安全地广播消息给所有在线客户端
FastAPI本身不维护WebSocket连接池,需要自己用线程安全的数据结构(如set配合asyncio.Lock)管理活动连接。直接用全局list或dict在并发场景下会丢连接或引发RuntimeError: Set changed size during iteration。
推荐方案:用weakref.WeakSet自动清理已断开的连接,避免内存泄漏:
from weakref import WeakSet
<p>active_connections = WeakSet()</p><p>@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
active_connections.add(websocket)
try:
while True:
data = await websocket.receive_text()</p><h1>广播给其他人(不含自己)</h1><pre class="brush:php;toolbar:false;"><pre class="brush:php;toolbar:false;"> for conn in active_connections:
if conn != websocket:
await conn.send_text(data)
except WebSocketDisconnect:
pass
- 用
list或<code>dict手动管理 → 需要额外逻辑清理断连,易出错 - 没做
conn != websocket判断 → 自己发的消息又回传给自己,形成回环 - 广播时未加
try/except→ 某个连接已断但尚未被WeakSet清理,send_text()抛WebSocketDisconnect中断整个广播
为什么websocket.receive_json()总是报JSONDecodeError
客户端发来的数据未必是合法JSON字符串,比如空格、换行、BOM头或二进制帧混入。FastAPI默认不校验帧类型,receive_json()遇到非文本帧(如Binary)会直接崩溃。
稳妥做法是先用receive()拿到原始dict,再根据type字段分支处理:
msg = await websocket.receive()
if msg["type"] == "text":
try:
data = json.loads(msg["text"])
except json.JSONDecodeError:
await websocket.close(code=4000) # 自定义错误码
return
elif msg["type"] == "binary":
# 处理二进制数据,例如protobuf或图片
pass
- 盲目用
receive_json()→ 任意非JSON文本(如" "或"\n")都触发异常 - 忽略
msg["type"]→ 二进制帧传进来直接炸,无法恢复 - 没设
close(code=...)→ 异常后连接挂起,占用资源
生产环境必须关掉的调试配置
开发时用uvicorn.run(..., reload=True)很方便,但上线后必须禁用reload和debug,否则:
-
reload=True→ 文件监视器在多进程下失效,可能触发多次热重载,导致连接状态错乱 -
debug=True→ 暴露完整堆栈、路径、环境变量,WebSocket握手阶段就可能泄露敏感信息 - 没设
timeout_keep_alive→ 默认5秒,高延迟网络下频繁断连重连
上线启动命令应类似:
uvicorn main:app --host 0.0.0.0 --port 8000 \ --workers 4 \ --timeout-keep-alive 30 \ --log-level warning
WebSocket长连接对超时和日志敏感,30秒保活比默认值更稳妥;warning级别可过滤大量无意义的INFO连接日志,避免IO瓶颈。
真正难的不是写通一条连接,而是让几百个并发连接稳定跑三天不掉、不出错、不拖垮内存——这些细节没压到每个await后面,就容易在凌晨三点被报警叫醒。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











