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 中间件。
python
代码解读
复制代码
from fastapi.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_origins=["*"], # 根据需求调整 allow_methods=["*"], )
2. 连接保持
- 防止超时:某些服务器或代理可能关闭空闲连接,需定期发送心跳包:
python
代码解读
复制代码
async def generate_sse_data(): while True: yield ": keep-alive\n\n" # 注释行(以冒号开头)可作为心跳 await asyncio.sleep(15)
- 流式中断:如果需要主动终止流,可以使用
AbortController
:javascript
代码解读
复制代码
const controller = new AbortController(); fetch('/stream', { signal: controller.signal }); // 终止请求 controller.abort();
3. 数据格式验证
- 错误处理:前端需捕获 JSON 解析错误:
javascript
代码解读
复制代码
eventSource.onmessage = (event) => { try { const data = JSON.parse(event.data); } catch (e) { console.error("Invalid JSON:", event.data); } };
四、适用场景
- ✅ 实时通知(如邮件、站内信)
- ✅ 数据监控(如服务器状态、实时图表)
- ✅ 新闻推送、股票行情
- ❌ 需要客户端频繁发送数据的场景(用 WebSocket)
- ❌ 需要传输二进制数据的场景(用 WebSocket 或 Fetch API)
通过 SSE 协议,你可以轻松实现服务器到客户端的实时单向通信,无需复杂的协议握手,且天然兼容 HTTP 生态。