WebSocket:一条会"升级"的全双工长连接
更新时间:2026-09-17。网络协议域·应用层。HTTP/1.1 的连接复用见 keep-alive,需要服务端推送时 WebSocket 与 SSE 是两种主要选择。
普通 HTTP 是请求-响应模式:客户端不问,服务端不能主动发。要做实时推送(聊天、行情、协同编辑、通知),早期靠轮询/长轮询,效率低。WebSocket(RFC 6455)在建立时借用 HTTP,随后"升级"成一条全双工、可双向随时发数据的持久连接。
一、握手:借 HTTP 升级
连接最初就是一个普通 HTTP/1.1 请求,带特殊升级头:
GET /ws/chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13服务端同意则返回:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=Sec-WebSocket-Accept 由 Key 加固定 GUID 后 SHA-1+Base64 算出,证明服务端真懂 WebSocket(防误连)。101 之后,底层 TCP 连接不变,但协议从 HTTP 切换为 WebSocket,之后双方随时可发帧,不再是一问一答。
- 因为始于 HTTP,能穿过大多数 80/443 端口与代理;加密版
wss://走 TLS(类比 HTTPS)。 - HTTP/2 也定义了扩展的 WebSocket(RFC 8441),可复用其多路流。
二、数据帧:轻量双向
升级后传输的是紧凑的二进制帧,而非 HTTP 头,开销极小:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload length==126/127)|
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| Masking-key, if MASK set to 1 | Payload Data ... |
+-------------------------------+-------------------------------+要点:
- opcode 区分文本帧(0x1,UTF-8)、二进制帧(0x2)、关闭(0x8)、Ping(0x9)/Pong(0xA)。
- FIN 位支持把一条消息拆成多个分片帧(fragmentation),先发片段再发结束帧。
- 客户端发给服务端的载荷必须掩码(防缓存投毒类的中间盒攻击),服务端发的不掩码。
注意:WebSocket 的"消息"可以由多个帧拼成,同样没有强制的应用层结构,自己协议的字段/边界仍需用长度/JSON 等约定(思路与 TCP 粘包拆包同源,只是这里协议已按帧区分了文本/二进制消息)。
三、心跳:Ping/Pong 与保活
NAT、代理、LB 常对"长时间无数据的连接"做空闲回收;此外一端崩溃(断电/断网)另一端未必能立刻知道(TCP 半开)。WebSocket 用应用层心跳解决:
- 任一方发 Ping 控制帧,对方应回 Pong;超时收不到则判定连接已死、触发重连。
- 心跳间隔要短于中间设备的空闲超时(常见 20–30s,移动网络可更短)。
- Ping/Pong 也可测往返延迟。应用层也可自定义心跳消息。
生产 WebSocket 客户端必须实现:心跳 + 自动重连(指数退避)+ 状态机(CONNECTING/OPEN/CLOSING/CLOSED),因为长连接随时可能被网络或代理断开。
四、和轮询、SSE、HTTP/2 的区别
| 方案 | 方向 | 模型 | 适用 |
|---|---|---|---|
| 短轮询 | 客户端反复请求 | 反复 HTTP,延迟高、浪费 | 简单、低实时 |
| 长轮询 | 服务端挂起请求到有数据 | 一个请求一条数据后重建 | 兼容好的准实时 |
| SSE(Server-Sent Events) | 服务端→客户端单向 | 一条持久 HTTP 流、自动重连、文本 | 只需推送(通知、行情) |
| WebSocket | 全双工双向 | 升级后的帧连接 | 聊天、协同、游戏、双向实时 |
| HTTP/2/3 推送/流 | — | 服务器推送已弱化;多路复用适合常规请求-响应 | 普通 Web 资源 |
经验:只需要服务端推用 SSE 更简单(自带重连、走普通 HTTP);需要客户端也频繁上行、强双向交互才用 WebSocket。gRPC 双向流等则跑在 HTTP/2 之上。
五、工程与代理注意
- 长连接占用服务端资源(每连接内存/文件描述符),海量连接要用事件驱动(epoll/异步)、关注文件描述符与连接数。
- Nginx/网关默认对 WebSocket 要显式放行
Upgrade/Connection头(proxy_set_header Upgrade/Connection $http_upgrade),否则升级失败;还要调大读超时让空闲连接不被网关掐断(配合心跳)。 wss://与 TLS:见 TLS 握手,加密能更好穿透代理且安全。- 水平扩展时连接有状态,需要在网关层做 sticky 或借助 Redis pub/sub 等把消息路由到持有连接的节点。
相关阅读
升级发生在 HTTP/1.1 连接上,见 keep-alive 与 HTTP/1.1;连接与 fd 上限见连接数 FAQ,高并发模型见 epoll。
总结
WebSocket 先用带 Upgrade: websocket 的 HTTP 请求完成 101 握手,再把同一条 TCP 连接切换成全双工长连接,用轻量二进制帧双向随时收发(文本/二进制/Ping-Pong/关闭),适合聊天、协同等双向实时场景;生产必须做 Ping/Pong 心跳、断线重连和网关 Upgrade 放行,只需要单向推送时更简单的 SSE 往往就够。