ARTICLE DETAIL

资讯详情

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

彻底吃透 SSE 服务端推送:从原理到 SpringBoot 实战(替代 WebSocket 最简方案)

彻底吃透 SSE 服务端推送:从原理到 SpringBoot 实战(替代 WebSocket 最简方案) 前言做过后端开发都知道前端想要实时拿服务端数据最常见两种方案前端轮询每秒请求一次接口—— 浪费服务器资源、延迟高WebSocket 双向长连接 —— 重、复杂、需要握手、需要心跳、断开重连麻烦但90% 的单向实时场景AI 流式输出、日志推送、消息通知、进度条、直播弹幕根本不需要 WebSocket这里就引出一个超级轻量、零配置、原生支持、专门服务端向客户端单向推送的技术SSEServer-Sent Events服务端推送事件本文从零手写彻底吃透 SSE 是什么、和 WebSocket 区别、适用场景、底层原理、SpringBoot 完整实战、多用户推送、异常重连、429 限流问题、AI 流式输出实战。一、什么是 SSE1.1 定义SSE 是一种基于 HTTP 协议的服务端单向推送技术。客户端发起一次 HTTP 请求服务端持续推送数据连接不断开直到客户端主动关闭。1.2 一句话区分轮询前端不断问后端 → 浪费请求WebSocket双向互通聊天、弹幕、游戏→ 太重SSE后端主动推给前端单向→ 最轻量二、SSE 核心优势优势 1基于原生 HTTP零配置不用握手、不用协议升级、不用心跳、不用处理帧格式。优势 2自带自动重连连接断开浏览器自动重试连接不用前端写重连逻辑。优势 3协议极简格式固定服务端只需要输出data: 内容\n\n优势 4天生支持流式输出AI 问答、代码生成、文本续写全部首选 SSE不用 WebSocket。优势 5资源极低、不占端口、性能极高三、SSE VS WebSocket3.1 适用场景完全不同SSE 适用单向推送AI 流式对话系统日志实时推送任务进度条消息通知服务端实时监控数据WebSocket 适用双向交互在线聊天实时游戏直播间互动协同编辑3.2 核心区别表协议通信方向重连机制数据格式复杂度SSEHTTP服务端 → 客户端单向浏览器自带自动重连纯文本极简WebSocketWS 自定义协议双向互通前端手动写重连二进制 / 文本复杂心跳、断连、帧解析、缓冲区四、SSE 底层协议规范SSE 响应头固定Content-Type: text/event-stream Cache-Control: no-cache Connection: keep-aliveSSE 固定报文格式data: 消息内容 event: 自定义事件名 id: 消息ID retry: 重连时间每条消息必须以 \n\n 结尾五、SpringBoot 完整 SSE 实战我们实现单用户实时推送多用户在线广播连接管理、断线释放模拟 AI 流式输出5.1 核心工具类 SSEConnectionManager用于保存、管理、释放所有客户端连接import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * SSE 连接管理器 * 支持多用户在线、广播、单点推送 */ public class SseConnectionManager { // 保存所有客户端连接 private static final MapString, SseEmitter CLIENT_MAP new ConcurrentHashMap(); // 建立连接 public static SseEmitter connect(String userId) { SseEmitter sseEmitter new SseEmitter(0L); // 永不超时 CLIENT_MAP.put(userId, sseEmitter); // 连接关闭回调 sseEmitter.onCompletion(() - CLIENT_MAP.remove(userId)); // 超时回调 sseEmitter.onTimeout(() - CLIENT_MAP.remove(userId)); // 异常回调 sseEmitter.onError(e - CLIENT_MAP.remove(userId)); return sseEmitter; } // 单点推送 public static void sendMsg(String userId, String msg) { try { SseEmitter emitter CLIENT_MAP.get(userId); if (emitter ! null) { emitter.send(msg); } } catch (Exception e) { CLIENT_MAP.remove(userId); } } // 全员广播 public static void broadcast(String msg) { CLIENT_MAP.forEach((k, v) - { try { v.send(msg); } catch (Exception e) { CLIENT_MAP.remove(k); } }); } // 关闭连接 public static void close(String userId) { CLIENT_MAP.remove(userId); } }5.2 编写 SSE 控制器import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; RestController RequestMapping(/sse) public class SseController { /** * 建立SSE长连接 */ GetMapping(/connect/{userId}) public SseEmitter connect(PathVariable String userId) { return SseConnectionManager.connect(userId); } /** * 单点推送消息 */ GetMapping(/send/{userId}/{msg}) public String send(PathVariable String userId, PathVariable String msg) { SseConnectionManager.sendMsg(userId, msg); return 推送成功; } /** * 全员广播 */ GetMapping(/broadcast/{msg}) public String broadcast(PathVariable String msg) { SseConnectionManager.broadcast(msg); return 广播成功; } /** * 关闭连接 */ GetMapping(/close/{userId}) public String close(PathVariable String userId) { SseConnectionManager.close(userId); return 关闭成功; } }六、SSE 常见坑坑 1SSE 超时断开解决new SseEmitter (0L) 设置永不超时坑 2连接堆积、内存泄漏解决必须 onCompletion /onTimeout/onError 清除连接坑 3Nginx 默认 60 秒断开生产 Nginx 必须配置proxy_read_timeout 3600s; proxy_send_timeout 3600s;坑 4429 太多请求报错前端不要疯狂重连可加间隔防抖。七、SSE 最佳业务场景总结AI 流式问答后台任务进度推送文件导入、导出、解压系统实时日志输出简单消息通知服务端监控实时数据推送只要不是双向聊天一律优先 SSE不用 WebSocket结语人生如棋世事若水弃子攻杀谷底待发落子无悔过眼云烟。人生线人生如棋弃子攻杀落子无悔。态度线世事若水谷底待发过眼云烟。up自己瞎写的加油陌生人
返回列表