1820 字
约 6 分钟
7
S02-B07 · WebSocket、SSE、长轮询:四种实时通信怎么选

适用读者:要做"服务端主动推消息"的功能,不知道选哪种方案的人。
前置: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

三个关键观察:

  1. 每条消息间隔 约 300 ms,与服务端 sleep(0.3) 一致——服务端不结束响应,就能持续推。
  2. 格式是 字段: 值 + 空行分隔(\n\n),这是 SSE 协议规定的帧边界(呼应 B05 的"边界必须由协议定义")。
  3. 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. 自检

  1. SSE 的帧边界是什么?为什么必须有它?
  2. Sec-WebSocket-Accept 的作用是什么?它防的是什么?
  3. 纯服务端推送(行情/通知),你会选 SSE 还是 WebSocket?理由?
  4. WebSocket 鉴权应该放在哪一步?
  5. HTTP/2 多路复用对 SSE 有什么实际好处?

9. 下一篇

S02-B08 · 实时通信的稳定性:断线、重连、心跳与幂等

长连接一旦建立,新的问题才开始:网络抖一下连就断了、重连后消息会不会丢、心跳间隔怎么定、重复消息怎么去重。下一篇给出可落地的参数取值依据与验证方法。


本篇属于系列《S02 · 计算机与网络基础》。旧文来源:A-1945、A-403、A-511;扩展点 E-S02-013/014。SSE 流时间戳与 Sec-WebSocket-Accept 计算为 2026-11-05 实测。

S02-B07 · WebSocket、SSE、长轮询:四种实时通信怎么选
https://www.clxhxhhr.top/posts/5188/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。