websocket掉线主因是网络中间设备清理空闲连接,需心跳机制与nginx等代理配置协同:proxy_read_timeout须大于心跳超时阈值,且必须配置http/1.1升级头;前后端需双向响应ping/pong并设超时兜底,服务端还需主动跟踪连接活跃状态。

WebSocket长连接掉线不是代码写错了,而是网络中间设备(Nginx、防火墙、运营商NAT)在“默默清理空闲连接”。只靠 new WebSocket() 连上就不管,生产环境基本活不过 60 秒。保活必须靠心跳 + 配置协同,缺一不可。
为什么 Nginx 会主动断开 WebSocket 连接
Nginx 默认 proxy_read_timeout 是 60 秒:只要后端 60 秒内没往客户端发数据,它就认为连接“死了”,直接 FIN 断开。这不是 Bug,是它的默认资源管理策略。
- 必须显式配置
proxy_read_timeout 86400(24 小时)或至少大于你的心跳间隔 -
proxy_http_version 1.1和proxy_set_header Upgrade $http_upgrade缺一不可,否则压根走不了 WebSocket 升级流程 - 别信“我本地没断”,本地绕过了 Nginx;上线前务必确认代理层配置已生效
前端心跳必须带超时判定,不能只发不等
只用 setInterval(() => ws.send('PING'), 30000) 是典型半残实现——发出去就不管,连断没断都不知道。真正可用的心跳要双向响应+超时兜底。
PigX UI Pro 前端开发指南 - Vue 3 + TypeScript + Element Plus。当用户提到 PigX UI、PigX 前端、lgb-mgui 项目、Vue 3 企业级后台开发、Element Plus 后台开发时使用此技能。
- 发送
PING后立即启动一个setTimeout,比如 45 秒未收到PONG就ws.close() - 每次收到
PONG必须clearTimeout当前定时器,否则多个定时器叠加触发重连 -
ws.readyState !== WebSocket.OPEN时禁止发送,避免InvalidStateError - 心跳内容建议用纯字符串
"PING"或轻量 JSON{"type":"ping"},别塞时间戳或随机数——增加无谓解析负担
FastAPI 后端必须响应心跳且管理连接存活状态
前端发了 PING,后端光打印日志不回 PONG,等于没做心跳。更关键的是:服务端得自己记着“这个连接上次活跃是什么时候”,超时就主动关。
- 在
WebSocketEndpoint的on_connect里存一个last_heartbeat = time.time() - 收到消息时,先判断是否为
"PING",是则立刻await websocket.send_text("PONG"),并更新last_heartbeat - 另起一个后台任务(如
asyncio.create_task(keep_alive_check())),每 10 秒检查所有连接的last_heartbeat,超时(比如 > 65 秒)就await websocket.close() - 别依赖
on_disconnect清理——异常断连时这个钩子根本不会触发
心跳间隔与超时阈值怎么设才不翻车
设 30 秒发一次心跳,但服务端 proxy_read_timeout 是 60 秒?那刚好卡在边缘,网络抖动一下就断。必须留出安全余量。
- 公式:
heartbeat_interval ,例如 Nginx 设了 90 秒,心跳就别超过 30 秒 - 前端超时判定阈值(
setTimeout时间)应略大于心跳间隔,比如间隔 30 秒,超时设 45 秒——给网络传输留缓冲 - 移动端尤其注意:切后台时浏览器可能节流
setInterval,建议用document.visibilityState监听页面激活状态,切回来立刻补发一次心跳
最容易被忽略的一点:心跳不是单点动作,它是前后端 + 代理层的三方契约。少配一个 proxy_read_timeout,或者前端忘了 clearTimeout,或者后端没做连接活跃度轮询,整套机制就形同虚设。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










