SSE 技术
· 阅读需 11 分钟
基本知识
Server-Sent Events, 是一种基于 HTTP 协议的服务器推送技术. 只允许从服务端到客户端的单向推送.
- 单向(服务端->客户端)
- 自动重连
- 基于 HTTP 协议
- 浏览器支持比较好
使用场景
- 实时通知系统
- 实时更新的动态数据
- 数据流展示
- 数据更新频繁
- 低延迟单向通信
- ChatGPT 的流式输出
简单代码
JS 通过 EventSource 对象与服务端建立连接
- new EventSource(url)
- onmessage
- onopen
- onerror
- close
const url = "xxxx";
// 1. 建立连接
const eventSource = new EventSource(url);
// 2. 消息事件
eventSource.addEventListener("message", this.handleMessage); // 还可以使用 onmessage 方法
// 3. 错误事件
eventSource.addEventListener("error", this.handleError); // 还可以使用 onerror 方法
// 4. 关闭 SSE 连接
eventSource.close();
@GetMapping(path = "/stream-flux", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<MessageData>> streamFlux(@RequestParam("id") String id) {
return Flux.interval(Duration.ofSeconds(1))
.map(sequence ->
ServerSentEvent.<MessageData>builder()
.id(id) // id
.event("periodic-event") // 事件
.retry(Duration.ofSeconds(30)) // 重连时间
.data(new MessageData( // 自定义数据
UUID.randomUUID().toString(),
"heartbeat",
LocalDateTime.now(),
Map.of("clientId", UUID.randomUUID(),
"status", "connected"
)))
.build()
)
.take(1)
;
}
基本原理
响应头
服务器会设置三个响应头:
Context-Type: text/event-stream; charset=utf-8:响应的内容类型是事件流Cache-Control: no-cache:禁止缓存响应内容,确保客户端每次都能接收到最新的数据。Connection: keep-alive保持 HTTP 连接打开, 以便服务器可以持续向客户端推送数据。
如果使用到了 Nginx, 一般还会加上 x-accel-buffering: no, 让 Nginx 不缓冲后端服务器的响应
SSE 报文
服务端响应报文:
: 注释\n
id: xxx\n
retry: 5000\n
event:message\n
data: xxxx\n
\n
- 每行结束都要有一个
\n :注释id: 消息 idretry: 设置断线重连等待的时间event: 设置事件名data: 数据- 空行表示消息结束
SSE 与 Websocket 对比
- ws 是双向的, 因此长连接情况下 ws 数据传输效率会比 SSE 高.
- 相对于 SSE 的 HTTP 协议, ws 协议的开销更大, 实现起来也更复杂.
- ws 支持传输二进制, SSE 只支持 UTF-8 文本(二进制类格式可以通过 base64 编码成文本进行传输)
- SSE 内置自动重连机制, ws 需要手动实现重连和心跳机制.
自动重连机制
当 HTTP 长连接因网络波动, 服务器重启或者其他异常断开时, EventSource 会自动在一段固定的间隔(未设置 retry 时,默认是 3000ms)后重新发起连接请求。
重连等待时间: 可以通过服务端响应的事件流设置 retry 字段告知客户端下次重连等待的时间。 如 retry: 10000, 等待 10s 再重新连接.
断点续传:Last-Event-ID, 如果发生了重连, 浏览器会把最近接收到的 id 通过 Last-Event-ID 请求头传给服务端. 服务端可以根据这个字段续传, 避免数据丢失.
异常处理,EventSource.onerror 可以监听异常事件。
断线重连的几种情况
- EventSource 自动重连
const source = new EventSource(url);
在出现异常情况之后, 客户端会自动重连.
下面几种情况, 浏览器端在连接断开后会重新建立连接请求。
- 网络故障
- 服务器崩溃
- 连接超时
- 服务器主动断开(调用了
emitter.complete())
SSE 会无限次重连,直到成功或被手动关闭。
不会重连的情况
- 客户端手动调用 eventSource.close()
- 服务器返回 HTTP 204 No Content
- 服务器返回非 text/event-stream 的 Content-Type
- 服务区返回 HTTP 错误码(204, 401,403,404 等)
错误处理, 不同类型的错误需要使用不同的处理策略
- 暂时性错误(重连)
- 致命错误(需要向用户报告)
- 服务端控制重连的时机
服务端通过设置 retry 来控制客户端在断连之后等待重连的时间.
- 异常情况 下的手动重连
通过监听 EventSource.onerror 事件来实现断线重连机制。
const source = new EventSource(url);
// 在 onerror 中实现断线重连
source.onerror = function (event) {
setTimeout(() => {
source = new EventSource(url);
}, 1000);
};
- 服务端主动要求客户端重连
服务器端可以通过发送特定事件(如 reconnect )来要求客户端重新连接。
// 服务器端设置重连延迟
emitter.send(SseEmitter.event()
.reconnectTime(5000L) // 5秒后重连
.data("数据"));
上面代码对应的报文
retry: 5000\n
data: 数据\n
\n
// 客户端
eventSource.addEventListener("reconnect", function (e) {
console.log("收到重连指令:", e.data);
eventSource.close(); // 关闭当前连接
setTimeout(() => {
connect(); // 重新连接
}, 5000);
});