WebSocket vs SSE:2026 年实时推送技术全方位选型指南
“选型从来不是选最好的技术,而是选最合适的那一个。” — 每一位踩过坑的架构师
实时推送已经是现代 Web 应用的标配:股票行情、AI 流式输出、协同编辑、游戏状态同步……每一个场景背后,都藏着一道绕不开的选择题:WebSocket 还是 SSE?
这个问题在 2026 年有了新的背景——HTTP/3(QUIC)加速落地,Cloudflare Workers / Deno Deploy 等边缘运行时成为主流,浏览器对 SSE 的支持趋于完善。本文从原理到实战,帮你做出有据可查的技术决策。
一、先把概念说清楚
WebSocket
WebSocket 是一个全双工、持久化的 TCP 连接协议(RFC 6455)。握手借用 HTTP Upgrade 机制完成,之后双方可以随时互发二进制或文本帧,延迟极低。
Client ──── HTTP Upgrade ──▶ Server
◀──── 101 Switching ───
⟺ 全双工帧流 ⟺
Server-Sent Events(SSE)
SSE 是基于 HTTP 的单向服务器推流协议,响应头 Content-Type: text/event-stream,服务端持续写入格式化的文本块,客户端通过浏览器内置的 EventSource API 订阅。
Client ──── GET /events ──▶ Server
◀── 200 text/event-stream ──
◀── data: {...}
──
◀── data: {...}
──
SSE 天然搭载自动重连(retry: 字段)和断点续传(Last-Event-ID 请求头),浏览器原生支持,无需任何第三方库。
二、2026 年的新变量:HTTP/3 与边缘运行时
HTTP/3 对 WebSocket 的影响
HTTP/3 基于 QUIC(UDP),原生多路复用,消除了 TCP 的队头阻塞。WebSocket over HTTP/3(RFC 9220)已于 2022 年标准化,主流浏览器在 2025 年底完成全面支持。
实测结论:
- 在弱网、移动网络场景下,WebSocket/H3 的连接建立速度比 /H1.1 快约 30-50ms(0-RTT 握手)。
- SSE 作为普通 HTTP 响应,在 HTTP/3 下同样受益,且复用已有连接时无额外握手开销。
边缘运行时的限制
Cloudflare Workers、Vercel Edge Functions 等平台对长连接有严格限制:
| 平台 | WebSocket | SSE |
|---|---|---|
| Cloudflare Workers | ✅ 原生支持(Hibernation API) | ✅ 支持(TransformStream) |
| Vercel Edge Functions | ⚠️ 受限(需 Durable Objects 等方案) | ✅ 较好支持 |
| AWS Lambda | ❌ 不适合(有执行时长限制) | ❌ 不适合 |
| Fly.io / Railway | ✅ 完整支持 | ✅ 完整支持 |
结论:在 Serverless / Edge 场景下,SSE 的部署门槛明显低于 WebSocket。
三、核心维度对比
| 维度 | WebSocket | SSE |
|---|---|---|
| 通信方向 | 全双工(双向) | 单向(服务端 → 客户端) |
| 底层协议 | TCP(或 QUIC over HTTP/3) | HTTP/1.1、HTTP/2、HTTP/3 |
| 自动重连 | ❌ 需手动实现 | ✅ 浏览器原生支持 |
| 断点续传 | ❌ 需业务层处理 | ✅ Last-Event-ID 原生支持 |
| 防火墙兼容性 | ⚠️ 部分企业防火墙会拦截 Upgrade | ✅ 标准 HTTP,几乎无拦截风险 |
| HTTP/2 多路复用 | ❌ 每连接独占一个 TCP 连接 | ✅ 多个 SSE 流共享一个 H2 连接 |
| 二进制支持 | ✅ 原生支持(ArrayBuffer / Blob) | ⚠️ 仅文本(需 Base64 编码二进制) |
| 浏览器并发限制 | 无特殊限制 | HTTP/1.1 下最多 6 个/域名(H2 无此限制) |
| 服务端实现复杂度 | 中等(需维护连接状态) | 低(普通 HTTP 长响应) |
| 水平扩展难度 | 高(有状态,需 sticky session 或消息总线) | 较低(可借助 HTTP 层负载均衡) |
四、代码实战:同一功能的两种实现
以”服务端每秒推送一条实时日志”为例,用 Node.js 实现。
SSE 实现(推荐起点)
// server-sse.mjs
import { createServer } from 'node:http';
createServer((req, res) => {
if (req.url !== '/events') {
res.writeHead(404);
res.end();
return;
}
// SSE 响应头
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*',
});
// 发送初始重连间隔(毫秒)
res.write('retry: 3000
');
let id = 0;
const timer = setInterval(() => {
const payload = JSON.stringify({
ts: Date.now(),
level: 'info',
msg: `Log entry #${++id}`,
});
// SSE 格式:id + event + data,以两个换行结束
res.write(`id: ${id}
event: log
data: ${payload}
`);
}, 1000);
req.on('close', () => {
clearInterval(timer);
console.log('Client disconnected');
});
}).listen(3000, () => console.log('SSE server on :3000'));
// client-sse.mjs(浏览器端等效逻辑)
const es = new EventSource('/events');
es.addEventListener('log', (e) => {
const { ts, level, msg } = JSON.parse(e.data);
console.log(`[${level}] ${msg} (latency: ${Date.now() - ts}ms)`);
});
es.onerror = () => console.warn('SSE error, browser will auto-reconnect...');
WebSocket 实现
// server-ws.mjs(使用标准库 ws)
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 3001 });
wss.on('connection', (ws, req) => {
console.log('Client connected');
let id = 0;
const timer = setInterval(() => {
if (ws.readyState !== ws.OPEN) return;
ws.send(JSON.stringify({
id: ++id,
ts: Date.now(),
level: 'info',
msg: `Log entry #${id}`,
}));
}, 1000);
// WebSocket 支持接收客户端消息(SSE 做不到)
ws.on('message', (data) => {
const { cmd } = JSON.parse(data);
if (cmd === 'pause') clearInterval(timer);
});
ws.on('close', () => {
clearInterval(timer);
console.log('Client disconnected');
});
});
console.log('WS server on :3001');
// client-ws.mjs
const ws = new WebSocket('ws://localhost:3001');
ws.onmessage = (e) => {
const { ts, level, msg } = JSON.parse(e.data);
console.log(`[${level}] ${msg} (latency: ${Date.now() - ts}ms)`);
};
// 需要手动实现重连
ws.onclose = () => setTimeout(() => location.reload(), 3000);
// 双向:客户端可发指令
document.getElementById('pause').onclick = () =>
ws.send(JSON.stringify({ cmd: 'pause' }));
两段代码对比的直观感受:SSE 服务端是一个”普通 HTTP 路由 + 定时写入”,WebSocket 服务端需要管理连接状态和消息帧,复杂度更高但能力也更强。
五、选型决策框架
优先选 SSE,如果你的场景是:
- AI 流式输出(ChatGPT / Claude 风格的打字机效果)
- 实时通知、Feed 流、进度条
- 日志流、监控大盘
- 部署在 Serverless / Edge 平台
- 团队对 WebSocket 运维经验不足
- 需要穿越企业防火墙或代理
优先选 WebSocket,如果你的场景是:
- 协同编辑(双向 OT/CRDT 操作流)
- 在线游戏、实时竞价、交易撮合
- 音视频信令(WebRTC SDP/ICE 交换)
- 需要传输二进制帧(音频包、图像差量)
- 客户端需要主动发送高频消息(>10 次/秒)
2026 年的新建议
如果你在构建 AI Agent 工作流的前端界面,SSE 几乎是唯一合理选择:流式 token 输出、工具调用进度、多 step 状态更新,全部适合单向推流。WebSocket 在这里只会增加复杂度。
六、生产级注意事项
SSE 的两个常见陷阱:
Nginx 反向代理需关闭缓冲:
location /events { proxy_pass http://backend; proxy_buffering off; # 关键! proxy_cache off; proxy_read_timeout 86400s; proxy_set_header Connection ''; proxy_http_version 1.1; }HTTP/1.1 下的连接数限制:同域名最多 6 个并发 SSE。升级到 HTTP/2 即可解除,或用
SharedWorker在多个 Tab 间共享单条 SSE 连接。
WebSocket 的常见陷阱:
- 心跳保活:大量云平台(AWS ALB、Cloudflare)会对空闲 WebSocket 连接 60-300s 后强制断开,必须实现
ping/pong心跳。 - 水平扩展:多实例部署时需要 Redis Pub/Sub 或 NATS 等消息总线路由消息,否则不同实例的客户端收不到彼此的广播。
七、总结
| 场景 | 推荐方案 |
|---|---|
| AI 流式输出 | SSE |
| 实时通知 / 消息推送 | SSE |
| 协同编辑 / 游戏 | WebSocket |
| 高频双向交互 | WebSocket |
| Serverless / Edge 部署 | SSE |
| 需穿越企业防火墙 | SSE |
2026 年的核心判断标准只有一个:客户端是否需要主动、高频地向服务端发送数据?
- 需要 → WebSocket
- 不需要 → SSE,它更简单、更稳健、HTTP 生态无缝集成
不要因为 WebSocket “听起来更厉害”就无脑选它。SSE 在绝大多数推送场景下已经足够,而且你的运维团队会感谢你。
延伸阅读
- RFC 6455 - The WebSocket Protocol
- RFC 9220 - Bootstrapping WebSockets with HTTP/3
- MDN - Using server-sent events
- Cloudflare Workers WebSocket Hibernation API
- Will it SSE? — 主流云平台 SSE 兼容性测试报告(2025)
本文代码已在 Node.js 22 LTS + Chrome 134 环境下验证。如有勘误欢迎在评论区指出。








