ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SSE轻松实现服务端推送,比WebSocket更简单

SSE轻松实现服务端推送,比WebSocket更简单 01 引言最近做的业务项目总是遇到到长连接、服务端向前端推送消息的功能点。由于项目中已经集成了现成的WebSocket也就是没有再重复造轮子。但是如果单独搭建WebSocket可能会繁琐能不能更加简单的SSE代替呢我们一起来试试SSE这门技术。02 简介2.1 核心概念SSEServer-Sent Events是一种基于 HTTP 协议的服务端推送技术。服务端通过持久的 HTTP 连接单向向客户端推送数据客户端通过EventSourceAPI监听并处理这些事件。主要特点• 单向通信仅服务端能发送消息• 消息仅支持文本数据• 协议是常规的HTTP 协议为什么选择SSE主要原因就是简单通常我们遇到的场景都是单项推送从服务器接收数据流。。这正是EventSource所擅长的。它还支持自动重新连接此外它是一个普通的旧的 HTTP不是一个新协议。官网地址https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events2.2 SSE vs WebSocket 对比维度SSEWebSocket通信方向单向服务端 → 客户端双向底层协议HTTP独立的 WS 协议断线重连浏览器自动重连需手动实现数据格式纯文本text/event-stream任意二进制复杂度低几行代码搞定高需要握手协议代理/防火墙友好好本质就是 HTTP一般连接数限制浏览器对同域名 SSE 连接数有限制通常 6 个无此限制适用场景通知推送、日志流、进度条、AI 流式输出聊天室、实时游戏、协同编辑选型建议如果你的场景只需要服务端向客户端单向推送优先用 SSE简单、稳定、省心。不要为了听起来高级就上WebSocke。03 SpringBoot 集成 SSEorg.springframework.web.servlet.mvc.method.annotation.SseEmitter是Spring MVC原生支持的方式最常用。是在Spring 4.2及以后就开始支持的。3.1 核心控制类Slf4j RestController RequestMapping(/sse) public class SseController { private final CopyOnWriteArrayListSseEmitter emitters new CopyOnWriteArrayList(); /** * Description: 建立连接 * * Author: ws * Date: 2026/9/17 11:38 **/ RequestMapping(value /connect, produces MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter connect(HttpServletResponse response) { response.setHeader(Access-Control-Allow-Origin, *); log.info(建立连接。。。); SseEmitter sseEmitter new SseEmitter(0L); try { sseEmitter.send(SseEmitter.event().data(连接成功)); emitters.add(sseEmitter); } catch (Exception e) { e.printStackTrace(); } return sseEmitter; } /** * Description: 推送测试 * * Author: ws * Date: 2026/9/17 17:27 **/ RequestMapping(value /push) public void push() throws Exception { for (int i 0; i 10; i) { final int index i; Thread.sleep(1000); emitters.forEach(emitter - { try { emitter.send(SseEmitter.event().data(推送数据 index)); } catch (Exception e) { log.error(推送数据失败, e); } }); log.info(推送数据 i); } } }其中connect()用来建立连接并将客户端保存到CopyOnWriteArrayList中而push()用来模拟数据推送。3.2 客户单订阅const eventSource new EventSource(http://localhost:8080/sse/connect); eventSource.addEventListener(message, (event) { console.log(收到消息:, event.data); }); eventSource.onerror (event) { console.log(连接出错浏览器会自动重连...); };3.3 测试分别通过谷歌浏览器和火狐浏览器接受服务端的消息。推送消息http://localhost:8080/sse/push到这里简单的搭建就已经完成了是不是很简单。04 使用注意事项上面完成简单的搭建功能如果用在生产商可能还会有很多问题。4.1 超时问题SseEmitter构造函数接收一个Long类型的超时时间毫秒new SseEmitter(0L) // 永不超时慎用 new SseEmitter(60000L) // 60秒超时 new SseEmitter() // 使用默认超时Spring 默认 30秒注意事项• 超时时间到了之后Spring 会自动调用complete()连接会被关闭。• 如果你的推送场景是长时间保持的如实时通知要么设0L永不超时要么自己实现心跳机制定期续命。•推荐做法设置一个合理的超时时间 客户端自动重连 服务端心跳保活。永不超时固然好用但是客户端堆积的问题如何解决这是首要考虑的问题。4.2 心跳保活机制既然选择了永不超时为什么还要使用保活机制呢这是因为长时间不发送数据连接可能被中间代理、Nginx或浏览器断开。这就需要我们定时不断给客户端发送心跳用来保持连接。下面是伪代码Scheduled(fixedRate 30000) // 每30秒 public void sendHeartbeat() { emitters.forEach(emitter - { try { emitter.send(SseEmitter.event().comment(heartbeat)); } catch (IOException e) { emitter.complete(); emitters.remove(emitter); } }); }4.3 连接数管理浏览器对同一域名的 SSE 连接数有限制HTTP/1.1 下通常为 6 个。如果你的应用打开了多个标签页连接数会很快耗尽。超过6个以上的标签页均无法建立连接。解决方案• 使用 HTTP/2连接复用不受 6 个限制• 使用共享 WorkerSharedWorker让多个标签页共享一个 SSE 连接• 后端用频道/分组管理一个连接推送多个主题的数据05 易错点5.1 忘记注册事件案例就是没有使用注册事件onCompletion、onTimeout、onError。当然案例使用的永不超时onTimeout就没有了意义。会造成什么后果客户端断开后emitter 还留在列表里每次推送都报 IOException日志刷屏内存泄漏。推荐做法SseEmitter emitter new SseEmitter(timeout); emitter.onCompletion(() - emitters.remove(emitter)); emitter.onTimeout(() - emitters.remove(emitter)); emitter.onError(e - emitters.remove(emitter)); emitters.add(emitter);5.2 emitter 管理如果在异步线程中发送数据时 emitter 已完成就会出现这样的异常IllegalStateException: ResponseBodyEmitter is already set complete主要因为在另一个线程中调用emitter.send()时连接已经被超时或客户端断开关闭了。推荐做法try { emitter.send(SseEmitter.event().data(hello)); } catch (IOException | IllegalStateException e) { emitter.complete(); emitters.remove(emitter); log.warn(推送失败已移除 emitter: {}, e.getMessage()); }5.3 produces 类型produces 类型没设置或设错会导致客户端收到数据但EventSource不触发事件或者浏览器直接拒绝连接。并不是所有的浏览器都会出现这样的问题。这个可以说是针对不同版本的浏览器的兼容性处理。推荐做法// ✅ 必须设置 produces GetMapping(value /subscribe, produces MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter subscribe() { ... }如果不设置produces text/event-streamSpring 可能返回application/json浏览器EventSource会直接报错。5.4 跨域问题前后端分离项目前端控制台报CORS错误。自己在做测试的时候也遇到了所以案例使用了response.setHeader(Access-Control-Allow-Origin, *);来解决跨域。解决跨域的方法有很对可以CrossOrigin注解、全局配置CORS或者其他方式只要解决跨域问题即可。06 小结除了上面列举的问题还有其他很多细节如鉴权、集群等场景。既然小细节那么多就没有直接造好的轮子直接使用么找了一圈下来没有找到。既然找不到那就自己造一个。后面专门建一个项目将SSE作为一个独立的推送服务再也不用去考虑那些零碎的小细节。
返回列表