前言
SSE(Server-Sent Events)是一种允许服务器向浏览器单向、持续推送数据的技术。
它的核心特点是 服务器推 。与传统的客户端请求、服务器响应的模式不同,SSE 建立连接后,服务器可以主动、多次地向客户端发送数据,直到连接关闭。
一、SSE 是什么?有什么作用?
SSE 基于 HTTP 协议,使用简单,天生支持断线重连和事件标识 ,非常适合用于服务器向客户端单向推送实时更新的场景。
主要作用和应用场景:
- 实时通知:如新消息提醒、订单状态更新。
- 实时数据展示:如股票行情、体育比分、监控数据仪表盘。
- AI 流式响应:这正是你之前代码处理的场景。大模型可以一边生成文本,一边通过 SSE 将结果片段推送给前端,实现"打字机"效果,用户体验极佳。
- 日志实时输出:实时展示后台任务的执行日志。
与 WebSocket 的对比:
| 特性 | SSE (Server-Sent Events) | WebSocket |
|---|---|---|
| 通信模式 | 单向 (服务器 → 客户端) | 双向 (服务器 ⇌ 客户端) |
| 协议 | 基于 HTTP/HTTPS | 独立的 WS/WSS 协议 |
| 复杂度 | 简单,API 友好 | 相对复杂,需要处理连接、心跳等 |
| 适用场景 | 服务器推送为主,如通知、流式数据 | 需要高频双向交互,如在线游戏、聊天室 |
简单来说,如果只需要服务器向客户端推送数据,SSE 是更轻量、更简单的选择。
二、后端如何使用 (Spring Boot)
在 Spring Boot 中,实现 SSE 非常简单,核心是返回 SseEmitter 对象。
1. 创建 SSE 接口
创建一个 Controller,提供一个接口来建立 SSE 连接。
java
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@RestController
public class SseController {
// 使用线程池来异步发送数据,避免阻塞主线程
private final ExecutorService executorService = Executors.newCachedThreadPool();
@GetMapping(value = "/api/sse/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter stream() {
// 1. 创建 SseEmitter 实例
// 参数是超时时间,0L 表示永不过期
SseEmitter emitter = new SseEmitter(0L);
// 2. 在子线程中模拟数据推送
executorService.execute(() -> {
try {
for (int i = 0; i < 10; i++) {
// 3. 发送数据
// 数据会被自动包装成 "data: {...}\n\n" 的格式
emitter.send(SseEmitter.event()
.name("message") // 可选:事件名称
.data("这是第 " + i + " 条消息"));
// 模拟耗时操作
Thread.sleep(1000);
}
// 4. 发送完成信号
emitter.complete();
} catch (IOException | InterruptedException e) {
// 5. 发生异常时,通知客户端连接出错
emitter.completeWithError(e);
}
});
return emitter;
}
}
代码解析:
@GetMapping(..., produces = MediaType.TEXT_EVENT_STREAM_VALUE):TEXT_EVENT_STREAM_VALUE的值是text/event-stream,这是 SSE 协议要求的 MIME 类型,告诉浏览器这是一个持续的数据流。
SseEmitter emitter = new SseEmitter(0L):- 创建一个
SseEmitter对象,它代表了与客户端的 SSE 连接。 - 构造函数中的
0L表示连接永不超时。默认是 30 秒,超时后连接会自动断开。
- 创建一个
emitter.send(...):- 这是推送数据的核心方法。你可以发送字符串、对象(会被自动序列化为 JSON)等。
SseEmitter.event()是一个构建器,可以方便地设置事件名(name)、数据(data)和重连时间(reconnectTime)。
emitter.complete():- 数据发送完毕后,调用此方法正常关闭连接。
emitter.completeWithError(e):- 如果发生异常,调用此方法关闭连接并通知客户端。
2. 处理连接生命周期(可选但推荐)
你可以为 SseEmitter 添加回调,来处理连接超时、完成或出错的情况,便于进行资源清理或日志记录。
java
// 在创建 emitter 后添加
emitter.onCompletion(() -> System.out.println("SSE 连接已完成"));
emitter.onTimeout(() -> {
System.out.println("SSE 连接已超时");
emitter.complete(); // 超时后手动关闭
});
emitter.onError((e) -> System.out.println("SSE 连接发生错误: " + e.getMessage()));
3. 广播消息(进阶)
如果需要向多个客户端推送同一条消息(如公告),可以使用一个集合来管理所有的 SseEmitter 实例。
java
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
// ...
@RestController
public class BroadcastController {
// 使用线程安全的集合存储所有活跃的 emitter
private final Set<SseEmitter> emitters = ConcurrentHashMap.newKeySet();
@GetMapping("/api/sse/subscribe")
public SseEmitter subscribe() {
SseEmitter emitter = new SseEmitter(0L);
emitters.add(emitter);
// 连接关闭时,从集合中移除
emitter.onCompletion(() -> emitters.remove(emitter));
emitter.onTimeout(() -> emitters.remove(emitter));
emitter.onError((e) -> emitters.remove(emitter));
return emitter;
}
// 模拟一个推送公告的接口
@GetMapping("/api/admin/announce")
public String announce(String message) {
// 遍历所有 emitter,向每个客户端发送消息
emitters.removeIf(emitter -> {
try {
emitter.send(message);
return false; // 保留在集合中
} catch (Exception e) {
return true; // 发送失败,从集合中移除
}
});
return "公告已推送";
}
}
三、总结
SSE 是一个轻量级、基于 HTTP 的服务器推送技术,非常适合用于实时更新、通知和流式数据等场景。在 Spring Boot 中,通过 SseEmitter 可以非常方便地实现 SSE 功能,是处理服务器到客户端单向通信的绝佳选择。