Spring Boot 整合 SSE(Server Sent Event)

本文介绍了一种基于HTTP的Server-SentEvents(SSE)技术,用于实现服务器到客户端的单向消息推送。后端代码展示了如何使用Spring的SseEmitter创建和管理连接,以及发送和断开消息流。前端代码展示了如何使用EventSource进行连接并接收消息。SSE连接需要发送空消息以避免浏览器的加载状态,同时管理超时和错误回调。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

服务器发送事件(Server-Sent Events),简称 SSE。这是一种服务器端到客户端的单向消息推送。SSE 基于 HTTP 协议的,SSE 在服务器和客户端之间打开一个单向通道,服务端响应的不再是一次性的数据包而是text/event-stream类型的数据流信息

后端代码:

import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;

@Slf4j
@Component
public class SseUtil {

    private static final Map<Long, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();

    /**
     * 创建连接
     */
    public SseEmitter connect(Long userId, Consumer<Throwable> errorCallback,Runnable timeOutCallback) {
        if (sseEmitterMap.containsKey(userId)) {
            SseEmitter sseEmitter =sseEmitterMap.get(userId);
            sseEmitterMap.remove(userId);
             sseEmitter.complete();
        }
        try {
            // 设置超时时间,0表示不过期。默认30秒
            SseEmitter sseEmitter = new SseEmitter(5*60*1000L);
            sseEmitter.send(SseEmitter.event().id(IdUtil.simpleUUID()).reconnectTime(1*60*1000L).data(""));
            // 注册回调
            sseEmitter.onCompletion(() -> {
            });
            sseEmitter.onError(errorCallback);
            sseEmitter.onTimeout(timeOutCallback);
            sseEmitterMap.put(userId, sseEmitter);
            log.info("创建sse连接完成,当前用户:{}", userId);
            return sseEmitter;
        } catch (Exception e) {
            log.info("创建sse连接异常,当前用户:{}", userId);
        }
        return null;
    }

    /**
     * 给指定用户发送消息
     *
     */
    public boolean sendMessage(Long userId,String messageId, String message) {
        if (sseEmitterMap.containsKey(userId)) {
            SseEmitter sseEmitter = sseEmitterMap.get(userId);
            try {
                sseEmitter.send(SseEmitter.event().id(messageId).reconnectTime(1*60*1000L).data(message));
                log.info("用户{},消息id:{},推送成功:{}", userId,messageId, message);
                return true;
            }catch (Exception e) {
                sseEmitterMap.remove(userId);
                log.info("用户{},消息id:{},推送异常:{}", userId,messageId, e.getMessage());
                sseEmitter.complete();
                return false;
            }
        }else {
            log.info("用户{}未上线", userId);
        }
        return false;
    }

    /**
     * 断开
     * @param userId
     */
    public void removeUser(Long userId){
        if (sseEmitterMap.containsKey(userId)) {
            SseEmitter sseEmitter = sseEmitterMap.get(userId);
            sseEmitterMap.remove(userId);
            sseEmitter.complete();
        }else {
            log.info("用户{} 连接已关闭",userId);
        }

    }

}

细节:

  1. 创建SseEmitter 对象时需要返回给客户端,且不能二次包装
  2. 建立连接后,浏览器会处于加载中的状态,直到SseEmitter发送消息,或者连接超时和关闭,所以连接后就像进行了一次空消息的发送,避免浏览器一直处于加载中
  3. SseEmitter 中, timeout 属性表示 SseEmitter 在发送 SSE 事件到客户端时的超时时间。也就是说,当您使用 SseEmittersend() 方法来发送 SSE 事件时,如果超过了 timeout 属性指定的时间,则将抛出 AsyncRequestTimeoutException 异常。客户端会进行自动重连,这个异常最好直接交给spring处理,因为这个请求是text/event-stream,全局异常处理可能会报错
  4. SseEventBuilder 中, timeout 属性表示当前正在构建的 SSE 事件的超时时间。也就是说,当您调用 SseEventBuilderbuild() 方法来构建 SSE 事件时,如果超过了 timeout 属性指定的时间,则将抛出 SseEventTimeoutException 异常

前端代码:

<script>
if (window.EventSource) {
            // 建立连接
            source = new EventSource(http://localhost:8080/test/sse?id=1);

            source.onopen = function (event) {
                console.log('SSE链接成功');
            }

            source.onmessage = function (event) {
 
                if(event.data){
                   
                   console.log('后端返回的数据:', data.value);
                }
              
            }
            source.onerror = (error) => {
                console.log('SSE链接失败');
            };

        } else {
            alert("你的浏览器不支持SSE");
        }
</script>


 

### Spring Boot 中实现 SSE (Server-Sent Events) 的方法 Spring Boot 提供了简单而强大的支持来实现 Server-Sent Events (SSE),这是一种允许服务器向客户端推送实时更新的技术。以下是关于如何在 Spring Boot 应用程序中设置和使用 SSE 长连接的具体说明。 #### 依赖配置 为了启用 SSE 功能,在 `pom.xml` 文件中需要引入 WebFlux 或者传统的 MVC 支持模块,因为它们都提供了对响应流的支持[^1]: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> ``` 或者如果仅需基于传统 Servlet 容器的方式,则可以使用以下依赖项: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> ``` #### 控制器定义 创建一个控制器类用于处理 HTTP 请求并返回事件流数据。通过 `TextEventStream` 类型作为返回值声明该接口是一个持续的事件源[^2]: ```java import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.time.LocalDateTime; import java.util.concurrent.atomic.AtomicLong; @RestController public class SseController { private final AtomicLong counter = new AtomicLong(); @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<String> streamEvents() { return Flux.interval(Duration.ofSeconds(1)) // 设置每秒触发一次消息 .map(sequence -> LocalDateTime.now().toString()); // 返回当前时间戳 } } ``` 上述代码片段展示了如何利用 Project Reactor 的 `Flux` 来生成无限序列的消息给前端订阅者。每次调用 `/stream` 接口都会建立一个新的 SSE 连接,并每隔一秒发送一条新的时间字符串到客户端[^3]。 对于非反应式编程模型下的解决方案也可以采用如下方式实现相同功能: ```java @GetMapping(value = "/sse-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public ResponseEntity<StreamingResponseBody> sseStream() { StreamingResponseBody responseBody = outputStream -> { try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream))) { while (!Thread.currentThread().isInterrupted()) { Thread.sleep(1000); // 模拟延迟 String message = "event: time\n" + "data: " + LocalDateTime.now().toString() + "\n\n"; writer.write(message); writer.flush(); } } catch (Exception e) { System.err.println(e.getMessage()); } }; return ResponseEntity.ok() .header(HttpHeaders.CACHE_CONTROL, "no-cache") .body(responseBody); } ``` 此版本适用于不希望切换至完全异步框架的应用场景下仍然能够提供基本的 SSE 能力[^4]。 #### 前端接收示例 最后,在 HTML 页面上可以通过简单的 JavaScript 添加监听逻辑以便于展示来自服务端的信息变化情况: ```html <script type="text/javascript"> const eventSource = new EventSource('/stream'); eventSource.onmessage = function(event) { console.log(`Received data at ${new Date()}:`, JSON.parse(event.data)); }; </script> ``` 以上就是完整的 spring boot 实现 server-sent events 长连接的过程描述以及相应代码实例[^5]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值