Skip to content

SSE的使用

约 1465 字大约 5 分钟

SSE

2025-06-04

SSE基本概念

一、什么是 SSE?

SSE(Server-Sent Events,服务器发送事件)是一种用于服务器向客户端实时推送数据的技术。它最早出现在 HTML5 规范中,并在 2014 年随 HTML5 成为 W3C 推荐标准。MDN WEB DOCS

SSE 基于 HTTP 协议,通过长连接实现服务器到客户端的单向通信,适用于需要持续接收更新的应用场景,如股票行情、实时通知、聊天系统等。


二、SSE 的特点

特点描述
基于 HTTP使用标准的 HTTP 协议进行通信,易于部署和调试。
单向通信数据仅从服务器推送到客户端,不支持客户端主动发送消息。
轻量级传输的是文本格式的数据(通常是 UTF-8),无需复杂的编解码过程。
自动重连机制如果连接中断,浏览器会自动尝试重新连接。
事件类型支持支持自定义事件类型,便于客户端区分不同类型的消息。
断线续传标识符提供 id 字段用于记录事件位置,方便断线后恢复。

三、SSE 工作原理

1. 客户端发起请求

客户端通过 JavaScript 创建 EventSource 对象,向服务器发起一个 HTTP GET 请求:

const eventSource = new EventSource('http://example.com/sse');

2. 服务器响应并保持连接

服务器接收到请求后,不会立即关闭连接,而是以 text/event-stream 类型返回响应,并持续推送事件流数据:

HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

data: Hello, world!

3. 客户端监听事件

客户端可以监听以下主要事件:

  • onmessage:默认事件处理函数,当没有指定事件名时触发。
  • onopen:连接建立时触发。
  • onerror:连接出错或中断时触发。
  • onclose:连接关闭时手动调用 close() 方法触发。
eventSource.onopen = () => {
    console.log('连接已打开');
};

eventSource.onmessage = (event) => {
    console.log('收到消息:', event.data);
};

eventSource.onerror = (err) => {
    console.error('发生错误:', err);
};

四、SSE 消息格式规范

每个事件流由多个字段组成,每行以 \n 分隔,不同字段之间也以空行分隔。常见字段如下:

字段含义
data必填项,表示要发送的数据,可以是多行。
event可选,指定事件名称,客户端通过 addEventListener 监听。
id可选,事件 ID,用于断线重连时恢复上次的位置。
retry可选,指定自动重连间隔时间(单位:毫秒)。
id: 12345
event: update
data: {"type": "stock", "price": 102.5}
data: More data here.

retry: 5000
data: This is a message with multiple lines.

五、自定义事件监听

除了 onmessage,还可以使用 addEventListener 来监听特定事件类型:

eventSource.addEventListener('update', (event) => {
    console.log('收到 update 类型消息:', event.data);
});

对应的服务端响应示例:

event: update
data: 更新内容

六、SSE 的优缺点

✅ 优点

  • 简单易用,基于 HTTP,兼容性好。
  • 自动重连,简化客户端逻辑。
  • 轻量级,适合文本数据推送。
  • 支持事件分类,便于消息处理。

❌ 缺点

  • 非全双工通信:只能服务器向客户端推送,无法双向交互。
  • 不支持二进制数据:传输数据必须为文本格式。
  • 资源占用较高:需维持长连接,对服务器性能有一定要求。
  • 浏览器兼容性限制:IE 不支持,移动端主流浏览器基本支持(iOS Safari、Android Chrome 等)。

七、适用场景

  • 实时新闻推送
  • 股票行情更新
  • 在线聊天应用的通知层
  • 实时日志查看
  • 游戏排行榜更新

八、与 WebSocket 的对比

功能SSEWebSocket
协议HTTP自定义协议(通常基于 TCP)
通信方向单向(服务器 → 客户端)双向
数据格式文本文本或二进制
是否自动重连否(需自行实现)
浏览器兼容性较好(不支持 IE)良好(现代浏览器均支持)
实现复杂度简单复杂
性能开销较低较高

九、注意事项与优化建议

  • 避免缓存:设置 Cache-Control: no-cache 防止中间缓存干扰。
  • 设置合适的超时时间:防止连接长时间无效占用资源。
  • 合理控制并发连接数:SSE 连接占用服务器资源,注意负载均衡。
  • 使用压缩(可选):可开启 GZIP 减少带宽消耗。
  • 断线续传:利用 id 字段实现断线后继续获取未接收的消息。
  • 安全措施:确保接口权限控制,避免被滥用。

十、总结

SSE 是一种轻量级的服务器推送技术,特别适合只需要单向通信的实时应用场景。相比 WebSocket,SSE 更加简单易用,且兼容性较好,但在功能上有所局限。应根据业务需求选择合适的技术方案。

SpringBoot使用SSE

1. 返回Flux响应式对象,添加SSE响应MediaType

@GetMapping(value = "/chat/sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chatSSEByFlux() {
    return Flux.create(sink -> {
        for (int i = 1; i <= 30; i++) {
            sink.next("第" + i + "次");
            sink.next("hello");
            sink.next("world");
        }
        sink.complete();
    });
}
image-20250604095619517
image-20250604095619517

2. 返回Flux对象

@GetMapping(value = "/chat/sse/noMT")
public Flux<ServerSentEvent<String>> chatSSEByFluxTrunk() {
    return Flux.create(sink -> {
        // 模拟分段发送的虚拟内容
        sink.next(ServerSentEvent.<String>builder()
                .data("Hello, this is the first message.")
                .build());

        sink.next(ServerSentEvent.<String>builder()
                .data("This is a second virtual chunk of data.")
                .build());

        sink.next(ServerSentEvent.<String>builder()
                .data("Streaming content to client...")
                .build());

        // 结束流
        sink.complete();
    });
}
image-20250604100237189
image-20250604100237189

3. 使用Emitter

@GetMapping("/chat/sse/emitter")
public SseEmitter simulateSseEmitter() {
    // 设置超时时间为 3 分钟
    SseEmitter emitter = new SseEmitter(180000L);
    // 模拟异步发送虚拟内容
    new Thread(() -> {
        try {
            // 发送多条消息
            emitter.send("Hello, this is the first message.");
            Thread.sleep(1000); // 模拟延迟
            emitter.send("This is a second virtual chunk of data.");
            Thread.sleep(1000);
            emitter.send("Streaming content to client...");
            Thread.sleep(1000);
            // 完成流
            emitter.complete();
        } catch (IOException | InterruptedException e) {
            emitter.completeWithError(e);
        }
    }).start();
    return emitter;
}
image-20250604100907243
image-20250604100907243

贡献者

  • flycodeuflycodeu

公告板

2025-03-04正式迁移知识库到此项目