前言
做实时功能时,很多团队都会遇到同一个问题:消息推送到底用 WebSocket 还是 SSE?
如果只是"能跑起来",两者都能做;但到了线上,连接数、稳定性、网关兼容、开发复杂度都会影响结果。
这篇文章用工程视角讲清三件事:
- 两者的本质区别;
- 典型业务场景该怎么选;
- 上线前最容易踩的坑有哪些。
一、先给结论:一句话理解两者
- WebSocket:全双工,客户端和服务端都可以主动发消息,适合高频双向交互。
- SSE(Server-Sent Events):服务端单向推送到客户端,基于 HTTP,适合"服务端通知客户端"类场景。
如果你现在只记一条:
只需要服务端推送,用 SSE 先赢一半复杂度;双向实时互动,直接 WebSocket。
二、核心差异对比(工程最关心的维度)
| 维度 | WebSocket | SSE |
|---|---|---|
| 通信方向 | 双向 | 单向(服务端 -> 客户端) |
| 协议基础 | ws:// / wss:// |
HTTP(text/event-stream) |
| 连接维护 | 需处理心跳、断线重连 | 浏览器原生支持自动重连(可配) |
| 数据格式 | 文本/二进制都可 | 主要是文本流 |
| 适用场景 | 聊天、协同编辑、游戏、交易终端 | 通知流、日志流、任务进度、AI输出流式展示 |
| 实现复杂度 | 较高 | 较低 |
三、典型场景怎么选
场景 1:AI 对话流式输出
用户发请求后,前端只需要不断接收模型输出。
这类是标准"服务端持续推送",SSE 更轻量,接入成本低。
场景 2:IM 聊天室
客户端要发消息、撤回、输入状态同步,服务端也要广播。
这是典型双向高频,WebSocket 更合适。
场景 3:运维看板/实时监控
浏览器端只看状态更新,不太需要主动发。
优先 SSE;如果后续要双向控制,再评估迁移 WebSocket。
四、最小代码示例
4.1 SSE:Node.js(Express)服务端
js
import express from "express";
const app = express();
app.get("/events", (req, res) => {
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");
const timer = setInterval(() => {
res.write(`data: ${JSON.stringify({ ts: Date.now() })}\n\n`);
}, 1000);
req.on("close", () => clearInterval(timer));
});
app.listen(3000);
4.2 前端订阅 SSE
js
const es = new EventSource("/events");
es.onmessage = (e) => {
const payload = JSON.parse(e.data);
console.log("收到推送", payload);
};
这套代码就能跑出"服务端持续推送,前端实时显示"的最小闭环。
五、上线最常见的 4 个坑
5.1 反向代理超时
Nginx/网关如果默认超时较短,会导致长连接被频繁断开。
需要针对长连接路由单独配置超时与缓冲策略。
5.2 忘记做重连与幂等消费
不管 WebSocket 还是 SSE,真实网络都会抖动。
客户端要有重连逻辑,服务端要避免消息重复处理。
5.3 心跳机制缺失(WebSocket)
没有心跳时,连接可能"看起来在线,实际上不可用"。
建议服务端和客户端都实现 ping/pong 或应用层心跳。
5.4 把实时链路当普通 HTTP 接口压测
连接数、广播风暴、消息堆积和普通 QPS 不是一套指标。
实时系统要看:在线连接数、消息延迟、断线率、重连成功率。
六、实战建议:从"够用"开始,不要一步到位过度设计
可以按下面顺序决策:
- 先判断是否需要双向;不需要就优先 SSE;
- 评估连接规模与消息频率,确认网关和实例承载;
- 加入可观测性(连接数、延迟、断线率)再扩大流量;
- 当业务从"通知"演进为"交互",再升级到 WebSocket。
这比一开始就上复杂通信栈更稳,也更省研发成本。
总结
WebSocket 和 SSE 没有绝对优劣,关键看你是"实时通知"还是"实时交互"。
工程上最实用的原则是:能用 SSE 先用 SSE,必须双向再上 WebSocket。
你们现在线上的实时需求主要是通知流,还是双向交互?评论区说下场景,一起交流学习~~~