Server-Sent Events (SSE) 服务器推送事件 作者:马育民 • 2026-05-28 16:57 • 阅读:10000 # 基础概念 **SSE(服务器推送事件)** 是一套**基于 HTTP 单向通信**的浏览器标准,允许**服务端主动向客户端持续推送数据**,客户端无需反复轮询。 - 定位:**单向长连接**(服务端 → 客户端),客户端不能通过该通道发数据给服务端。 - 标准:[HTML5 规范](https://html.spec.whatwg.org/multipage/server-sent-events.html),原生浏览器支持,无需第三方库。 - 对比:区别于 WebSocket(**双向通信**),SSE 更适合**纯服务端推送**场景,实现更简单。 ### 核心特点 1. **基于标准 HTTP/HTTPS**,兼容现有代理、认证、跨域规则,无需额外端口。 2. **自动重连**:连接中断后浏览器会定时自动重试连接。 3. **文本格式**:仅传输**UTF-8 文本**,不支持二进制(可 Base64 编码变通)。 4. **原生 API**:浏览器内置 `EventSource` 对象,开箱即用。 5. **限流可控**:支持事件 ID、重连间隔、自定义事件类型。 --- # 适用 & 不适用场景 ### ✅ 适合场景 - 实时消息播报、公告、系统通知 - 实时监控、日志流、数据大屏(指标刷新) - 直播弹幕、实时行情、进度条(文件导出/任务执行进度) - 简单单向推送(**首选 SSE**,比 WebSocket 轻量) ### ❌ 不适合场景 - **双向交互**(聊天、游戏、协同编辑)→ 用 WebSocket - 高频海量二进制流(视频、大文件) - 严格低延迟、高并发双向通信 --- # 工作原理 1. 客户端通过 `EventSource` 发起 **HTTP GET 请求** 建立连接。 2. 服务端响应头标记为 `text/event-stream`,**不关闭 TCP 连接**,转为长连接。 3. 服务端持续按 SSE 格式分段推送数据。 4. 客户端解析流、触发对应事件回调。 5. 网络断开时,`EventSource` 自动等待一段时间后**重新发起连接**。 --- # SSE 数据格式(服务端输出规范) 响应**必须**满足两个条件: 1. 响应头: ```http Content-Type: text/event-stream Cache-Control: no-cache Connection: keep-alive ``` 2. 响应体:**按行分割**,每行以 `字段:` 开头,多条消息用**空行 `\n\n` 分隔**。 ### 标准字段(共 5 种) | 字段 | 说明 | 格式示例 | |------|------|----------| | `data` | **主体数据**,最常用;多行数据用多个 `data:` | `data: 消息内容` | | `event` | 自定义事件名,客户端按事件名监听 | `event: notice` | | `id` | 事件 ID,断线重连时客户端会带上 `Last-Event-ID` 头,用于断点续传 | `id: 1001` | | `retry` | 重连等待时间(单位:毫秒),覆盖浏览器默认值 | `retry: 3000` | | `:` | 注释行,客户端直接忽略(常用于心跳保活) | `: 心跳包` | ### 格式规则 - 每条完整事件**必须以两个换行 `\n\n` 结尾**。 - 单个 `data` 内容含换行,可写多行 `data:`。 - 字段名和冒号后**建议加空格**(规范写法)。 ### 示例 1:基础单条消息 ``` data: 这是一条普通推送消息 ``` ### 示例 2:多行数据 ``` data: 第一行内容 data: 第二行内容 ``` ### 示例 3:带事件名 + ID + 重连时间 ``` event: message id: 2026052801 retry: 5000 data: 带自定义事件和ID的消息 ``` ### 示例 4:心跳包(防代理断开连接) ``` : heartbeat ``` --- # 客户端 API:EventSource ### 1. 基础构造函数 ```javascript // 语法:new EventSource(url[, options]) const es = new EventSource('/sse/stream'); ``` - **默认仅支持 GET 请求**,无法发 POST。 - `options.withCredentials = true` 开启跨域携带 Cookie/认证头。 ### 2. 内置事件 #### (1)`onmessage`:接收默认事件(无 `event:` 字段时触发) ```javascript es.onmessage = function (e) { console.log("收到数据:", e.data); console.log("事件ID:", e.lastEventId); }; ``` #### (2)`onopen`:连接建立成功 ```javascript es.onopen = function () { console.log("SSE 连接已建立"); }; ``` #### (3)`onerror`:连接异常/断开 ```javascript es.onerror = function (err) { console.error("SSE 连接出错/断开", err); // 可选:手动关闭连接,停止自动重连 // es.close(); }; ``` #### (4)监听**自定义事件**(`event: xxx`) ```javascript // 对应服务端 event: notice es.addEventListener('notice', function(e) { console.log("通知消息:", e.data); }); // 对应服务端 event: progress es.addEventListener('progress', function(e) { console.log("任务进度:", e.data + "%"); }); ``` ### 3. 关键方法 & 属性 - `es.close()`:**手动关闭连接**,关闭后不会自动重连。 - `es.readyState`:连接状态枚举 - `0`:CONNECTING 正在连接 - `1`:OPEN 连接正常 - `2`:CLOSED 已关闭 ### 4. 断点续传(结合 Last-Event-ID) 客户端断线重连时,会自动在请求头带上: ```http Last-Event-ID: 2026052801 ``` 服务端读取该头,**从该 ID 之后的消息继续推送**,实现断点恢复。 ### 5. 限制说明 - 同源策略:默认跨域需服务端配置 CORS。 - 部分浏览器**单域名 SSE 连接数有限制**(通常 6~10 条),大量长连接场景需注意。 --- # 服务端实现示例(主流语言) 核心逻辑:**设置响应头 + 循环持续输出 SSE 格式文本 + 不结束响应** ### 1. Node.js (Express) ```javascript const express = require('express'); const app = express(); app.get('/sse', (req, res) => { // 设置 SSE 响应头 res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); res.flushHeaders(); // 立即发送响应头 let count = 0; // 定时推送数据 const timer = setInterval(() => { count++; // 输出标准 SSE 格式 res.write(`id: ${count}\n`); res.write(`event: msg\n`); res.write(`data: 实时计数:${count}\n\n`); }, 1000); // 客户端断开连接时清理定时器 req.on('close', () => { clearInterval(timer); console.log('客户端断开 SSE 连接'); }); }); app.listen(3000); ``` ### 2. Python (FastAPI) ```python from fastapi import FastAPI from fastapi.responses import StreamingResponse import time app = FastAPI() def sse_generator(): count = 0 while True: count += 1 # SSE 格式字符串 yield f"id: {count}\nevent: msg\ndata: 计数 {count}\n\n" time.sleep(1) @app.get("/sse") async def sse_stream(): return StreamingResponse( sse_generator(), media_type="text/event-stream", headers={ "Cache-Control": "no-cache", "Connection": "keep-alive" } ) ``` ### 3. Java (Spring Boot) 使用 `SseEmitter`(Spring 原生封装,极简) ```java import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @RestController public class SseController { private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); @GetMapping("/sse") public SseEmitter sse() { SseEmitter emitter = new SseEmitter(0L); // 0 = 永不超时 executor.scheduleAtFixedRate(() -> { try { long num = System.currentTimeMillis(); // 发送事件:数据、事件名、ID emitter.send(SseEmitter.event() .id(String.valueOf(num)) .name("msg") .data("时间戳:" + num)); } catch (IOException e) { emitter.complete(); // 连接断开,结束 } }, 0, 1, TimeUnit.SECONDS); return emitter; } } ``` --- # SSE 与 轮询、WebSocket 对比 | 特性 | 短轮询 | 长轮询 | SSE | WebSocket | |------|--------|--------|-----|-----------| | 通信方向 | 单向(客户端拉) | 单向(客户端拉) | **单向(服务端推)** | **双向** | | 底层 | HTTP 短连接 | HTTP 长连接 | HTTP 长连接 | TCP 自定义协议 | | 自动重连 | 需手动实现 | 需手动实现 | **浏览器原生支持** | 需手动实现 | | 数据格式 | 任意 | 任意 | 仅文本 | 文本/二进制 | | 实现复杂度 | 低 | 中 | **极低** | 较高 | | 适用场景 | 低频查询 | 准实时推送 | **单向实时推送** | 双向交互、游戏、聊天 | --- # 常见问题 & 踩坑 1. **连接被代理/网关断开** 解决方案:定时发送**心跳包**(`: heartbeat\n\n`),维持长连接。 2. **跨域报错** 服务端配置 CORS,允许 `Origin`、允许 `GET`、暴露 `Last-Event-ID`。 3. **页面刷新/标签页休眠导致重连** 属于浏览器正常行为,配合 `id` 做断点续传即可。 4. **无法发送 POST 请求** SSE 标准仅支持 GET;如需传参可放 URL 或 Cookie。 5. **多标签页连接数超限** 单域名 SSE 连接有上限,复杂系统可改用 WebSocket 或合并连接。 --- # 总结 - **SSE 是轻量、简单的 HTTP 单向长连接方案**,优先用于**服务端主动推文本数据**。 - 相比轮询:减少请求开销、实时性更高;相比 WebSocket:实现简单、兼容 HTTP 生态。 - 选型口诀:**只推不收用 SSE,双向交互用 WebSocket**。 原文出处:http://malaoshi.top/show_1GW3OU9LaDOb.html