Server-Sent Events (SSE) 技术解析与实战

服务器 服务器产品
Server-Sent Events是一种允许服务器向客户端推送实时事件的技术。与传统的HTTP请求 - 响应模式不同,SSE建立的是一个持久的HTTP连接,服务器可以在任何时候通过这个连接向客户端推送数据。

前言

在当今互联网应用中,实时数据交互已成为关键需求。从AI聊天机器人到实时通知系统,从股票行情更新到协作编辑工具,都需要服务器能够主动向客户端推送数据。Server-Sent Events作为一种基于HTTP的单向服务器推送技术,在这些场景中展现出独特的优势。

SSE特别适合以下场景:

  • AI聊天机器人:服务器逐步返回AI生成的回答
  • 实时通知系统:新消息、提醒等通知推送
  • 日志监控:实时展示系统日志输出
  • 数据更新:股票价格、天气信息等实时更新

效果图

图片图片

基础概念

Server-Sent Events是一种允许服务器向客户端推送实时事件的技术。与传统的HTTP请求 - 响应模式不同,SSE建立的是一个持久的HTTP连接,服务器可以在任何时候通过这个连接向客户端推送数据。

SSE具有以下特点:

  • 基于标准HTTP协议,无需额外协议支持
  • 单向通信:仅服务器向客户端推送数据
  • 轻量级:相比WebSocket,实现更简单
  • 支持自动重连:连接断开时客户端可自动尝试重新连接
  • 支持事件类型:可以推送不同类型的事件

工作原理

SSE的工作流程如下:

客户端                    服务端
   |                        |
   |-- GET /events -------->|  建立连接
   |<-- 200 OK -------------|  返回事件流
   |<-- data: message1 -----|  推送消息1
   |<-- data: message2 -----|  推送消息2
   |<-- ...              ---|  持续推送
  • 客户端通过EventSource API向服务器发起请求
  • 服务器建立连接并保持该连接打开
  • 服务器可以随时通过该连接向客户端推送事件数据
  • 当连接断开时,客户端会自动尝试重新连接
  • 服务器可以通过特定响应关闭连接

SSE数据格式采用简单的文本格式,使用data:前缀表示数据内容,event:前缀表示事件类型,例如:

event: message
data: Hello, this is a server-sent event!

data: This is another message without event type

SSE 与其他实时技术的对比

技术

连接类型

双向通信

实现复杂度

浏览器支持

适用场景

SSE

单向 HTTP

良好

服务器主动推送,单向数据流

WebSocket

双向 TCP

良好

双向实时通信,高交互场景

轮询

多次 HTTP

良好

简单场景,对实时性要求不高

长轮询

单次 HTTP

良好

服务器主动推送,中等实时性要求

实现 SSE

创建 SSE 控制器

