Server-Sent Events(SSE)
- SSE 的特点
- 1. 单向通信
- 2. 简单易用,浏览器原生支持
- 3. 持久连接
- 4. 纯文本传输
- 5. 自动重连机制
- 6. 轻量级协议
- SSE 的实现
- 服务器端实现(Node.js 示例)
- 1. HTTP 响应头设置
- 2. 数据推送模式
- 3. 服务器端代码示例
- 数据格式规范详解
- 1. 完整事件结构
- 2. 错误处理示例
- 客户端实现(HTML + JavaScript)
- HTML + JavaScript 代码示例
- 高级功能
- SSE 与 WebSocket 的比较
- 混合架构实践
- SSE 的应用场景
- 安全与性能优化
- 1. 安全防护
- 2. 性能调优
- SSE扩展方案
- 1. 断线续传实现
- 2. 二进制数据传输
- 总结
在现代 Web 应用中,实时数据推送成为了关键需求之一。例如,在股票行情、天气更新、社交通知等应用场景中,客户端需要能够持续接收服务器端的最新数据。Server-Sent Events (SSE) 是一种基于 HTTP 协议的轻量级实时通信技术,能够让服务器主动向客户端推送消息。
下一节分享了「WebSocket:高效的双向实时通信技术」
SSE 的特点
1. 单向通信
SSE 是服务器向客户端推送数据的单向通道。客户端可以持续接收服务器端的更新,但不会主动向服务器发送数据(除非建立额外的请求)。
2. 简单易用,浏览器原生支持
SSE 使用简单的文本格式传输事件,大多数现代浏览器(如 Chrome、Firefox、Edge 和 Safari)都提供了内置支持,无需额外的库或插件。
3. 持久连接
SSE 采用持久 HTTP 连接(HTTP 长连接),服务器可以连续发送数据流,而无需客户端重复发送请求。
4. 纯文本传输
SSE 传输的数据是纯文本格式,消息之间以换行符 (\n\n
) 分隔,便于解析和调试。
5. 自动重连机制
如果 SSE 连接因网络故障等原因断开,浏览器会自动尝试重新连接服务器,而无需额外处理。
const es = new EventSource('/sse');
es.onerror = () => { /* 处理中断 */ };
- 底层原理: SSE协议规范中定义了客户端自动重连机制,当连接异常断开时,浏览器默认以指数退避策略(初始约3秒)尝试重新连接
- 开发优势: 相比WebSocket需手动实现断线重连,SSE显著降低实时应用开发复杂度
- 注意事项: 可通过
retry:
字段指定重试间隔(单位:毫秒ms)
6. 轻量级协议
- 协议开销: 基于纯文本的简单协议格式,每个消息仅增加约20字节头部信息
- 传输效率: 适用于高频小数据量场景(如实时股票报价),避免WebSocket的握手开销
SSE 的实现
服务器端实现(Node.js 示例)
在服务器端,SSE 通过正确的 HTTP 头部设置来标识事件流,并以特定格式发送数据。
1. HTTP 响应头设置
res.writeHead(200, {'Content-Type': 'text/event-stream', // 必须声明SSE类型'Cache-Control': 'no-cache', // 禁用浏览器缓存'Connection': 'keep-alive', // 保持长连接'Access-Control-Allow-Origin': '*' // CORS配置
});
- 关键头信息:
Content-Type
必须为text/event-stream
Cache-Control: no-cache
防止代理服务器缓存X-Accel-Buffering: no
(Nginx环境禁用缓冲)
2. 数据推送模式
setInterval(() => {res.write(`data: ${JSON.stringify(data)}\n\n`);
}, 1000);
- 流式写入: 必须通过分块编码(chunked encoding)持续发送数据
- 消息边界: 每条消息必须以
\n\n
结尾,字段间用\n
分隔 - 性能优化: 使用写缓冲区,避免频繁的TCP包发送
3. 服务器端代码示例
const http = require('http');http.createServer((req, res) => {res.writeHead(200, {'Content-Type': 'text/event-stream','Cache-Control': 'no-cache','Connection': 'keep-alive'});let clientId = 0;const retryInterval = 5000; // 5秒自动重连const eventSender = setInterval(() => {clientId++;const message = `id: ${clientId}\nevent: update\nretry: ${retryInterval}\ndata: This is message number ${clientId}\n\n`;res.write(message);}, 1000);req.on('close', () => {clearInterval(eventSender);});}).listen(3000, () => {console.log('SSE server running at http://localhost:3000/');
});
数据格式规范详解
1. 完整事件结构
event: userUpdate\n
data: {"id": 101, "status": "online"}\n
id: 101-20230301\n
retry: 10000\n\n
event
:自定义事件类型,触发客户端对应事件监听器data
:支持多行数据(每行需以data:
开头)- 建议JSON格式传输结构化数据
id
:事件ID,用于断线续传(Last-Event-ID请求头)retry
:控制重连间隔(单位:毫秒)
2. 错误处理示例
res.write(`event: error\ndata: 服务端异常\n\n`);
- 客户端通过监听
error
事件进行异常处理 - 建议包含错误代码和描述信息的JSON数据
客户端实现(HTML + JavaScript)
在前端,使用 EventSource
API 轻松实现 SSE 连接。
HTML + JavaScript 代码示例
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>SSE Demo</title><script>document.addEventListener("DOMContentLoaded", function () {const eventSource = new EventSource('http://localhost:3000/api/notifications');// 自定义事件处理eventSource.addEventListener('update', function(event) {console.log('Received update:', event.data);});// 默认消息类型监听eventSource.onmessage = function(event) {console.log('General message:', event.data);};// 错误处理eventSource.onerror = function(err) {console.error('EventSource failed:', err);};});</script>
</head>
<body><h1>Server-Sent Events Demo</h1><p>Check the console for messages.</p>
</body>
</html>
高级功能
// 自定义请求头(需CORS支持)
const es = new EventSource('/api/stream', {withCredentials: true
});// 连接状态管理
es.onopen = () => console.log('连接已建立');
es.onerror = (e) => {if (e.target.readyState === EventSource.CLOSED) {console.log('连接永久关闭');}
};// 主动关闭连接
document.getElementById('stop').onclick = () => es.close();
SSE 与 WebSocket 的比较
特性 | SSE | WebSocket |
---|---|---|
协议 | HTTP长连接 | 独立的ws/wss协议 |
压缩支持 | 支持gzip/brotli压缩 | 需要扩展实现 |
消息延迟 | 较高(依赖HTTP层) | 低延迟(二进制帧) |
连接方式 | 服务器推送数据到客户端 | 客户端和服务器可双向通信 |
适用场景 | 服务器需要频繁更新数据,如新闻推送、日志更新、实时仪表盘(如监控系统)、新闻/社交媒体Feed流、长轮询替代方案等 | 需要实时交互,如聊天应用、多人协作、高频双向通信(如在线游戏)等 |
浏览器支持 | 原生支持,大多数现代浏览器均支持 | 需要 WebSocket API,部分老旧浏览器不支持 |
连接管理 | 自动管理重新连接 | 需手动处理连接丢失与重连 |
混合架构实践
// 组合使用案例:SSE推送通知 + WebSocket实现聊天
const notificationStream = new EventSource('/notifications');
const chatSocket = new WebSocket('wss://chat.example.com');// 消息类型路由处理
function handleMessage(data) {if (data.type === 'notification') {// 使用SSE处理} else if (data.type === 'chat') {// 使用WebSocket处理 }
}
SSE 的应用场景
SSE 适用于以下应用场景:
- 实时新闻推送:新闻网站可使用 SSE 向用户推送最新的新闻资讯。
- 股票行情更新:股票交易平台可以用 SSE 传输市场行情数据。
- 服务器日志实时展示:开发者工具可以通过 SSE 实时显示服务器日志。
- 社交通知:社交平台可利用 SSE 向用户推送好友动态或点赞通知。
安全与性能优化
1. 安全防护
// 身份验证示例(Cookie + CORS)
app.use('/secure-stream', (req, res, next) => {if (!validateToken(req.cookies.token)) {return res.status(401).end();}next();
});
- 认证方案: Cookie验证、JWT令牌、OAuth2.0
- 安全头设置:
Content-Security-Policy: default-src 'self'
X-Content-Type-Options: nosniff
2. 性能调优
-
服务端优化:
- 连接数限制(Nginx worker_connections)
- 心跳机制保持连接活跃
setInterval(() => {res.write(':ping\n\n'); // 注释行作为心跳 }, 30000);
-
客户端优化:
- 合理设置EventSource并发数
- 及时关闭不需要的连接
SSE扩展方案
1. 断线续传实现
// 服务端记录最后事件ID
let lastId = 0;
app.get('/resumable-stream', (req, res) => {const clientLastId = req.headers['last-event-id'] || 0;// 从clientLastId之后开始发送数据
});
2. 二进制数据传输
// 通过Base64编码传输
const buffer = await getImageBuffer();
res.write(`data: ${buffer.toString('base64')}\n\n`);// 客户端解码
es.onmessage = (e) => {const img = document.createElement('img');img.src = `data:image/png;base64,${e.data}`;
};
总结
SSE 是一种轻量级的服务器推送技术,适用于服务器单向推送数据的场景。它基于 HTTP,简单易用,支持持久连接和自动重连,广泛应用于实时数据更新场景。尽管相比 WebSocket 功能有限,但对于不需要双向通信的应用,SSE 提供了更简单、更高效的解决方案。
如果你的应用场景涉及实时数据推送,而不需要客户端主动发送消息,那么 SSE 可能是一个理想的选择!