本文目录导读:

SSE (Server-Sent Events) 是一种允许服务器主动向客户端推送数据的技术,与 WebSocket 不同,SSE 是单向的(服务器到客户端),且基于 HTTP 协议。
以下是 SSE 的核心机制、实现示例及适用场景:
SSE 的核心特点
- 单向通信:数据只能从服务器流向客户端。
- 基于 HTTP:使用标准的 HTTP 协议,无需复杂的握手(如 WebSocket)。
- 自动重连:如果连接断开,浏览器会自动尝试重新连接(默认重试间隔 3 秒)。
- 文本传输:只能发送文本数据(UTF-8 编码),如果需要发送二进制数据,需要先编码为 Base64 或其他文本格式。
- 简单易用:客户端使用
EventSourceAPI,服务端返回text/event-stream格式的响应。
SSE 的工作原理
- 客户端请求:浏览器发起一个 GET 请求,并声明
Accept: text/event-stream。 - 服务端响应:服务端设置响应头
Content-Type: text/event-stream,并保持连接打开(不关闭)。 - 数据推送:服务端定期或不定期地向客户端发送格式化的文本消息。
- 客户端接收:浏览器通过
EventSource对象监听message或自定义事件。
完整实现示例
客户端(HTML + JavaScript)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">SSE 示例</title>
</head>
<body>
<h1>服务端推送消息</h1>
<div id="messages"></div>
<script>
// 创建 EventSource 对象,连接到服务端 SSE 端点
// 注意:如果跨域,需要设置 withCredentials: true 并服务端允许
const eventSource = new EventSource('/events');
// 监听默认的 message 事件(没有指定事件名称的消息)
eventSource.onmessage = function(event) {
const messagesDiv = document.getElementById('messages');
const newMessage = document.createElement('p');
newMessage.textContent = `收到消息: ${event.data}`;
messagesDiv.appendChild(newMessage);
};
// 监听自定义事件,"customEvent"
eventSource.addEventListener('customEvent', function(event) {
console.log('自定义事件:', event.data);
});
// 监听连接打开
eventSource.onopen = function() {
console.log('SSE 连接已建立');
};
// 监听错误(包括连接断开)
eventSource.onerror = function() {
console.error('SSE 连接出错');
// EventSource 会自动重连
};
// 关闭连接(例如页面离开时的清理)
// window.onbeforeunload = function() {
// eventSource.close();
// };
</script>
</body>
</html>
服务端(Node.js + Express 示例)
const express = require('express');
const app = express();
const PORT = 3000;
app.get('/events', (req, res) => {
// 设置 SSE 必需的响应头
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*' // 允许跨域
});
// 发送初始连接成功消息
res.write('data: 已连接\n\n');
// 每 2 秒发送一条消息
const intervalId = setInterval(() => {
const data = new Date().toLocaleTimeString();
res.write(`data: ${data}\n\n`);
// res.flushHeaders(); // 如果需要立即刷新缓冲区(某些框架需要)
}, 2000);
// 当客户端断开连接时,清理定时器
req.on('close', () => {
clearInterval(intervalId);
console.log('客户端断开连接');
res.end();
});
});
app.listen(PORT, () => {
console.log(`SSE 服务运行在 http://localhost:${PORT}`);
});
SSE 消息格式标准
服务端发送的消息行必须以 text/event-stream 格式,每条消息由多个字段组成:
field: value\n
常见字段:
data(必须,可以有多行)。event:事件名称(可选,客户端addEventListener监听)。id:消息 ID(可选,客户端自动设置Last-Event-ID以实现断线重连)。retry:重连时间(毫秒,可选)。
消息之间用两个换行符 \n\n 分隔。
示例:
event: userLogin
id: 12345
data: {"username": "张三", "time": "14:30:00"}
SSE 的优缺点
| 优点 | 缺点 |
|---|---|
简单易用:客户端只需 EventSource API。 |
单向通信:只能服务端推送,客户端无法发送数据(需结合 AJAX)。 |
| 自动重连:内置断线重连机制。 | 连接数限制:浏览器限制每个域名最多 6-8 个 SSE 连接。 |
| HTTP 兼容:走标准 HTTP 端口 80/443,防火墙友好。 | 文本传输:不支持二进制数据(除非编码)。 |
| 实时性较好:延迟一般在 1-3 秒内。 | 资源消耗:服务端需要保持大量长连接(可以使用 HTTP/2 提升效率)。 |
SSE vs WebSocket 对比
| 特性 | SSE | WebSocket |
|---|---|---|
| 通信方向 | 单向(服务端 → 客户端) | 双向(全双工) |
| 协议 | HTTP | WebSocket(非 HTTP) |
| 复杂性 | 简单 | 较复杂 |
| 自动重连 | 内置 | 需手动实现 |
| 二进制数据 | 需编码 | 原生支持 |
| 浏览器支持 | 良好(除 IE) | 广泛 |
| 应用场景 | 实时通知、日志流、股票价格 | 聊天、游戏、协同编辑 |
典型应用场景
- 实时通知系统:新订单提醒、系统告警。
- 股票/加密货币价格更新:低延迟推送行情。
- 日志流:持续推送服务器日志到前端。
- 社交媒体时间线:新帖子、新消息推送。
- 进度更新:文件上传/下载进度、任务执行状态。
注意事项
- 跨域问题:如果服务端和客户端不同域,需在服务端设置
Access-Control-Allow-Origin。 - 负载均衡:SSE 是长连接,负载均衡器需要配置
sticky session(会话保持),或使用 Redis 等中间件共享状态。 - 资源释放:服务端务必监听
req.on('close')事件,清理定时器和内存。 - 浏览器兼容性:IE 不支持,推荐使用 EventSource Polyfill 或降级方案(如轮询)。
高级用法:使用 EventID 实现断线重连
// 服务端发送带 ID 的消息
res.write(`id: ${Date.now()}\ndata: 消息内容\n\n`);
// 客户端自动携带 Last-Event-ID 重连
// 浏览器内部自动处理,无需额外代码
如果需要在重连时从最后一条消息开始,服务端可以读取请求头 Last-Event-ID:
const lastId = req.headers['last-event-id']; // 从 lastId 之后继续推送
SSE 是实现服务端单向推送最简单、最轻量的方案,如果你的需求只是服务端主动推送数据给客户端,且不需要客户端频繁发送数据回服务端(比如实时通知、新闻推送、系统状态更新),SSE 是比 WebSocket 更合适的选择。