SSE与AI流式输出实战深度解析
SSE 是基于 HTTP 的单向服务端推送技术,浏览器用 EventSource 收流、断线自动重连; 现在大模型聊天几乎都用它实现"打字机"流式输出。这篇文章讲透原理、格式和与 WebSocket 的选型。
一句话概括
SSE(Server-Sent Events)就是服务端往浏览器单向推数据的一条 HTTP 长连接:客户端发起一次普通 HTTP 请求,服务端把响应头设成 Content-Type: text/event-stream,然后连接不关闭,想推几条就写几条,浏览器用 EventSource 一行代码就能收。
面试官为什么爱问它?因为 2026 年所有大模型聊天(ChatGPT、文心、通义)的”打字机”逐字效果,底层基本都是 SSE。你只要答清楚三件事:原理、消息格式、和 WebSocket 怎么选,这道题就稳了。
核心知识点
1. 原理:一次 HTTP 请求,服务端持续写
说白了,SSE 不是新协议,就是普通的 HTTP 响应没结束。服务端把响应头设为事件流格式,然后按 SSE 规范持续往响应体里写文本,每写完一条消息就发一次,浏览器每收到一条就触发一次 message 事件。
1
2
3
4
// 前端:三行代码搞定
const es = new EventSource('/api/stream');
es.onmessage = (e) => console.log('收到:', e.data); // 每推一条触发一次
// 组件卸载时记得关:es.close();
1
2
3
4
5
6
7
8
// 后端(Node + Express):核心就三行响应头 + 循环 res.write
app.get('/api/stream', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.flushHeaders(); // 立即把响应头发出去,否则可能被缓冲
const timer = setInterval(() => res.write(`data: ${new Date().toISOString()}\n\n`), 1000);
req.on('close', () => clearInterval(timer)); // 客户端断开要清理,防泄漏
});
要点:\n\n(两个换行)是消息结束的标志,浏览器靠它切分消息;客户端断开时服务端必须清理定时器,不然就是经典的内存泄漏。
2. 消息格式:先把最简单的跑通
一条消息 = 一行 data: + 一个空行。服务端往连接里写下面的原文,浏览器就收到一条 "你好":
1
2
data: 你好
前端收(自动触发,不用轮询):
1
es.onmessage = (e) => console.log(e.data); // 输出:你好
多条消息连起来长这样(服务端持续 res.write 写这些原文):
1
2
3
4
5
6
7
8
data: 第一行
data: 第二行
data: 又一条消息
: 这是注释行,浏览器直接忽略,常用来发心跳保活
data: ping
浏览器收到的效果:
- 第 1 块:两行
data:会拼成一条 →"第一行\n第二行",触发 1 次message - 第 2 块:
"又一条消息",触发 1 次message - 第 3 块:
:开头那行是注释被忽略,剩下"ping",触发 1 次message
event 字段:决定这条消息触发哪个事件
- 不带
event→ 触发默认的message事件,onmessage就能收:
1
data: 普通消息
1
es.onmessage = (e) => console.log(e.data); // 普通消息
- 带
event: 名字→ 触发同名自定义事件,必须用addEventListener('名字')才收得到,普通onmessage收不到这条:
1
2
event: update
data: {"progress": 50}
1
2
3
es.addEventListener('update', (e) => {
console.log(e.data); // {"progress": 50}
});
记忆口诀:有
event看event,没event走message。
还有两个可选字段:
id: 1001:给消息编号,断线重连时浏览器自动带上Last-Event-ID,服务端凭它续传断掉的内容retry: 5000:自定义重连间隔(毫秒),不写默认约 3 秒
3. 面试高频对比:SSE vs WebSocket vs 轮询
先记结论:只服务端推文本 → 用 SSE;要双向高频通信(聊天、游戏、协作)→ WebSocket。
| 维度 | SSE | WebSocket | 轮询 |
|---|---|---|---|
| 方向 | 单向(服务端→客户端) | 双向 | 双向(模拟) |
| 协议 | 普通 HTTP | 独立协议(ws://,需升级握手) | HTTP |
| 自动重连 | ✅ 内置 | ❌ 自己写 | 无 |
| 二进制 | ❌ 仅文本 | ✅ | ✅ |
| 实现成本 | 低(原生 EventSource) | 高 | 低但浪费 |
| 典型场景 | AI 流式、通知、进度 | 聊天、游戏、协作 | 简单状态同步 |
再补两个加分点:① HTTP/1.1 下浏览器每域名最多 6 条 SSE 连接(多个标签页会互相占),上 HTTP/2 后基本不受限;② EventSource 不能自定义请求头,带 token 要么拼 URL 参数、要么走 Cookie,所以很多 AI 场景用 fetch + ReadableStream 自己读流(既能带 header 又能拿响应状态码)。
其实你每天都在用
- 和 ChatGPT/文心一言对话,字一个个蹦出来——就是 SSE 逐 token 推送给前端,前端一边收一边往 DOM 里 append
- 网页版大文件处理进度条:任务执行百分比从服务端一格格推过来
- 系统公告/消息提醒:后台有新消息,直接推给在线的浏览器
- 部署/构建日志实时刷屏:CI 面板里一行行日志滚动
- 股票行情、比分直播这种”只读 + 实时”的场景,SSE 比轮询省太多请求
常见误解(FAQ)
❌ 误区1:”SSE 和 WebSocket 差不多,哪个都行” 方向完全不同。SSE 是单向的(服务端→客户端),客户端没法复用这条连接发消息;WebSocket 是双向全双工。只推不用回 → SSE,双向频繁交互 → WebSocket,选错就是架构事故。
❌ 误区2:”EventSource 可以带 Authorization 头” EventSource 构造器只接受 URL,不支持自定义请求头。要带 token 只能拼 URL 参数或靠 Cookie,这也是大模型场景改用 fetch 流式读的原因之一。
❌ 误区3:”断线了要自己写重连逻辑” 不用。EventSource 内置自动重连(默认约 3 秒),重连时还会自动带 Last-Event-ID 头,服务端根据它把断掉的进度续上——这是 SSE 比 WebSocket 省心的重要一点。
❌ 误区4:”SSE 就是聊天用的” 聊天只是 AI 时代最显眼的场景。实时通知、任务进度、日志流、行情推送这些”服务端单向下推文本”的场景都更合适,它比 WebSocket 轻、比轮询省。
❌ 误区5:”连上了就不用管,浏览器会一直收” 组件销毁/页面离开时要手动 es.close(),服务端也要在 req.on('close') 里清理定时器——不清理就是内存泄漏,这是面试官最爱追的坑。
一句话总结
SSE 就是”服务端单向推文本的 HTTP 长连接”:响应头设 text/event-stream、消息以 \n\n 结尾、EventSource 原生收流还自动重连——AI 流式输出选它,双向通信选 WebSocket。