🚀 SSE (Server-Sent Events) 详解:比 WebSocket 更轻量的实时推送方案
摘要:在 AI 流式输出、实时通知、数据监控等场景中,我们常需要服务器主动向客户端推送数据。除了大名鼎鼎的 WebSocket,还有一个被严重低估的“神器”——SSE。本文将带你彻底搞懂 SSE 的原理、协议格式、代码实战以及与 WebSocket 的选型对比。
一、 什么是 SSE?
SSE(Server-Sent Events,服务端推送事件) 是一种基于 HTTP 协议的单向实时通信技术。它是 HTML5 规范的一部分,允许服务器向客户端持续发送文本数据流。
用一句话概括它的本质:
SSE = HTTP 长连接 + 文本事件流格式 + 浏览器原生自动重连
💡 核心特点
- 单向通信:只能服务器 → 客户端(客户端发请求仍走普通 HTTP)
- 基于 HTTP:无需协议升级,天然穿透防火墙/代理
- 自动重连:浏览器 EventSource API 内置断线重连机制
- 纯文本传输:数据格式为 UTF-8 文本,不支持二进制
- 轻量级:无需引入第三方库,浏览器原生支持
二、 为什么需要 SSE?(痛点分析)
在没有 SSE 之前,实现"服务器推数据"通常有两种方式:
| 短轮询 (Polling) | 定时发请求,浪费带宽和 CPU,延迟高 |
| 长轮询 (Long Polling) | 每次收到数据后连接断开再重连,开销大 |
| WebSocket | 双向通信能力强,但协议复杂、需额外部署、部分代理不友好 |
SSE 恰好填补了中间地带:当你只需要"服务器持续推数据给客户端"时,它比轮询高效得多,又比 WebSocket 简单得多。
🔥 典型场景:ChatGPT 等 AI 对话的流式输出、股票行情推送、构建进度条、实时日志、消息通知
三、 工作原理与协议格式
3.1 通信流程
客户端 服务器
| |
|— GET /events ————–>|
| Accept: text/event-stream |
| |
|<– 200 OK ——————-|
| Content-Type: text/event-stream
| Cache-Control: no-cache |
| |
|<– data: {"msg":"hello"} —-| ← 第一条数据
|<– data: {"msg":"world"} —-| ← 第二条数据
|<– event: done ————–| ← 自定义事件
|<– data: [DONE] ————-|
| …连接保持打开… |
3.2 SSE 数据格式(重要!)
SSE 是纯文本协议,每条消息由以下字段组成(以 \\n\\n 分隔多条消息):
| data: | ✅ | 消息内容,多行数据每行都以 data: 开头 |
| event: | ❌ | 事件名称,默认为 message |
| id: | ❌ | 消息ID,用于断线重连时告诉服务器上次收到的位置 |
| retry: | ❌ | 重连等待时间(毫秒) |
示例原始响应体:
id: 1
event: update
data: {"temperature": 26.5}
data: 这是一条普通消息
event: done
data: [DONE]
四、 代码实战
4.1 前端(浏览器原生 API)
const source = new EventSource('/api/stream');
// 默认 message 事件
source.onmessage = (e) => {
console.log('收到数据:', e.data);
};
// 自定义事件监听
source.addEventListener('update', (e) => {
console.log('更新事件:', JSON.parse(e.data));
});
// 错误处理 & 自动重连
source.onerror = (e) => {
console.log('连接异常,浏览器将自动重连…', e);
// 如需手动关闭:source.close()
};
4.2 后端(Node.js / Express 示例)
app.get('/api/stream', (req, res) => {
// ⚠️ 关键响应头
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
let count = 0;
const timer = setInterval(() => {
res.write(`data: ${JSON.stringify({ count: ++count })}\\n\\n`);
if (count >= 10) {
clearInterval(timer);
res.write('event: done\\ndata: [DONE]\\n\\n');
res.end();
}
}, 1000);
req.on('close', () => clearInterval(timer));
});
4.3 Python FastAPI 示例(AI 流式输出常用)
from fastapi.responses import StreamingResponse
import asyncio, json
async def event_generator():
for i in range(10):
yield f"data: {json.dumps({'token': f'word_{i}'})}\\n\\n"
await asyncio.sleep(0.5)
yield "data: [DONE]\\n\\n"
@app.get("/chat/stream")
async def chat_stream():
return StreamingResponse(
event_generator(),
media_type="text/event-stream"
)
五、 SSE vs WebSocket 选型指南
| 通信方向 | 单向(服务器→客户端) | 双向 |
| 协议 | HTTP | WS/WSS |
| 数据格式 | 纯文本 | 文本 + 二进制 |
| 自动重连 | ✅ 浏览器内置 | ❌ 需手动实现 |
| 实现复杂度 | ⭐ 极低 | ⭐⭐⭐ 较高 |
| 代理/防火墙兼容 | ✅ 优秀 | ⚠️ 可能被拦截 |
| 连接数限制 | 同域最多 6 个(HTTP/1.1) | 无此限制 |
| 适用场景 | AI流式输出、通知、监控 | 聊天室、游戏、协同编辑 |
⚠️ 注意:HTTP/1.1 下浏览器对同一域名 SSE 连接数限制为 6 个。如果使用 HTTP/2+,此问题基本消除。生产环境强烈建议开启 HTTP/2。
六、 常见坑与最佳实践
proxy_cache off;
X-Accel-Buffering: no; # 或在后端响应头中设置
- URL 参数传递 Token(注意安全)
- Cookie 认证
- 使用 fetch + ReadableStream 手动实现 SSE 客户端(推荐)
七、 总结
| 服务器单向推数据,快速落地 | ✅ SSE |
| AI 大模型流式对话 | ✅ SSE(业界标准) |
| 实时聊天 / 多人协作 / 游戏 | WebSocket |
| 低频数据同步 | 短轮询 / 长轮询 |
SSE 不是 WebSocket 的替代品,而是互补品。 在合适的场景选择合适的工具,才是架构设计的精髓。
📝 笔记标签:#SSE #Server-Sent-Events #实时通信 #WebSocket #AI流式输出 #前端 #后端
如果这篇笔记对你有帮助,欢迎点赞收藏 ❤️ ~

