
聊天窗口能打开,消息也能发出去,但空闲一段时间就显示“连接已断开”。如果断线常常接近 60 秒,值得先检查代理读取超时和服务端心跳。Nginx 官方文档说明,代理服务器默认在上游连续 60 秒没有传来数据时关闭连接;这只是某一层的默认行为,不能据此认定所有 CDN 都有同样限制。[1]
排查时,把“握手没成功”和“建立连接后断线”分开记录。本文以 HTTP/1.1 Upgrade 路径为例,说明怎样定位连接在哪一层被终止,以及怎样验证超时与心跳配置。涉及 HTTP/2 或 HTTP/3 WebSocket 扩展时,需要另外核对客户端、边缘与回源协议的支持情况。
先记录握手结果和断线时刻
在浏览器开发者工具的 Network 面板找到 WebSocket 请求,记录请求地址、握手响应、建立时间、最后一条消息时间和断线时间。按本文的 HTTP/1.1 路径,握手成功应返回 101 Switching Protocols;返回 301、403、404 或 502 时,应先查重定向、权限、路径与回源错误,不能直接归因于连接空闲超时。[2]
如果握手已经成功,继续比较两段时长:从建立连接到断线的时间,以及从最后一条服务端消息到断线的时间。第二段时长反复接近固定值,更符合空闲计时器触发的现象;有持续流量仍在同一连接年龄断开,则需要核对最大连接时长、服务重启和网络变化。这个比较只能缩小范围,最终还要结合各层日志和配置。
浏览器上报 1006 表示异常关闭,通常意味着没有收到正常的 Close 帧。它不能指出是哪台代理关闭了连接,也不是可以让服务端发送的关闭码。记录应用实例、会话标识、边缘请求标识和断线时间,比只截一张 1006 报错图更有助于关联日志。[2]
用同一条测试连接比较 CDN 与源站
以下命令用于你有权限的测试环境,域名、路径与源站地址都是示例。需要 Cookie、令牌或子协议的接口,要按实际约定补齐;不要把生产凭证粘贴到公开记录中。
在 Bash 中执行一次 HTTP/1.1 握手探测:
URL='https://realtime.example.com/ws/' curl --http1.1 --connect-timeout 10 --max-time 8 -sS -i -N \ -H 'Connection: Upgrade' \ -H 'Upgrade: websocket' \ -H 'Sec-WebSocket-Version: 13' \ -H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' \ -H 'Origin: https://app.example.com' \ "$URL"
这里使用 HTTPS URL 来观察升级响应。示例 Key 来自协议文档,只用于一次性诊断;生产客户端应按协议生成随机 Key。--max-time 8 会主动结束探测,握手成功后出现超时退出也可能只是达到了人为设置的八秒上限。这个命令没有实现完整的 WebSocket 帧收发和心跳,不能用来证明长连接正常。[2][4]
若源站明确允许从测试机直连,可在同一命令中增加下面两个选项,把连接发往示例源站 IP,同时保留域名对应的 Host 与 TLS 校验:
--noproxy '*' --resolve realtime.example.com:443:203.0.113.10
203.0.113.10 是文档示例地址,需要替换为授权测试目标。源站要求专用回源证书、客户端证书或仅允许 CDN IP 时,直连失败没有比较价值;应使用允许的测试入口或源站日志,不要关闭证书校验、放开防火墙来凑出结果。[4]
连接时长比较应使用浏览器或支持 WebSocket 的客户端,保持相同路径、账号与消息模式:分别观察连接经 CDN、经源站测试入口时,在完全空闲和有服务端回复的情况下何时断开。对齐 CDN、反向代理与应用日志。某一层没有日志也可能是日志延迟、过滤或记录时机造成,不能单凭缺失日志认定故障发生在它之前。
检查实际命中的 Nginx 代理配置
HTTP/1.1 的 Upgrade 和 Connection 是逐跳头,代理需要显式向上游传递升级信息。核对实际请求命中的 server、location 和 proxy_pass,同时检查应用是否真的使用该路径;改错配置块不会影响现有连接。[1]
下面是 Linux 上已有 HTTPS 站点的配置片段示例,假设应用监听本机 127.0.0.1:3000,接受 /ws/ 路径。map 放在 http 上下文,location 放入现有站点的 server,不能把两个片段直接拼成完整配置。
# 放在 http 上下文
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}# 放在现有站点的 server 中
location /ws/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
# 120 秒只是示例,要结合实际超时与心跳设计
proxy_read_timeout 120s;
}示例显式使用 HTTP/1.1,便于兼容不同 Nginx 版本。proxy_pass 没有追加 URI,意图是保留请求路径;如果应用实际监听其他路径,应按现有路由调整并验证。proxy_read_timeout 针对读取上游数据时相邻两次读取之间的间隔,不是连接的总寿命。客户端不停发送消息,而服务端始终没有向代理返回数据,仍可能触发这类读取超时。[1][5]
改配置之前备份站点文件,并记录当前生效值。确认连接数、文件描述符、应用负载和连接保持成本后,只调整需要的站点范围。使用 systemd 管理 Nginx 的机器可先检查语法,再重载:
sudo nginx -t && sudo systemctl reload nginx
容器或其他进程管理方式要使用对应流程。修改后用新连接验证,并观察既有连接与旧工作进程的退出情况;如出现资源异常,恢复备份,重新检查语法,再按原流程重载。不要为避免断线直接改成很大的全局超时。
心跳要让服务端确实返回数据
WebSocket 协议定义了 Ping 与 Pong 控制帧。端点收到 Ping 后,除已经收到 Close 帧的情况外,需要返回 Pong;服务端可以周期性发送 Ping 检查连接。浏览器的 JavaScript WebSocket API 没有直接暴露协议 Ping/Pong 接口,因此不能把 socket.send('ping') 当成协议 Ping 帧。[2][3]
对于浏览器应用,可以按业务协议约定普通消息心跳,例如客户端发送带序号的 JSON 消息,服务端返回对应确认。这需要前后端同时实现。单方面发送字符串、后端忽略消息,会让界面看起来“正在心跳”,却没有改变上游读取空闲的事实。
心跳间隔应短于需要维持的各层空闲阈值,并留出网络抖动余量。例如,若测试确认某层在约 60 秒无数据后关闭连接,可以把 25 秒作为试验间隔,再观察后台标签页、移动网络和负载下的实际发送与回复时间;它不是对所有 CDN 都成立的推荐值。浏览器后台计时器可能被调度限制,不应把单个前台标签页的结果当成生产保证。
还要定义连续未收到回复时如何结束旧连接、如何重连,以及重连后如何恢复订阅。若服务端已有协议 Ping/Pong,可优先评估该机制,避免重复发送无意义的业务心跳。鉴权、Origin 校验、连接配额与速率限制仍应保留,不能为了连得久而全部关闭。[2]
CDN 上限与断线恢复要分别验证
核对实际 CDN 产品与套餐的 WebSocket 支持、回源要求、空闲超时和最大连接时长;有些限制不能仅靠源站参数改变。本文没有验证 CloudFlew 某一套餐的具体限额,不能把 Nginx 的默认值套成平台承诺。
修复验证至少保留四组记录:成功握手、超过原断线窗口的空闲连接、有心跳回复的连接,以及网络中断后的恢复。连接保持试验应覆盖多个原断线周期;服务发布、代理重载和网络切换则单独测试,记录真实的断线时刻与恢复耗时。本文命令和参数是诊断示例,没有在读者的生产环境执行。
重连需要退避和抖动,避免所有客户端同时重新建立连接;消息协议还要明确序号、确认、重放与去重方式。连接重新建立只说明传输恢复,不能保证断线期间的消息已经补齐。验收时检查重复订阅、重复消息、旧连接残留和身份过期后的重新鉴权。
如果连接在没有服务端数据时反复断开,而服务器返回心跳后能稳定越过原时间窗口,再结合代理日志确认空闲计时器,才有依据把问题归到读取超时。持续收发仍断开的连接,应继续查最大时长、发布重启或网络层中断,保留正常的连接清理和恢复机制。
官方参考资料
资料核验日期:2026 年 10 月 12 日。
[1] Nginx:WebSocket proxying,升级头与默认空闲行为。https://nginx.org/en/docs/http/websocket.html
[2] RFC 6455:WebSocket 协议,握手、Ping/Pong 与关闭码,重点参看 4、5.5、7.4 节。https://www.rfc-editor.org/rfc/rfc6455.html
[3] WHATWG WebSockets Standard:浏览器 WebSocket API 与 Ping/Pong 可见性。https://websockets.spec.whatwg.org/
[4] curl 官方手册:HTTP/1.1 请求、请求头、连接超时和域名解析覆盖选项。https://curl.se/docs/manpage.html
[5] Nginx ngx_http_proxy_module:proxy_read_timeout 与 proxy_pass。https://nginx.org/en/docs/http/ngx_http_proxy_module.html