一.SSE的应用场景

场景
经常使用大模型AI的小伙伴应该不难发现,AI的回答都是一个字一个字蹦出来的,而不是整段文字直接出现。对大模型并不了解的小伙伴可能会误以为这是前端的动画效果。
实际上,大模型的回复本身是逐词( 严格来说是逐token) 生成的,如果等全部生成完再返回,用户会明显感到卡顿。所以在实际部署和应用中,大模型问答应用通常采用流式(streaming)方式生成回答,也就是所谓的“一个字一个字蹦出来”。

解决方式
既然流式文本输出并非前端特效,那么要实现这样的效果,自然就要用到实时通讯了


在常见的实时通讯方式中,我们首先排除HTTP 轮询,用几十上百次请求得到一个回答,显然服务器压力巨大且效率极低。


那么WebSocket似乎是一个可选的方案?但大模型的回复主要还是单向的。服务器(AI模型)源源不断地向客户端(你的浏览器/App)推送生成出来的文本片段(tokens)。客户端只需要接收,不需要实时向服务器发送数据。那么WebSocket 提供的“双向实时发送”能力显然是过剩的。


那么有没有恰到好处的通讯方式呢?有的兄弟,有的。
SSE就非常适合文本的流式输出这样需要服务器单向的,持续不断推送数据的场景。

二.SSE是什么

SSE 是一种基于 HTTP 的“服务器单向广播”技术,允许服务器通过一次长连接,持续不断地向客户端实时推送数据。

🔘基于 HTTP:不是新协议,只是 HTTP 的一种特殊用法(Content-Type: text/event-stream)。
🔘单向推送:连接建立后,只有服务器能发,客户端只能收(类似“只读电台”)。
🔘长连接流式推送:一次连接,服务器可以源源不断发送消息,直到任务完成或连接中断。

核心特点(与轮询、WebSocket 对比)

✅ 比轮询更高效:不用客户端反复“问”,服务器有消息直接推,延迟低、浪费少。
✅ 比 WebSocket 更简单:不用双向通信,不涉及复杂协议切换,兼容性好。
✅ 适合场景:实时通知、消息流、大模型逐字生成等“服务器主动推,客户端被动看”的需求。

三.使用Spring boot提供的SseEmitter对象实现SSE接口

核心依赖,确保你的 pom.xml中包含了 Spring Web 依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

控制层代码:

@RestController
@RequestMapping("/api/sse")
public class SseController {

    // 存储活跃的 SSE 连接(可选,用于广播等高级场景)
    private final List<SseEmitter> emitters = new CopyOnWriteArrayList<>();

    /**
     * 创建 SSE 连接,并开始流式生成内容
     */
    @GetMapping("/stream")
    public SseEmitter streamResponse(@RequestParam String question) {
        // 创建一个 SseEmitter,可设置超时时间(默认 30 秒,设为 0 表示永不超时)
        SseEmitter emitter = new SseEmitter(0L); // 0 表示不超时

        // 可选:将 emitter 加入列表,方便后续管理
        emitters.add(emitter);

        // 模拟大模型逐字生成
        simulateStreamingGeneration(emitter, question);

        // 返回 emitter,Spring 会自动管理连接
        return emitter;
    }

    /**
     * 模拟流式生成:逐字发送回答
     */
    private void simulateStreamingGeneration(SseEmitter emitter, String question) {
        // 在子线程中执行,避免阻塞 HTTP 请求线程
        CompletableFuture.runAsync(() -> {
            try {
                // 模拟生成回答
                String answer = "你好,我是AI助手。你问的是:" + question + "。这是一个SSE流式响应的示例。";

                // 逐字发送
                for (int i = 0; i < answer.length(); i++) {
                    String token = String.valueOf(answer.charAt(i));

                    // 发送一个 SSE 事件
                    // 格式为:data: {内容}\n\n
                    emitter.send(SseEmitter.event()
                            .data(token) // 发送的内容
                            .name("token") // 事件名称(可选)
                            .id(String.valueOf(i)) // 事件 ID(可选,用于断线重连)
                    );

                    // 模拟生成延迟(每 100ms 发一个字)
                    Thread.sleep(100);
                }

                // 发送结束标记
                emitter.send(SseEmitter.event()
                        .data("[DONE]")
                        .name("complete")
                );

                // 正常完成,关闭连接
                emitter.complete();

            } catch (Exception e) {
                // 发生错误,关闭连接并发送错误信息
                emitter.completeWithError(e);
            } finally {
                // 清理:从列表中移除 emitter
                emitters.remove(emitter);
            }
        });
    }
}
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