SSE(Server-Sent Events)是什么?和 WebSocket 怎么选?
简化版
SSE(Server-Sent Events,服务器发送事件)是一种基于普通 HTTP 的服务器单向推送技术:客户端发一个普通 HTTP 请求,服务器不关闭连接,而是持续用 text/event-stream 格式一条条往下推数据。它只能服务器→客户端单向,胜在简单——就是一个长时间不结束的 HTTP 响应,天然走 HTTP/HTTPS、自动重连、带事件 id。要双向就用 WebSocket,只要服务器单向推(如 AI 流式输出、消息通知、进度条)SSE 更轻量。ChatGPT 那种「文字一个个蹦出来」用的就是 SSE。
详细版
SSE 的本质:一次「永不结束的 HTTP 响应」。
客户端:
GET /events HTTP/1.1
Accept: text/event-stream
服务器:
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
data: 第一条消息\n\n
data: 第二条消息\n\n
...(连接一直开着,有数据就继续写)
数据格式是纯文本,每条以字段行组成、\n\n 分隔:
data:消息内容(可多行)。event:自定义事件名(前端用addEventListener('事件名')监听)。id:事件 id,浏览器会记住,断线重连时通过Last-Event-ID头带回,实现断点续传。retry:告诉浏览器重连间隔(毫秒)。
浏览器端用内置 EventSource 接收,且自动重连:
const es = new EventSource('/events');
es.onmessage = (e) => console.log(e.data);
es.addEventListener('progress', (e) => { /* 自定义事件 */ });
SSE vs WebSocket 速览:
| 维度 | SSE | WebSocket |
|---|---|---|
| 方向 | 服务器→客户端单向 | 双向全双工 |
| 底层 | 普通 HTTP(长响应) | 借 HTTP 握手后独立帧协议 |
| 数据 | 只能文本(UTF-8) | 文本 + 二进制 |
| 自动重连 | 内置(浏览器自动) | 要自己实现 |
| 断点续传 | 内置(Last-Event-ID) | 要自己实现 |
| 复杂度 | 低,就是个 HTTP 接口 | 较高 |
| 代理/防火墙友好 | 好(就是 HTTP) | 好(借 HTTP 握手) |
完整版教学
一、SSE 解决的问题:只要「服务器单向推」
先厘清需求光谱。「实时」有两种:
- 双向实时:聊天、协作、在线游戏——客户端和服务器都要随时主动发。这类只能上 WebSocket。
- 单向推送:服务器有新数据就通知客户端,客户端基本不需要频繁回话——股票行情、系统通知、任务进度、日志滚动、AI 流式回答。这类用 WebSocket 属于「杀鸡用牛刀」,SSE 刚好合适。
SSE 就是为第二类而生:它不追求双向,专注把「服务器持续推」这件事做得极简。
二、SSE 为什么这么简单——它就是个 HTTP 响应
WebSocket 要专门的握手、帧协议、掩码、心跳,而 SSE 什么新协议都没发明:
- 客户端发一个普通 GET 请求。
- 服务器返回
Content-Type: text/event-stream,然后不结束响应,保持连接打开,有数据就往响应流里写一段data: xxx\n\n。 - 浏览器的
EventSource边收边解析,每收到一个完整事件就触发回调。
因为它从头到尾都是 HTTP,所以:能直接走 HTTPS、天然穿过绝大多数代理和防火墙、能复用 HTTP 的认证/Cookie/压缩、服务端实现只要「持有一个 response 对象不断 write」即可。后端框架里往往几行代码就能搞定。
三、内置能力:自动重连 + 断点续传
SSE 有两个 WebSocket 需要自己造轮子、而它协议内置的杀手锏:
① 自动重连——连接断了,浏览器的 EventSource 会自动重新发起请求,默认间隔约 3 秒,服务器还能用 retry: 字段自定义间隔。开发者什么都不用写。
② 断点续传(Last-Event-ID)——服务器给每条消息带 id:,浏览器会记住最后收到的 id;断线重连时,浏览器自动在请求头带上 Last-Event-ID,服务器据此从断点之后继续推,不丢消息、不重复。
服务器推: id: 1001\n data: 消息A\n\n
(断线)
浏览器重连:GET /events Last-Event-ID: 1001
服务器: 从 1001 之后继续推 → id: 1002 ...
这套「自动重连 + 续传」对做可靠消息推送非常省心,是 SSE 相对 WebSocket 的一大优势。
四、SSE 的三个硬限制
选型时必须知道 SSE 的短板:
- 只能单向:客户端要发数据,得另开一个普通 HTTP 请求。真正频繁双向交互就别硬用 SSE。
- 只能传文本(UTF-8):要传二进制得先 base64 编码(会膨胀约 33%)。传大量二进制选 WebSocket。
- HTTP/1.1 下的连接数限制:浏览器对同一域名的并发连接有上限(约 6 个),每个 SSE 都占一条长连接,多开几个页面/标签就可能耗尽。HTTP/2 下靠多路复用,这个问题基本消失(多个 SSE 复用一条连接),所以 SSE + HTTP/2 是推荐组合。
五、经典场景:为什么 AI 流式输出用 SSE
大模型「打字机效果」(文字一个个往外蹦)几乎清一色用 SSE,因为它完美契合:
- 交互是天然单向的——用户发一次问题(普通 POST),然后服务器持续把生成的 token 一段段推回来,客户端只管接收显示。
- 用 SSE 服务端实现极简:模型每生成一点就
write一段data:,前端EventSource边收边渲染。 - 不需要 WebSocket 的双向和二进制能力,SSE 更轻、更好接入现有 HTTP 基础设施(网关、鉴权、限流全复用)。
(注:OpenAI 等 API 用的是 POST + SSE 流式响应,浏览器原生 EventSource 只支持 GET,所以前端常用 fetch 手动读取流来兼容 POST,但底层数据格式仍是 SSE 的 text/event-stream。)
六、到底怎么选:按场景决策
- 要双向、要传二进制、高频互发(聊天、协作、游戏)→ WebSocket。
- 只要服务器单向推、想省事、要自动重连/续传(通知、行情、进度、日志、AI 流式)→ SSE。
- 偶尔查一下有没有新数据、实时性要求不高 → 普通轮询也够,别过度设计。
七、常见误区与追问
| 考点 | 正确口径 |
|---|---|
| SSE | 服务端通过 HTTP 长连接单向推送文本事件 |
| WebSocket | 全双工双向通信 |
| 适用 | 通知、日志、进度、行情等服务端到客户端推送 |
HTTP/1.1 200 OK
Content-Type: text/event-stream
event: message
data: hello
data: next update
SSE 是“服务端持续说”,WebSocket 是“双方都能随时说”;别把单向推送复杂化。
- 误区:SSE 和 WebSocket 完全一样。 SSE 基于 HTTP 单向推送文本,WebSocket 是全双工帧协议。
- 误区:SSE 只能发送一次消息。 SSE 连接保持打开,服务端可持续发送多个事件。
- 误区:SSE 适合大量客户端上行消息。 客户端频繁发送消息仍要走普通 HTTP 请求或改用 WebSocket。
- 追问:SSE 如何断线重连? 浏览器 EventSource 内置重连,并可用 Last-Event-ID 续接。
- 追问:SSE 的 Content-Type 是什么? 使用
text/event-stream,事件按文本格式分行发送。 - 追问:SSE 的限制是什么? 只支持服务端到客户端单向推送,二进制和复杂双向交互不如 WebSocket。
八、加强记忆
SSE 是基于普通 HTTP 的服务器单向推送:一次「不结束的 HTTP 响应」,用 text/event-stream 一条条推 data:。优点是简单(就是 HTTP 接口)、浏览器 EventSource 自带自动重连 + Last-Event-ID 断点续传。限制:单向、只传文本、HTTP/1.1 有并发连接数上限(HTTP/2 解决)。要双向/二进制/高频互发用 WebSocket;只要服务器单向推(通知、行情、进度、AI 流式输出)用 SSE 更轻。