以下使用预设的中文回复示例,实际应用中这里会调用AI API

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

    // 存储所有活跃的SSE连接
    private final Map<String, SseEmitter> clients = new HashMap<>();
    // 用于处理消息生成的线程池
    private final ExecutorService executor = Executors.newFixedThreadPool(10);
    // 消息计数器,用于生成唯一消息ID
    private final AtomicInteger messageCounter = new AtomicInteger(0);

    // 超时时间设置为30分钟,避免频繁断开重连
    private static final long TIMEOUT = 30 * 60 * 1000L;

    @GetMapping("/connect/{clientId}")
    public SseEmitter connect(@PathVariable String clientId) {
        // 检查是否已有相同客户端ID的连接
        if (clients.containsKey(clientId)) {
            clients.get(clientId).complete();
        }

        // 创建新的SseEmitter实例
        SseEmitter emitter = new SseEmitter(TIMEOUT);

        // 添加心跳机制,每20秒发送一次心跳消息
        executor.submit(() -> {
            try {
                while (true) {
                    Thread.sleep(20000);
                    if (emitter != null && clients.containsKey(clientId)) {
                        emitter.send(SseEmitter.event().name("heartbeat").data("ping"));
                    } else {
                        break;
                    }
                }
            } catch (Exception e) {
                emitter.completeWithError(e);
            }
        });

        emitter.onTimeout(() -> {
            System.out.println("客户端连接超时: " + clientId);
            clients.remove(clientId);
            emitter.complete();
        });

        emitter.onError(e -> {
            System.out.println("客户端连接错误: " + clientId + ", 错误: " + e.getMessage());
            clients.remove(clientId);
            emitter.completeWithError(e);
        });

        // 注册完成事件处理器
        emitter.onCompletion(() -> {
            System.out.println("SSE处理完成: " + clientId);
            clients.remove(clientId);
        });
        clients.put(clientId, emitter);
        sendSystemMessage(emitter, "连接已建立,您可以开始与AI助手聊天了。");
        return emitter;
    }

    @GetMapping("/chat/{clientId}/{message}")
    public void sendChatMessage(@PathVariable String clientId, @PathVariable String message) {
        SseEmitter emitter = clients.get(clientId);
        if (emitter == null) {
            return;
        }

        // 在单独线程中处理消息,避免阻塞主线程
        executor.submit(() -> {
            try {
                String response = generateAiResponse(message);
                int messageId = messageCounter.incrementAndGet();

                // 发送消息开始事件
                emitter.send(SseEmitter.event()
                        .name("messageStart")
                        .data(MapUtil.builder()
                                .put("id", messageId)
                                .put("clientId", clientId)
                                .build()));

                // 模拟AI逐步生成响应
                String[] parts = response.split("(?<=。|!|?|\\.|!|\\?)");
                for (String part : parts) {
                    if (part.trim().isEmpty()) continue;

                    // 发送消息片段
                    emitter.send(SseEmitter.event()
                            .name("messageFragment")
                            .data(MapUtil.builder()
                                    .put("id", messageId)
                                    .put("content", part)
                                    .put("isLast", false)
                                    .build()));

                    // 模拟思考时间
                    Thread.sleep(500 + (long)(Math.random() * 1000));
                }

                // 发送消息结束事件
                emitter.send(SseEmitter.event()
                        .name("messageEnd")
                        .data(MapUtil.builder()
                                .put("id", messageId)
                                .put("clientId", clientId)
                                .build()));

            } catch (Exception e) {
                try {
                    emitter.send(SseEmitter.event()
                            .name("error")
                            .data("生成AI回复时出错: " + e.getMessage()));
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        });
    }

    @PostMapping("/ack/{clientId}/{messageId}")
    public void acknowledgeMessage(
            @PathVariable String clientId,
            @PathVariable int messageId
    ) {
        System.out.println("收到消息确认: " + messageId + " 来自客户端: " + clientId);
        // 这里可以记录消息已确认,进行相应处理
    }

    @GetMapping("/disconnect/{clientId}")
    public void disconnect(@PathVariable String clientId) {
        SseEmitter emitter = clients.get(clientId);
        if (emitter != null) {
            clients.remove(clientId);
            emitter.complete();
        }
    }

    private void sendSystemMessage(SseEmitter emitter, String content) {
        try {
            emitter.send(SseEmitter.event()
                    .name("systemMessage")
                    .data(MapUtil.of("content", content)));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private String generateAiResponse(String prompt) {
        // 实际应用中这里会调用AI API
        // 这里使用预设的中文回复示例
        String lowerCasePrompt = prompt.toLowerCase();

        if (lowerCasePrompt.contains("你好") || lowerCasePrompt.contains("hi") || lowerCasePrompt.contains("hello")) {
            return"你好!我是AI助手,很高兴为你服务。";
        } elseif (lowerCasePrompt.contains("一安") || lowerCasePrompt.contains("一安未来")) {
            return"一安未来公众号致力于Java,大数据;心得交流,技术分享;";
        } else {
            return"很有意思的问题!让我思考一下... " + prompt + " 是一个很好的话题。我可以从多个角度为你分析和解答,你是否想了解更多相关信息?";
        }
    }
}

配置 CORS

@Configuration
public class CorsConfig {

    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOriginPattern("*"); // 允许所有域名进行跨域调用
        config.addAllowedHeader("*"); // 允许任何请求头
        config.addAllowedMethod("*"); // 允许任何方法(POST、GET等)
        config.setAllowCredentials(true); // 允许携带凭证

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config); // 对所有接口都有效

        return new CorsFilter(source);
    }
}

责任编辑:武晓燕 来源: 一安未来
相关推荐

2024-02-23 14:57:40

2024-04-01 00:05:00

ChatGPTSSE

2020-07-04 10:41:32

MQTTSSE网络协议

2011-09-13 10:17:26

PhoneGap AP

2010-08-20 11:18:49

Exchange Se

2024-03-12 09:50:27

Raft协议KRaft

2024-04-12 12:22:39

前端开发网络请求

2024-09-29 08:00:00

动态代理RPC架构微服务架构

2025-02-17 09:32:18

2025-05-20 08:30:00

2023-11-03 13:41:16

数据技术

2024-09-19 08:08:25

2024-09-19 08:49:13

2009-07-07 23:14:00

高可用性SQL Server

2018-04-23 11:11:52

数据挖掘机器学习Python

2019-11-21 14:01:37

Python数据挖掘机器学习

2023-12-14 13:28:00

Spring流程Web

2025-06-03 04:10:00

2021-06-02 07:15:57

Locust测试工具

2011-12-20 17:15:52

PhoneGap APEvents
点赞
收藏

51CTO技术栈公众号