- 什么是 SSE?它的全称是什么?
- 全称是 Server-Sent-Events,是 HTML5 规范的一部分。
- 一种服务端主动向浏览器推送数据的技术,基于 HTTP 协议。
- SSE 与 WebSocket 的区别是什么?
| 维度 |
SSE |
WebSocket |
| 协议 |
HTTP |
独立 WS 协议 |
| 通信方式 |
单向(服务器 -> 客户端) |
双向 |
| 实现复杂度 |
简单,原生 EventSource |
需要握手升级 |
| 自动重连 |
浏览器原生支持 |
需要手动实现 |
| 事件类型 |
支持自定义 event |
需要自定义协议 |
| 二进制支持 |
仅文本 |
支持二进制 |
| 试用场景 |
通知、消息推送、AI 流式输出 |
聊天室、协同编辑 |
- 为什么大模型(ChatGPT/DeepSeek)都用SSE 而不是WebSocket?
- 响应是单向数据流,服务端推送 + 客户端渲染即可。
- 基于 HTTP 协议,天然兼容 HTTPS、CDN、代理、Nginx。
- 浏览器原生的 EventSource,断线重连开箱即用。
- 后端实现简单,无需单独维护 WS 长连接。
- SSE 用的是什么HTTP方法和请求头?
- 方法:
GET
- 请求头:
Accept: text/event-stream
- 响应头:
Content-Type: text/event-stream; charset=utf-8
- 关键响应头:
Cache-Control: no-cache、Connection:keep-alive
- 浏览器端如何创建一个SSE 连接?
const es = new EventSource("/api/stream");
es.onmessage = (e) => console.log(e.data); // 接收数据
es.onopen = () => console.log("连接已建立");
es.onerror = (e) => console.log("连接异常", e);
es.addEventListener("custom", (e) => console.log("自定义事件", e.data)); // 发送数据

- SSE 数据格式是怎么定义的?
event: customEvent
id: 12345
data: { "msg": "hello" }
- 每条消息以两个换行\n\n结束
- 字段:
data 必需有、event 自定义事件名称、id (断线重连时 Last-Event-ID)、retry(重连间隔 ms)
- EventSource 不支持 Post,如何传参?
- 用 GET 把参数拼接到 URL 上。
- 或使用
fetch + ReadableStream 自己实现流式读取,这种方式更灵活,可使用 POST 请求,也可以添加 token。
- 如何使用 Fetch 实现 SSE 接收?
const res = await fetch("/api/stream", {
method: "POST",
header: { "Content-Type": "application/json" },
body: JSON.stringify({ prompt: "你好" }),
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
// 按\n\n 切分,逐段解析 data:
}
- Node.js(Koa/Express)如何返回 SSE 流?
// Koa
ctx.set({
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
});
const stream = new ReadableStream({
start(controller) {
controller.enqueque(`data: ${JSON.stringify({ msg: "hi" })}\n\n`);
// 定时推送
const timer = setInterval(() => {
constroller.enqueque(`data: ${Date.now()}\n\n`);
}, 1000);
// 关闭
return () => clearInterval(timer);
},
});
ctx.body = stream;
- SSE 在 Nginx 代理是要注意什么?
- 关闭代理缓冲:
proxy_buffering off;
- 关闭缓存:
proxy_cache off;
- 延长超时:
proxy_read_timeout 3600s;
- 响应头设置
X-Accel-Buffering: no
- SSE 在 CDN/Cloudflare 下缓存怎么办
- Cloudflare 默认会缓冲流式响应,需要在 Cloudflare 后台开启 "HTTP/2 with edge buffering off" 或类似选项。
- 国内 CDN 普遍需要特殊配置或绕过 CDN 直接连接源站点。
- SSE 断线重连机制是怎么工作的?
- 浏览器在连接断开后会自动重连。
- 服务端发送的
id:xxx 会被浏览器记住。
- 重连时请求头会带上
Last-Event-ID:xxx ,服务端可基于此 id 做断点续传。
- 如何手动控制 SSE 重连?
es.addEventListener("error", () => {
es.close(); // 关闭后浏览器还会自动重连
// 如需彻底停止,需配合 readyState 处理
});
readyState:0 (Connecting)、1(Open)、2(Close)。
- SSE 的流式输出在 LLM 场景是如何拼装的?
- 一次 HTTP 长连接,服务端逐个返回 token。
- 前端通过 ReadableStream 增量解析,把 token 拼接到 UI (打字机效果)。
- 涉及“消息合并”算法:用一个 ref 保存历史内容,每次 append 新的 chunk。
- SSE 和长轮询(Long Polling)的区别?
- 长轮询:客户端发起请求,服务端能够承载,有数据后在返回,客户端立即再发请求。
- SSE:一次连接保持,服务端持续推送。
- SSE 延迟更低、连接复用更彻底,资源消耗更少。
- SSE 存在哪些局限性?
- 单向推送,只能服务端推送,不能通过客户端主动发送这条请求。
- 受浏览器同源连接数量限制(HTTP/1.1 下是同时发送 6 个请求)。
- 部分代理/中间件会缓存,导致假的“流式输出”。
- 只能传递 UFT-8 文本。
- 项目中 SSE 用来做什么?举一个具体场景
- 常见:AI 对话流式输出、实时通知、股票行情、日志实时面板、大文件下载进度
- 描述时突出:为什么选 SSE 不选 WebSocket、遇到什么坑(缓冲、断线、中文乱码),怎么解决的。
- AI 流式输出中“中止生成”怎么实现?
- 客户端通过调用
constroller.abort() 关闭 fetch 的 reader。
- 通过 AbortController 传递 signal 给 fetch。
- 服务端配合:监听
req.on('onclose') 事件,停止调用 LLM 接口。
- SSE 接收到的中文数据乱码怎么办?
- 确认服务端
Content-Type 包含 charset=uft-8。
- 前端的 TextDecoder 明确指定
utf-8。
- 检查 Nginx 是否修改了编码。
- 如何保证 SSE 消息的顺序和完整性?
- 用
id字段做消息序号
- 前端按
id排序、去重
- 弱网环境下可以加入 ACK 机制(客户端收到响应后回执,服务端重新传送未确认的)
- 踩的几个坑
- "加 gzip 提升 SSE 性能" → 错。gzip 会在 Nginx 端攒够一定字节才压缩,直接破坏流式输出。 服务端配置里应强制 gzip off。
- Koa 用 ctx.body = ReadableStream 在某些版本下不会真正 pipe → 改用 ctx.respond = false + 原生 res.write,最稳。