Server-Sent Events (SSE) 是一种基于 HTTP 的 服务器到客户端的单向实时通信协议 ,专门用于实现服务器主动向浏览器推送数据。它比 WebSocket 更轻量级,适合需要 单向实时更新 的场景(如实时通知、股票行情、日志流等)。
一、SSE 协议原理
1. 核心机制
- 基于 HTTP 长连接:客户端发起一个普通 HTTP 请求,服务器保持连接打开,持续发送数据。
- 文本协议:数据以纯文本格式传输,默认编码为 UTF-8。
- 事件驱动 :服务器可以发送不同类型的事件(如
message
、custom-event
),客户端通过监听事件处理数据。 - 自动重连:若连接中断,浏览器默认会尝试重新连接。
2. 数据格式要求
服务器返回的数据必须遵循 SSE 规范:
text
event: custom-event\n // 事件类型(可选,默认是 message)
data: {"key": "value"}\n // 数据内容(可多行)
id: 12345\n // 事件 ID(用于断线重连)
retry: 5000\n // 重连间隔(毫秒,可选)
\n // 空行表示事件结束
- 关键点 :
- 每个事件以空行(
\n\n
)分隔。 data
字段可多次出现,最终值是多行合并后的结果。- 若未指定
event
,客户端默认监听onmessage
事件。
- 每个事件以空行(
3. 与 WebSocket 对比
特性 | SSE | WebSocket |
---|---|---|
通信方向 | 单向(服务器→客户端) | 双向 |
协议 | HTTP | 独立的 WS 协议 |
数据格式 | 文本 | 二进制/文本 |
断线重连 | 内置支持 | 需手动实现 |
浏览器兼容性 | 现代浏览器(IE 除外) | 广泛支持 |
适用场景 | 实时通知、数据流 | 聊天、游戏、双向交互 |
二、SSE 使用方法
1. 后端实现(FastAPI 示例)
python
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import asyncio
import json
app = FastAPI()
async def generate_sse_data():
for i in range(10):
await asyncio.sleep(1)
# 发送 SSE 格式数据
yield f"data: {json.dumps({'count': i, 'status': 'OK'})}\n\n"
@app.get("/sse")
async def sse_endpoint():
return StreamingResponse(
generate_sse_data(),
media_type="text/event-stream" # 必须指定此 MIME 类型
)
2. 前端实现
前端接收 FastAPI 流式接口的数据主要依赖于 Fetch API 的流式处理能力,通过逐步读取分块(chunk)数据实现。以下是实现步骤和示例代码:
1. 前端接收流式数据(基础版)
确保你的 FastAPI 接口返回的是 StreamingResponse
,例如:
python
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import asyncio
app = FastAPI()
async def generate_data():
for i in range(5):
await asyncio.sleep(1) # 模拟延迟
yield f"Data chunk {i}\n" # 发送数据块(注意换行符分隔)
@app.get("/stream")
async def stream_data():
return StreamingResponse(generate_data(), media_type="text/plain")
使用 fetch
+ ReadableStream
逐步读取数据:
javascript
async function fetchStream() {
try {
const response = await fetch('http://your-api/stream');
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break; // 流结束
const chunk = decoder.decode(value);
console.log('Received chunk:', chunk);
// 更新页面内容(例如:将数据追加到 DOM)
document.getElementById('output').textContent += chunk;
}
} catch (error) {
console.error('Stream error:', error);
}
}
// 调用函数
fetchStream();
2. 处理 JSON 流式数据
如果后端返回的是 换行分隔的 JSON 数据(NDJSON):
python
# FastAPI 示例:返回 JSON 流
async def generate_json():
for i in range(3):
await asyncio.sleep(1)
yield json.dumps({"id": i, "message": "Hello"}) + "\n" # 注意换行符
@app.get("/json-stream")
async def json_stream():
return StreamingResponse(generate_json(), media_type="application/x-ndjson")
前端按行解析 JSON:
javascript
let buffer = '';
async function fetchJsonStream() {
const response = await fetch('http://your-api/json-stream');
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// 按换行符分割处理
const lines = buffer.split('\n');
buffer = lines.pop(); // 剩余部分保留到下次处理
for (const line of lines) {
if (line.trim() === '') continue;
try {
const data = JSON.parse(line);
console.log('Parsed JSON:', data);
} catch (e) {
console.error('JSON parse error:', e);
}
}
}
}
4. 使用 EventSource(SSE 协议)
如果后端支持 Server-Sent Events (SSE),可以更简化:
python
# FastAPI 设置 SSE
from fastapi.responses import StreamingResponse
@app.get("/sse-stream")
async def sse_stream():
async def event_generator():
for i in range(5):
await asyncio.sleep(1)
yield f"data: {i}\n\n" # SSE 格式要求
return StreamingResponse(event_generator(), media_type="text/event-stream")
前端使用 EventSource
:
javascript
// 创建 EventSource 对象
const eventSource = new EventSource('http://your-api/sse');
// 监听默认消息事件(未指定 event 字段时触发)
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received data:', data);
};
// 监听自定义事件(需服务器发送 event: custom-event)
eventSource.addEventListener('custom-event', (event) => {
console.log('Custom event:', event.data);
});
// 监听错误事件
eventSource.onerror = (error) => {
console.error('SSE Error:', error);
// 可根据错误类型决定是否关闭连接
// eventSource.close();
};
// 手动关闭连接
// eventSource.close();
3. 高级功能
-
断线重连 :
- 浏览器自动处理重连,可通过
retry
字段指定重试间隔。 - 服务器发送
id
字段时,客户端会在重连时通过Last-Event-ID
请求头告知服务器最后接收的事件 ID。
- 浏览器自动处理重连,可通过
-
自定义事件 :
python# FastAPI 后端发送自定义事件 yield f"event: status\ndata: Server is alive\n\n"
javascript// 前端监听 eventSource.addEventListener('status', (e) => { console.log(e.data); // 输出 "Server is alive" });
三、关键注意事项
1. 跨域问题
-
跨域问题 :确保 FastAPI 已配置 CORS 中间件。
pythonfrom fastapi.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_origins=["*"], # 根据需求调整 allow_methods=["*"], )
2. 连接保持
-
防止超时 :某些服务器或代理可能关闭空闲连接,需定期发送心跳包:
pythonasync def generate_sse_data(): while True: yield ": keep-alive\n\n" # 注释行(以冒号开头)可作为心跳 await asyncio.sleep(15)
-
流式中断 :如果需要主动终止流,可以使用
AbortController
:javascriptconst controller = new AbortController(); fetch('/stream', { signal: controller.signal }); // 终止请求 controller.abort();
3. 数据格式验证
-
错误处理 :前端需捕获 JSON 解析错误:
javascripteventSource.onmessage = (event) => { try { const data = JSON.parse(event.data); } catch (e) { console.error("Invalid JSON:", event.data); } };
四、适用场景
- ✅ 实时通知(如邮件、站内信)
- ✅ 数据监控(如服务器状态、实时图表)
- ✅ 新闻推送、股票行情
- ❌ 需要客户端频繁发送数据的场景(用 WebSocket)
- ❌ 需要传输二进制数据的场景(用 WebSocket 或 Fetch API)
通过 SSE 协议,你可以轻松实现服务器到客户端的实时单向通信,无需复杂的协议握手,且天然兼容 HTTP 生态。