websocket长连接稳定需网络层、协议层、应用层协同:nginx必须配置upgrade头与proxy_read_timeout≥86400,tcp开启keepalive;前后端双向心跳(ping/pong+自定义消息),间隔20–30秒并设超时兜底;服务端主动维护活跃时间戳,客户端指数退避重连;握手鉴权、帧限流、缓冲复用等安全加固缺一不可。

WebSocket 长连接既要“连得久”,也要“守得住”。单纯保活不等于安全,只做防护不处理断开也不够稳健。真正落地时,必须把网络层、协议层、应用层三者的策略串起来——心跳检测、代理配置、异常感知、自动恢复缺一不可。
一、从 Nginx 到 TCP 层:堵住中间件主动断连
大多数“无声断开”其实发生在反向代理或云负载均衡器上,而非你的代码。Nginx 默认 60 秒无数据就切断连接,AWS ALB 同样默认 60 秒空闲超时且不可调。不改这一层,后面所有心跳都白搭。
- 在
location /ws/块中必须启用协议升级头:proxy_set_header Upgrade $http_upgrade;和proxy_set_header Connection "upgrade"; - 关键超时参数要显式拉长:
proxy_read_timeout 86400;(24 小时),proxy_send_timeout 86400; - 开启 TCP 层保活:
proxy_socket_keepalive on;,并设tcp_keepalive_time 300;(5 分钟探测一次) - 若用 Kestrel(ASP.NET Core),同步设置
KeepAliveInterval = TimeSpan.FromSeconds(30),与 Nginx 探测节奏对齐
二、心跳机制:用 Ping/Pong + 自定义心跳双保险
WebSocket 协议自带 Ping/Pong 控制帧,但很多前端库(如浏览器原生 WebSocket)不暴露 Pong 监听接口;服务端框架(如 Spring Boot)可能默认关闭 Ping 响应。所以推荐“协议级 + 应用级”双心跳。
- 服务端启用自动 Ping:Spring Boot 中设
session.setMaxIdleTimeout(30_000),触发底层自动发 Ping - 客户端额外发送自定义心跳消息(如
{"type":"heartbeat"}),并监听响应,超时未回即判定失效 - 心跳间隔建议 20–30 秒,Pong 或响应超时设为 5–10 秒,避免误判也防止积压
- 注意:不要仅依赖单边心跳。服务端发 Ping,客户端必须回 Pong;客户端发心跳,服务端必须回 ack —— 双向确认才可靠
三、异常感知与状态管理:拒绝“假在线”
连接断开未必触发 onclose,尤其 NAT 超时或静默丢包时,客户端可能还在发消息,服务端却已收不到。必须主动验证连接活性。
- 服务端维护连接池时,给每个 Session 绑定最后活跃时间戳,定时扫描超时连接并主动 close
- 客户端在每次发业务消息前,先检查心跳是否最近收到响应;若连续两次心跳失败,直接触发重连,不等真实断开
- 避免“重连风暴”:前端重连延迟应指数退避,如 1s → 2s → 4s → 最大 30s,加随机抖动
- 服务端记录
OnOpen/OnError/OnClose全生命周期日志,字段含 IP、User-Agent、断开码(如 1006 表示异常终止)
四、安全加固:防伪装、防注入、防耗尽
长连接是攻击面放大器。一次认证不能管全程,帧解析不能信输入,连接数不能无上限。
- 握手阶段严格校验 Origin、Token 或 JWT,并在
@OnOpen中完成权限绑定;后续每帧不重复鉴权,但需绑定 Session 上下文防越权 - 限制单连接消息频率(如每秒 ≤5 条文本帧)、单帧大小(如 ≤64KB),超限直接 close 并记日志
- 服务端接收缓冲区要预分配且复用,避免高频小帧触发 GC 或内存碎片;Go/Python 等语言需加写锁防并发 panic
- 生产环境禁用
AllowedOrigins: ["*"],静态配置白名单域名;WAF 层可识别 WebSocket 流量,对异常帧模式(如超长 payload、非法 opcode)实时拦截











