本文目录导读:

SSE(Server-Sent Events,服务器推送事件)是一种基于 HTTP 的轻量级技术,允许服务器主动向客户端(通常是浏览器)推送数据,相比 WebSocket,它更简单,且原生支持文本数据。
下面我会从原理、服务端实现(Node.js/Python/Java)、客户端实现和常见问题四个角度详细讲解。
SSE 的工作原理
- 客户端发起请求:客户端通过
EventSourceAPI 向服务器发送一个普通的 HTTP GET 请求。 - 服务器保持连接:服务器收到请求后,将响应头设置为
Content-Type: text/event-stream,并保持连接不关闭。 - 持续推送数据:服务器可以随时向这个打开的连接写入文本数据,格式遵循 SSE 协议(见下文)。
- 客户端接收:浏览器的
EventSource对象自动解析服务器推送的数据流,并触发onmessage或on事件。
关键点:SSE 是单向的(服务器 -> 客户端),如果客户端需要向服务器发送数据,需要使用普通的 HTTP 请求(fetch 或 XHR)。
SSE 数据格式
SSE 的数据是纯文本,每行一个字段,以 \n 换行,主要字段:
event: 事件类型,默认是message。data: 数据内容,可以有多行,以\n拼接。id: 事件 ID,用于断线重连时发送Last-Event-ID头。retry: 重连超时时间(毫秒),告诉浏览器如果连接断开,多久后重试。- 空行(
\n\n):表示一个事件结束。
示例:
event: userupdate
data: {"username": "Alice", "status": "online"}
event: message
data: line1
data: line2
: 这个是注释行,以冒号开头,客户端会忽略
服务端实现(代码示例)
Node.js (使用原生 http 或 Express)
const express = require('express');
const app = express();
// SSE 端点
app.get('/events', (req, res) => {
// 1. 设置 SSE 必需的头
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*' // 如果有跨域
});
// 2. 模拟每隔 2 秒推送一次数据
const intervalId = setInterval(() => {
const data = {
time: new Date().toLocaleTimeString(),
value: Math.random()
};
// 格式:event + data + \n\n
res.write(`event: message\n`);
res.write(`data: ${JSON.stringify(data)}\n\n`);
}, 2000);
// 3. 客户端断开连接时,清理资源
req.on('close', () => {
clearInterval(intervalId);
res.end();
console.log('Client disconnected');
});
});
app.listen(3000, () => console.log('SSE server on http://localhost:3000/events'));
Python (使用 Flask)
from flask import Flask, Response, stream_with_context
import time
app = Flask(__name__)
@app.route('/events')
def sse():
def generate():
while True:
data = {
'time': time.strftime('%H:%M:%S'),
'value': time.time()
}
yield f"event: message\ndata: {data}\n\n"
time.sleep(2)
headers = {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*'
}
return Response(stream_with_context(generate()), headers=headers)
if __name__ == '__main__':
app.run(port=5000, debug=False)
注意:debug=True 会导致 Flask 开发服务器使用线程延迟,可能影响 SSE,生产环境请使用 Gunicorn 等。
Java (Spring Boot)
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import java.time.Duration;
import java.time.LocalTime;
@RestController
public class SSEController {
@GetMapping(value = "/events", produces = "text/event-stream; charset=UTF-8")
public Flux<String> sse() {
return Flux.interval(Duration.ofSeconds(2))
.map(seq -> {
String data = "{\"time\": \"" + LocalTime.now() + "\", \"value\": " + Math.random() + "}";
return "event: message\ndata: " + data + "\n\n";
});
}
}
依赖:需要 Spring WebFlux(spring-boot-starter-webflux)。
客户端实现(浏览器 + JavaScript)
// 1. 创建 EventSource 实例,连接到 SSE 端点
const eventSource = new EventSource('/events');
// 2. 监听 'message' 事件(默认事件类型)
eventSource.onmessage = function(event) {
console.log('Received message:', event.data);
// 解析 JSON 数据
try {
const data = JSON.parse(event.data);
document.getElementById('output').innerHTML += `<div>${data.time} - ${data.value}</div>`;
} catch(e) {
console.error('Parse error', e);
}
};
// 3. 监听自定义事件('userupdate')
eventSource.addEventListener('userupdate', function(event) {
console.log('User update:', event.data);
});
// 4. 错误处理(连接断开或服务器故障)
eventSource.onerror = function(event) {
console.error('EventSource failed:', event);
// 浏览器默认会尝试重连(根据 retry 字段)
};
// 5. 断开连接(例如页面卸载时)
// eventSource.close();
关键点:
new EventSource(url)会自动发送Accept: text/event-stream头。- 客户端不能通过
EventSource向服务器发送自定义请求头或 POST 数据。 - 断线重连是自动进行的,浏览器会记录
Last-Event-ID并在重连时发送给服务器。
常用场景与优缺点
适合场景
- 实时通知:消息推送、系统告警、订单状态更新。
- 实时数据流:股票价格、加密货币、新闻瀑布流。
- 日志实时显示:服务器日志流、监控数据。
- 自动刷新数据:仪表盘、统计面板。
优点
- 简单:基于 HTTP,无需额外协议或库。
- 浏览器原生支持:EventSource API,不需要额外的网络库。
- 自动重连:浏览器自动处理,服务端只需发送
id和retry。 - 与 HTTP/2 完美配合:可以复用同一个连接,无同源限制。
缺点
- 单向通信:客户端不能通过 SSE 发送数据。
- 浏览器限制:每个域名最多 6 个 SSE 连接(HTTP/1.1 下)。
- 仅文本:只能发送文本数据(可发送 JSON 文本),不支持二进制。
- IE 不支持:Edge 支持,但 IE 完全不行,需要 polyfill(如
eventsource-polyfill)。
常见问题与注意事项
-
代理服务器(Nginx 等)缓冲问题:
- Nginx 默认会缓冲响应,导致 SSE 数据延迟推送。
- 需要在 Nginx 配置中关闭缓冲:
location /events { proxy_pass http://backend; proxy_buffering off; # 关闭缓冲 proxy_cache off; # 关闭缓存 chunked_transfer_encoding on; # 开启分块传输 proxy_set_header Connection ''; proxy_http_version 1.1; proxy_redirect off; }
-
性能考虑:
每个客户端需要维护一个长连接,如果客户端数量很大(>5000),建议使用事件总线 + 消息队列,并通过反向代理(如 Nginx)来管理连接。
-
服务器超时:
- 有些代理或负载均衡器会超时空闲连接,使用
retry字段或定期发送心跳(如每 30 秒发送一个 注释行)来保持连接活跃。
- 有些代理或负载均衡器会超时空闲连接,使用
-
同源策略:
- 默认不支持跨域,如果需要跨域,服务器必须设置
Access-Control-Allow-Origin头,并且客户端可能无法在跨域下发送withCredentials(需显式设置)。
- 默认不支持跨域,如果需要跨域,服务器必须设置
与 WebSocket 的对比
| 特性 | SSE | WebSocket |
|---|---|---|
| 协议 | HTTP | 自定义协议(ws/wss) |
| 通信方向 | 单向(服务器->客户端) | 双向 |
| 数据类型 | 文本 | 文本 + 二进制 |
| 浏览器兼容性 | 大部分现代浏览器(IE 除外) | 所有现代浏览器 |
| 自动重连 | 内置 | 需要自己实现 |
| 简单性 | 非常易用 | 稍复杂(握手、帧协议) |
选择建议:
- 只需要服务器推送数据:选 SSE(更简单)。
- 需要实时双向通信(如聊天、游戏):选 WebSocket。
- 需要传输二进制数据(如文件、音频流):选 WebSocket。
- 需要与现有 HTTP 基础设施集成:选 SSE。
标签: Sent Events) 服务端推送