WebSocket:从 HTTP 升级而来的长连接
WebSocket 的出身决定了它的故障形态:它不是独立协议栈,而是从一次 HTTP 请求升级而来——先以普通 HTTP 的身份穿过整条链路,再在终点变身为双向长连接。这个"变身"环节和随后的"长期存活",正是它两大类故障的来源。
升级握手:一次特殊的 HTTP 请求
客户端发送带 Upgrade: websocket、Connection: Upgrade 和随机 Sec-WebSocket-Key 的 GET 请求;服务端若同意,回复 101 Switching Protocols 并附上由 Key 计算出的 Sec-WebSocket-Accept。Accept 值的校验保证了对面真的懂 WebSocket,而不是某个碰巧返回 101 的中间层。
逐跳头是这个流程的命门:Upgrade 和 Connection 属于逐跳(hop-by-hop)头,任何一层反向代理默认不会转发它们——Nginx 必须显式配置 proxy_set_header 把它们加回去,CDN 必须显式开启 WebSocket 支持。链路上每多一层,就多一处可能吞掉升级头的地方。这就是为什么升级失败(WS-001)的排查重心永远在中间层配置而不是网络质量。
升级之后:帧与关闭语义
升级完成后双方用帧(frame)通信:数据帧、ping/pong(保活探测)、close 帧。两个排查要点:
- 客户端帧必须掩码(masked),服务端帧必须不掩码——这是协议规定,实现错误会导致对端立即断开;
- close 帧携带 close code:1000 正常关闭、1001 端点离开、1002 协议错误、1008 策略违规、1011 服务端内部错误、1012 服务重启、1013 稍后再试。code 是升级后立即被关闭(WS-002)最重要的证据——服务端等于把关闭理由写在了纸条上。
没有 close 帧的断开(裸 FIN 或 RST)则说明没走正常流程:服务端崩溃或中间设备切断,对应 WS-003。
长期存活的挑战
变身完成的 WebSocket 就是一条长连接,继承长连接的全部生存压力:NAT 空闲超时、网关 read timeout(Nginx 默认 60 秒是"连上一分钟就断"的著名来源)、以及移动系统的后台回收。ping/pong 机制就是为对抗空闲超时而生——但心跳间隔必须小于链路最短超时才有效。详见长连接专题。
浏览器与 CLI 检测的差别
浏览器的 WebSocket API 出于安全考虑隐藏失败细节:连接失败时你只能知道"失败了",拿不到状态码、close code 或断开原因。ConnProof CLI 自己实现完整握手,能提供升级状态码、Accept 校验、保持时长与 close code 的全套证据——这是"浏览器检测不能替代 CLI"在 WebSocket 场景的具体体现。
一分钟自查
connproof websocket wss://目标地址/路径 --hold 5000升级证据 + 保持观察 + 关闭方式,一条命令拿齐。