适用读者:要做"服务端主动推消息"的功能,不知道选哪种方案的人。
前置:S02-B06(HTTP 与 HTTPS)。
实测环境:Ubuntu 24.04.1 / Python 3.12.3 / curl 8.x;SSE 流与 WebSocket 握手计算为 2026-11-05 实测。
覆盖旧文:A-1945(四种实时通信技术,80 点)、A-403(WebSocket 与 HTTP,114 点)、A-511(WebSocket 入门,36 点)= 230 点。
覆盖扩展:E-S02-013(WebSocket 握手与帧)、E-S02-014(SSE 与 HTTP/2)。
上一篇:S02-B06 · HTTP 与 HTTPS:加密发生在哪一层
下一篇:S02-B08 · 实时通信的稳定性。
1. 一句话结论
四种方案的本质区别只有两点:连接是否长驻、方向是单向还是双向。
| 方案 | 连接 | 方向 | 协议基础 |
|---|---|---|---|
| 短轮询 | 每次新建 | 客户端拉 | HTTP |
| 长轮询 | 挂起直到有数据 | 客户端拉(伪推送) | HTTP |
| SSE | 长连接 | 服务端 → 客户端单向 | HTTP(text/event-stream) |
| WebSocket | 长连接 | 双向 | HTTP Upgrade 后切换协议 |
2. 实测一:SSE 是一条"永不结束"的 HTTP 响应
2.1 服务端(最小实现)
from http.server import BaseHTTPRequestHandler, HTTPServer
import time
class H(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-Type", "text/event-stream")
self.send_header("Cache-Control", "no-cache")
self.end_headers()
for i in range(3):
self.wfile.write(f"id: {i}\ndata: tick-{i}\n\n".encode()); self.wfile.flush()
time.sleep(0.3)
HTTPServer(("127.0.0.1", 19020), H).serve_forever()
2.2 客户端观察(带毫秒时间戳)
$ curl -sN http://127.0.0.1:19020/ | while IFS= read -r line; do [ -n "$line" ] && echo "$(date +%T.%N | cut -c1-12) $line"; done
实测输出:
17:13:01.527 id: 0
17:13:01.537 data: tick-0
17:13:01.827 id: 1
17:13:01.833 data: tick-1
17:13:02.127 id: 2
17:13:02.134 data: tick-2
三个关键观察:
- 每条消息间隔 约 300 ms,与服务端
sleep(0.3)一致——服务端不结束响应,就能持续推。 - 格式是
字段: 值+ 空行分隔(\n\n),这是 SSE 协议规定的帧边界(呼应 B05 的"边界必须由协议定义")。 id:字段用于断线续传(浏览器重连时会带Last-Event-ID)——这是 SSE 相对轮询的最大优势。
3. 实测二:WebSocket 握手就是一次带"暗号"的 HTTP 请求
3.1 握手请求
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
3.2 服务端必须算出的应答
Sec-WebSocket-Accept = base64(SHA1(Key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))
实测计算(RFC 6455 官方示例值):
$ python3 -c "import base64,hashlib; \
k='dGhlIHNhbXBsZSBub25jZQ=='; \
print(base64.b64encode(hashlib.sha1((k+'258EAFA5-E914-47DA-95CA-C5AB0DC85B11').encode()).digest()).decode())"
s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
服务端回:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
101 之后,这条 TCP 连接就不再是 HTTP 了——双方按 WebSocket 帧格式通信(帧头含 FIN/opcode/掩码/长度)。
这就是为什么 WebSocket 能穿透大部分代理:它开头是一个标准 HTTP 请求(走 80/443),代理放行后协议才切换。
注意:握手本身不校验业务身份。鉴权要在握手请求里带(Cookie / Token / 子协议)——很多实现忘了这一点,导致"任何人拿到 URL 就能连"。
4. 四种方案对比(工程判据)
| 维度 | 短轮询 | 长轮询 | SSE | WebSocket |
|---|---|---|---|---|
| 实时性 | 差(取决于间隔) | 较好 | 好 | 最好 |
| 服务端连接数 | 低 | 高(挂起) | 高(一条/客户端) | 高(一条/客户端) |
| 客户端→服务端 | 每次请求 | 每次请求 | 另发普通 HTTP | 同一连接双向 |
| 浏览器支持 | 全 | 全 | 除 IE 外基本全 | 全(现代) |
| 代理/防火墙友好度 | 最好 | 好 | 好(就是普通 HTTP) | 好(Upgrade 可能被个别代理拦) |
| 断线续传 | 无(靠业务) | 无 | 内置 Last-Event-ID |
需自己实现 |
| 实现复杂度 | 最低 | 低 | 低 | 中(心跳/重连/帧处理) |
| 二进制数据 | ✅(base64) | ✅ | ❌(文本) | ✅(原生帧) |
4.1 选型决策树
需要客户端频繁发数据吗?
├── 需要(聊天、协作、游戏) → WebSocket
└── 不需要(通知、行情、日志、进度) → SSE
└── 客户端是老旧浏览器/无法长连? → 长轮询
└── 数据更新很稀疏(分钟级)? → 短轮询
补充两条实践判据:
| 判据 | 选择 |
|---|---|
| 要跨越"只允许 HTTP 的代理" | SSE 更稳(就是 HTTP) |
| 需要服务端推送 + 断线自动补发 | SSE(id + Last-Event-ID) |
| 双向高频(> 10 msg/s 每连接) | WebSocket |
5. HTTP/2 与 HTTP/3 对实时通信的影响(E-S02-014)
| 特性 | 对实时通信的意义 |
|---|---|
| HTTP/2 多路复用 | SSE 可以和其他请求共用一个 TCP 连接,不必每个流开一条 |
| HTTP/2 头部压缩(HPACK) | 长连接下重复头部开销降低 |
| HTTP/3(QUIC) | 连接迁移(换网络不断)+ 无队头阻塞 |
但注意:HTTP/2 下浏览器对单域名并发流仍有限制(一般 100 左右),而 WebSocket 走 Upgrade 后不受 HTTP/2 流控约束。高并发推送场景,WebSocket 仍是主流。
6. 旧文口径的修正点
| 旧文说法 | 修正 |
|---|---|
| "WebSocket 是全双工,性能比 HTTP 好" | 要区分"协议开销"与"业务模型":单向推送用 SSE 更省(无需协议切换、自带续传) |
| "长轮询已经过时" | 在受限网络(只允许短连接)与老旧客户端里仍是可行方案 |
| "WebSocket 更适合大规模推送" | 取决于是否需要双向;纯推送的扇出成本两者相近,SSE 更容易水平扩展(普通 HTTP 基础设施) |
7. 五个误区
| 误区 | 真相 |
|---|---|
| "SSE 只能服务端推,所以不能做聊天" | 客户端可以用普通 HTTP POST 发消息,SSE 只负责下行 |
| "WebSocket 天生支持断线重连" | 协议不提供重连;必须自己实现(B08) |
| "握手成功就代表鉴权通过" | 握手只做协议升级,鉴权要自己加 |
| "长连接越多越好" | 每条连接都占内存与 fd,有硬上限(J03) |
| "SSE 无法传二进制" | 可用 base64,但效率低;二进制优先 WebSocket |
8. 自检
- SSE 的帧边界是什么?为什么必须有它?
Sec-WebSocket-Accept的作用是什么?它防的是什么?- 纯服务端推送(行情/通知),你会选 SSE 还是 WebSocket?理由?
- WebSocket 鉴权应该放在哪一步?
- HTTP/2 多路复用对 SSE 有什么实际好处?
9. 下一篇
S02-B08 · 实时通信的稳定性:断线、重连、心跳与幂等
长连接一旦建立,新的问题才开始:网络抖一下连就断了、重连后消息会不会丢、心跳间隔怎么定、重复消息怎么去重。下一篇给出可落地的参数取值依据与验证方法。
本篇属于系列《S02 · 计算机与网络基础》。旧文来源:A-1945、A-403、A-511;扩展点 E-S02-013/014。SSE 流时间戳与 Sec-WebSocket-Accept 计算为 2026-11-05 实测。