SSE 协议的核心规范在 2011 年完成 W3C 标准化,成为可商用的技术方案;
2021 年完成 IETF 层面的 HTTP 协议标准化。
[笑哭]不是WebSocket不好,只是 SSE更有性价比
SSE 真正在行业内普及、成为消息推送主流方案之一的时间是 2015-2017 年
核心原因:
- 浏览器兼容性成熟:2015 年前后,IE 以外的主流浏览器(Chrome、Firefox、Safari、Edge)均已全面支持 EventSource API,解决了兼容性痛点;
- 业务场景适配需求:移动互联网爆发期,「服务端单向推送」场景(如订单通知、系统告警、实时日志)大量出现,sse 相比 WebSocket 更轻量(无需处理双向通信、无需额外协议升级),开发和运维成本更低,成为这类场景的首选;
- 框架生态支持:2015-2017 年,SpringBoot(2014 年发布)、Node.js 等主流后端框架完善了对 SSE 的原生支持(如 SpringBoot 的 SseEmitter 组件),降低了开发门槛;
- 对比 WebSocket 的差异化优势:WebSocket 虽适合双向通信,但部署成本高(需反向代理适配、长连接管理复杂),而 SSE 基于 HTTP,可直接复用现有 HTTP 基础设施(Nginx、CDN),中小团队更倾向用 SSE 实现单向推送。
SSE 协议的核心特性(本质属性)1. 基于 HTTP/1.1 协议,无需额外升级
- SSE 完全复用 HTTP 协议栈,客户端通过普通 GET 请求建立连接,服务端返回 Content-Type: text/event-stream 响应即可,无需像 WebSocket 那样经历「HTTP 升级为 WebSocket 协议」的握手过程;
- 可直接复用现有 HTTP 基础设施(Nginx、CDN、负载均衡),无需额外配置,部署成本极低。
- 核心定位是「服务器→客户端」的单向数据传输,客户端无法通过 SSE 连接向服务端发送数据(若需客户端交互,需配合普通 HTTP/POST 请求);
- 完美适配「通知推送、实时日志、状态更新」等单向场景,避免了 WebSocket 双向通信的冗余设计。
- 服务端推送的数据是结构化的文本流(而非二进制),遵循固定格式(event:/data:/id:/retry: 字段 + 空行结束);
- 浏览器原生 EventSource API 可自动解析格式,无需前端手动处理流数据,开发成本低。
![]()
![]()
4. 内置自动重连机制
- 浏览器的 EventSource 会在连接断开(如网络异常、服务端重启)时自动重试,默认重连间隔 3 秒;
- 服务端可通过 retry: 字段自定义重连间隔(如 retry: 5000 表示 5 秒重试),客户端也可通过 lastEventId 记录最后接收的消息 ID,实现消息续传。
- 基于 HTTP 长连接实现,连接建立后保持打开状态,服务端可随时推送数据;
- 服务端采用「分块传输编码(Transfer-Encoding: chunked)」,数据分批次发送,无需等待完整响应,实时性接近 WebSocket。、
![]()
![]()
![]()
html>charset="UTF-8">SSE 实时消息推送测试
SpringBoot SSE 实时消息推送测试
用户ID: 建立连接 断开连接
消息内容: 推送给当前用户 广播给所有用户 查看在线数
package com.example.sse.manager;* @author* @title: SseEmitterManager* @description: TODOimport lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Component;import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import java.io.IOException;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.atomic.AtomicInteger;* SSE连接管理器:维护用户与SSE连接的映射,处理连接生命周期@Slf4j@Componentpublic class SseEmitterManager {// 线程安全的Map,存储用户ID -> SSE连接private final Map sseEmitterMap = new ConcurrentHashMap<>();// SSE连接超时时间:30分钟(1800000毫秒),可根据业务调整private static final long TIMEOUT = 1800000L;// 统计当前在线连接数private final AtomicInteger onlineCount = new AtomicInteger(0);* 创建并保存用户的SSE连接* @param userId 用户唯一标识* @return SseEmitter 连接对象public SseEmitter createSseEmitter(String userId) {// 创建SSE发射器,设置超时时间SseEmitter emitter = new SseEmitter(TIMEOUT);// 保存连接到MapsseEmitterMap.put(userId, emitter);onlineCount.incrementAndGet();log.info("用户[{}]建立SSE连接,当前在线数:{}", userId, onlineCount.get());// 连接超时回调:移除无效连接emitter.onTimeout(() -> {log.warn("用户[{}]的SSE连接超时", userId);removeEmitter(userId);// 连接完成回调(客户端主动断开)emitter.onCompletion(() -> {log.info("用户[{}]的SSE连接已完成", userId);removeEmitter(userId);// 连接异常回调emitter.onError(e -> {log.error("用户[{}]的SSE连接异常", userId, e);removeEmitter(userId);return emitter;* 向指定用户推送消息* @param userId 用户ID* @param message 消息内容(支持字符串/JSON)public void sendToUser(String userId, String message) {if (!sseEmitterMap.containsKey(userId)) {log.warn("用户[{}]未建立SSE连接,推送失败", userId);return;SseEmitter emitter = sseEmitterMap.get(userId);try {// 发送消息:指定事件ID、事件类型、消息内容emitter.send(SseEmitter.event().id(String.valueOf(System.currentTimeMillis())) // 消息唯一ID.name("custom-message") // 自定义事件类型(前端可根据此过滤).data(message)); // 消息内容log.info("向用户[{}]推送消息:{}", userId, message);} catch (IOException e) {log.error("向用户[{}]推送消息失败", userId, e);removeEmitter(userId); // 推送失败,移除无效连接* 广播消息:推送给所有在线用户* @param message 广播内容public void broadcast(String message) {if (sseEmitterMap.isEmpty()) {log.warn("无在线用户,广播消息失败:{}", message);return;sseEmitterMap.forEach((userId, emitter) -> {try {emitter.send(SseEmitter.event().id(String.valueOf(System.currentTimeMillis())).name("broadcast-message").data(message));} catch (IOException e) {log.error("向用户[{}]广播消息失败", userId, e);removeEmitter(userId);log.info("广播消息完成,覆盖{}个在线用户,消息内容:{}", onlineCount.get(), message);* 移除用户的SSE连接(释放资源)* @param userId 用户IDpublic void removeEmitter(String userId) {if (sseEmitterMap.containsKey(userId)) {SseEmitter emitter = sseEmitterMap.remove(userId);if (emitter != null) {emitter.complete(); // 主动完成连接,释放资源onlineCount.decrementAndGet();log.info("用户[{}]的SSE连接已移除,当前在线数:{}", userId, onlineCount.get());* 获取当前在线用户数* @return 在线数public int getOnlineCount() {return onlineCount.get();推送给具体某个用户
![]()
广播给所有用户
![]()
想要获取完整代码的小伙伴看过来啦!
不管是评论区留言,
还是私信我都可以,
看到后会第一时间发你哦✨
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.