本页目录
WebSocket 与 SSE
HTTP 的请求-响应模型不适合实时场景——WebSocket 升级为全双工通道,SSE 用单向流保持简洁。选哪个取决于"服务器是否需要主动推送"和"是否需要二进制帧"。
概述
WebSocket(RFC 6455, 2011)在 HTTP 握手基础上创建持久的双向 TCP 通道——浏览器和服务器可以随时互发消息。这解决了 HTTP 的"请求-响应"单向限制,使实时应用(聊天、游戏、直播弹幕、Grafana Live)成为可能。SSE(Server-Sent Events)是更简单的单向替代:服务器向客户端推送事件流,基于纯 HTTP,防火墙兼容性更好。两者都有心跳机制(ping/pong vs SSE 的自动重连),保持长连接在 NAT/proxy 环境中不掉线。
WebSocket Upgrade
Client → Server: GET /ws HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== ← random, base64
Sec-WebSocket-Version: 13
Sec-WebSocket-Protocol: chat ← optional subprotocol
Server → Client: HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= ← SHA1(Key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
Sec-WebSocket-Protocol: chat
→ 握手完成 → TCP 连接升级为双向 WebSocket
WebSocket Frame
0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Masking-key (if MASK set, 4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Payload Data (可变, masked if from client) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
FIN(1): 1 = 这是消息的最后一帧
opcode(4): 0=continuation, 1=text(UTF-8), 2=binary, 8=close, 9=ping, 10=pong
MASK(1): client→server 必须 mask, server→client 必须不 mask
Masking (client → server):
1. Client: 随机生成 4-byte masking key
2. 对 payload 逐 byte: payload[i] = original[i] XOR key[i % 4]
3. 目的: 防止中间代理缓存投毒 (攻击者让 browser 发 crafting WS payload
→ proxy 误解析为 HTTP response → corrupt cache)
Close frame (opcode=8):
[status code (2B, big-endian)][reason (UTF-8, optional)]
1000: normal closure
1001: going away (server shutdown, page navigated away)
Ping/Pong (Heartbeat)
Ping frame (opcode=9): 可选 payload (application-specific)
Pong frame (opcode=10): 必须复制 ping 的 payload
→ 应用层心跳 (不同于 TCP keepalive)
→ 检测: dead connection (即使 TCP 还 alive 但 remote app hung)
Proxy/server timeout: nginx proxy_read_timeout 适用于 WebSocket →
如果没有 ping/pong, 连接可能被 proxy 超时断开
SSE (Server-Sent Events)
单向 (server → client) 流, 基于 HTTP chunked encoding:
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
id: 42 ← last event ID (auto-reconnect since this ID)
event: alert ← optional event type (otherwise "message")
data: {"status":"firing"} ← event data
data: multi-line data
data: second line
← 空行 → dispatch event
Client API: EventSource (built-in HTML5):
const es = new EventSource("/events");
es.onmessage = (e) => { console.log(e.data); };
es.addEventListener("alert", (e) => { ... });
WebSocket vs SSE
| WebSocket | SSE | |
|---|---|---|
| 方向 | 双向 | Server → Client |
| 协议 | WebSocket (upgrade) | HTTP chunked |
| Binary data | 天然 (opcode=2) | 需 base64 encode |
| 防火墙兼容 | 差 (某些 proxy 不支持 upgrade) | 好 (纯 HTTP) |
| 自动重连 | 手动 | EventSource 自动 (since last-event-id) |
| 适合 | 聊天, 游戏, 实时协作 | 通知, feed, 实时数据推送 |
参考
- RFC: 6455
- SSE: html.spec.whatwg.org/multipage/server-sent-events.html
- WS 测试: websocat (CLI WebSocket client)
Keywords: WebSocket, upgrade, frame, masking, ping/pong, SSE, EventSource, heartbeat