基础概念
SSE(服务器推送事件) 是一套基于 HTTP 单向通信的浏览器标准,允许服务端主动向客户端持续推送数据,客户端无需反复轮询。
- 定位:单向长连接(服务端 → 客户端),客户端不能通过该通道发数据给服务端。
- 标准:HTML5 规范,原生浏览器支持,无需第三方库。
- 对比:区别于 WebSocket(双向通信),SSE 更适合纯服务端推送场景,实现更简单。
核心特点
- 基于标准 HTTP/HTTPS,兼容现有代理、认证、跨域规则,无需额外端口。
- 自动重连:连接中断后浏览器会定时自动重试连接。
- 文本格式:仅传输UTF-8 文本,不支持二进制(可 Base64 编码变通)。
- 原生 API:浏览器内置
EventSource对象,开箱即用。 - 限流可控:支持事件 ID、重连间隔、自定义事件类型。
适用 & 不适用场景
✅ 适合场景
- 实时消息播报、公告、系统通知
- 实时监控、日志流、数据大屏(指标刷新)
- 直播弹幕、实时行情、进度条(文件导出/任务执行进度)
- 简单单向推送(首选 SSE,比 WebSocket 轻量)
❌ 不适合场景
- 双向交互(聊天、游戏、协同编辑)→ 用 WebSocket
- 高频海量二进制流(视频、大文件)
- 严格低延迟、高并发双向通信
工作原理
- 客户端通过
EventSource发起 HTTP GET 请求 建立连接。 - 服务端响应头标记为
text/event-stream,不关闭 TCP 连接,转为长连接。 - 服务端持续按 SSE 格式分段推送数据。
- 客户端解析流、触发对应事件回调。
- 网络断开时,
EventSource自动等待一段时间后重新发起连接。
SSE 数据格式(服务端输出规范)
响应必须满足两个条件:
- 响应头:
Content-Type: text/event-stream Cache-Control: no-cache Connection: keep-alive - 响应体:按行分割,每行以
字段:开头,多条消息用空行\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. 基础构造函数
// 语法:new EventSource(url[, options])
const es = new EventSource('/sse/stream');
- 默认仅支持 GET 请求,无法发 POST。
options.withCredentials = true开启跨域携带 Cookie/认证头。
2. 内置事件
(1)onmessage:接收默认事件(无 event: 字段时触发)
es.onmessage = function (e) {
console.log("收到数据:", e.data);
console.log("事件ID:", e.lastEventId);
};
(2)onopen:连接建立成功
es.onopen = function () {
console.log("SSE 连接已建立");
};
(3)onerror:连接异常/断开
es.onerror = function (err) {
console.error("SSE 连接出错/断开", err);
// 可选:手动关闭连接,停止自动重连
// es.close();
};
(4)监听自定义事件(event: xxx)
// 对应服务端 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)
客户端断线重连时,会自动在请求头带上:
Last-Event-ID: 2026052801
服务端读取该头,从该 ID 之后的消息继续推送,实现断点恢复。
5. 限制说明
- 同源策略:默认跨域需服务端配置 CORS。
- 部分浏览器单域名 SSE 连接数有限制(通常 6~10 条),大量长连接场景需注意。
服务端实现示例(主流语言)
核心逻辑:设置响应头 + 循环持续输出 SSE 格式文本 + 不结束响应
1. Node.js (Express)
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)
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 原生封装,极简)
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 自定义协议 |
| 自动重连 | 需手动实现 | 需手动实现 | 浏览器原生支持 | 需手动实现 |
| 数据格式 | 任意 | 任意 | 仅文本 | 文本/二进制 |
| 实现复杂度 | 低 | 中 | 极低 | 较高 |
| 适用场景 | 低频查询 | 准实时推送 | 单向实时推送 | 双向交互、游戏、聊天 |
常见问题 & 踩坑
连接被代理/网关断开
解决方案:定时发送心跳包(: heartbeat\n\n),维持长连接。跨域报错
服务端配置 CORS,允许Origin、允许GET、暴露Last-Event-ID。页面刷新/标签页休眠导致重连
属于浏览器正常行为,配合id做断点续传即可。无法发送 POST 请求
SSE 标准仅支持 GET;如需传参可放 URL 或 Cookie。多标签页连接数超限
单域名 SSE 连接有上限,复杂系统可改用 WebSocket 或合并连接。
总结
- SSE 是轻量、简单的 HTTP 单向长连接方案,优先用于服务端主动推文本数据。
- 相比轮询:减少请求开销、实时性更高;相比 WebSocket:实现简单、兼容 HTTP 生态。
- 选型口诀:只推不收用 SSE,双向交互用 WebSocket。