SSE是什么?SSE解决什么问题?在什么场景使用SSE?
定义:SSE,首先需要明确其全称是 Server-Sent Events(服务器发送事件),它是一种基于 HTTP 协议的服务器向客户端单向推送实时数据的技术标准。与 WebSocket 等双向通信技术不同,SSE 专注于 “服务器到客户端” 的单向实时数据传输,具有轻量、易用、基于现有 HTTP 生态的核心优势。
一、SSE的核心定义:
SSE的本质是客户端和服务端建立一次持久化的HTTP连接,服务器通过这个连接持续向客户端“流式”发送结构化数据(格式为text/event-stream),客户端实时监听并解析这些数据,实现服务器主动推,客户端被 动收的实时通信。
它的核心特点可总结为:
- 单向通信:仅支持服务器向客户端推送数据,客户端无法通过同一连接向服务器发送数据(若需客户端交互,需额外用 AJAX/HTTP 请求)。
- 基于 HTTP:无需建立新的通信协议(如 WebSocket 的
ws://
协议),可复用现有 HTTP 端口(80/443)、防火墙规则和代理配置,降低部署成本。 - 持久连接:一次连接可长期保持(默认超时时间可配置),避免频繁建立 / 断开连接的开销。
- 自动重连:客户端(如浏览器)原生支持断线后自动重连(默认重试间隔 3 秒),无需开发者额外实现。
- 结构化数据:服务器发送的数据需遵循特定格式(包含
event
、data
、id
、retry
等字段),客户端可按字段解析,支持 “事件分类”(如区分 “消息通知”“数据更新” 等事件)。
二、SSE 的核心作用:解决什么问题?
在 SSE 出现前,客户端获取实时数据的常见方案(如 “轮询”“长轮询”)存在明显缺陷,SSE 正是为解决这些问题而生:
因此,SSE 的核心作用是:以最低成本实现 “服务器到客户端” 的单向实时数据传输,平衡 “实时性”“低开销” 和 “易用性”。
三、SSE 的典型应用场景:什么时候用?
SSE 仅适用于 “服务器需主动向客户端推数据,客户端无需向服务器推数据” 的场景,常见场景包括:
1. 实时通知类场景
- 业务通知:用户收到的订单状态变更(如 “订单已发货”)、账户余额变动、消息提醒(如 “有人 @你”)。
- 系统通知:网站公告更新、服务器维护提醒、APP 版本更新提示。
- 示例:电商平台中,用户下单后,服务器通过 SSE 实时推送 “支付确认→订单审核→商品出库” 的状态变更。
2. 实时数据监控类场景
- 数据仪表盘:后台系统的实时数据统计(如实时用户在线数、订单成交量、接口调用量),无需页面刷新即可更新。
- 设备监控:物联网(IoT)场景中,传感器实时上传的温度、湿度、设备运行状态(如服务器 CPU 使用率),客户端通过 SSE 实时展示。
- 示例:运维人员的监控面板,通过 SSE 实时接收各服务器的内存使用率、磁盘空间数据,异常时即时高亮。
3. 实时内容更新类场景
- 实时资讯 / Feed 流:新闻网站的 “突发新闻推送”、社交平台的 “好友动态更新”(如朋友圈新内容)、直播平台的 “弹幕”(轻量弹幕场景)。
- 实时日志:开发 / 测试场景中,服务器实时日志(如接口请求日志、错误日志)通过 SSE 推送到前端页面,无需手动刷新日志页面。
- 示例:股票行情页面,服务器通过 SSE 每秒推送一次实时股价、涨跌幅数据,客户端实时更新 K 线图。
四、SSE 的技术细节补充(帮助深入理解)
要正确使用 SSE,需了解其关键技术规范:
1. 数据格式要求
服务器发送的数据流必须是 text/event-stream
格式,每行需遵循以下字段规则(字段名区分大小写):
data
:核心数据内容,可多行(多行需每行加data:
),客户端接收后需拼接;若数据为空,代表 “心跳包”(维持连接)。- 示例:
data: {"orderId": 123, "status": "已发货"}
- 示例:
event:
事件类型(可选),用于客户端区分不同类型的推送(如event: orderStatus
代表订单状态变更,event: notification
代表消息通知)。- 示例:
event: orderStatus\ndata: {"orderId": 123, "status": "已发货"}
- 示例:
id:
:数据唯一标识(可选),客户端会记录最后一个id
,断线重连时会通过Last-Event-ID
请求头告知服务器,避免数据丢失。retry:
:客户端断线后的重试间隔(毫秒,可选),默认 3000ms(3 秒)。- 空行:每个事件的结束标志(必须有,否则客户端无法解析)。
2. 客户端实现
前端实例:
<template><h3>实时消息:</h3><div id="messageContainer"></div>
</template><script lang="js">// 1. 连接 SSE 接口(注意:需处理跨域,后端需配置 CORS)const userId = "user123";const eventSource = new EventSource(`/sse/notify/${userId}`);// 2. 监听默认事件(无 event 字段的推送)eventSource.onmessage = function(event) {const data = JSON.parse(event.data);showMessage(`默认事件:${data.message}`);};// 3. 监听指定类型事件(对应后端的 event: notification)eventSource.addEventListener('notification', function(event) {const data = JSON.parse(event.data);showMessage(`[${new Date(data.time).toLocaleString()}] ${data.message}`);});// 4. 监听连接打开eventSource.onopen = function() {showMessage("SSE 连接已建立");};// 5. 监听错误(包括断开重连)eventSource.onerror = function(error) {if (eventSource.readyState === EventSource.CLOSED) {showMessage("SSE 连接已关闭,将自动重连...");} else {showMessage("SSE 连接错误: " + error);}};// 辅助函数:显示消息到页面function showMessage(text) {const div = document.createElement('div');div.textContent = text;document.getElementById('messageContainer').appendChild(div);}// 页面关闭时主动断开连接window.onbeforeunload = function() {eventSource.close();};</script>
后端示例:
使用java(Springboot)实现
第一种:手动通过 HttpServletResponse 构造 SSE 响应格式。使用 ScheduledExecutorService 定时推送消息。通过 PrintWriter 直接写入响应流。
@RestController
public class SSEController {/*** SSE通知接口* produces = MediaType.TEXT_EVENT_STREAM_VALUE 的作用:* 1. 指定响应内容类型为 text/event-stream,这是SSE(Server-Sent Events)的标准MIME类型* 2. 告诉客户端浏览器这是一个SSE流,浏览器会自动建立长连接并监听服务器推送的消息* 3. 确保Spring框架正确处理SSE响应格式** @param userId 用户ID* @param response HTTP响应对象,用于输出SSE数据流*/@GetMapping(value = "/sse/notify/{userId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public void sseNotify(@PathVariable String userId, HttpServletResponse response) {// TODO: 实现SSE推送逻辑response.setContentType(MediaType.TEXT_EVENT_STREAM_VALUE);response.setCharacterEncoding("UTF-8");response.setHeader("Cache-Control", "no-cache");//缓存控制response.setHeader("Connection", "keep-alive");//保持长连接try {PrintWriter writer = response.getWriter();ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();executor.scheduleAtFixedRate(() -> {try {// 1. 构建 SSE 格式数据(必须遵循规范)// event: 事件类型(可选)writer.write("event: notification\n");
// 数据内容writer.write("data: {\"userId\":\"" + userId + "\",\"message\":\"新消息推送\",\"time\":" + System.currentTimeMillis() + "}\n");// id: 消息ID(可选,用于重连时定位)writer.write("id: " + System.currentTimeMillis() + "\n");writer.write("\n");//空行结束一个事件writer.flush();//刷新缓冲区if (writer.checkError()) {throw new IOException("客户端断开连接");}} catch (IOException e) {executor.shutdown(); // 断开连接时停止任务}}, 0, 3, TimeUnit.SECONDS);} catch (Exception e) {e.printStackTrace();}}
}
第二种:使用 Spring 提供的 SseEmitter 类来处理 SSE 连接。SseEmitter 封装了大部分 SSE 相关的操作,如发送事件、处理连接断开等。
@GetMapping(value = "/sse/notify/{userId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public SseEmitter sseNotify(@PathVariable String userId) {SseEmitter emitter = new SseEmitter(0L); // 0表示永不超时ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();executor.scheduleAtFixedRate(() -> {try {emitter.send(SseEmitter.event().name("notification").data("{\"userId\":\"" + userId + "\",\"message\":\"新消息推送\",\"time\":" + System.currentTimeMillis() + "}"));} catch (Exception e) {emitter.completeWithError(e);executor.shutdown();}}, 0, 3, TimeUnit.SECONDS);// 连接断开时清理资源emitter.onCompletion(executor::shutdown);emitter.onTimeout(executor::shutdown);return emitter;}
第三种:使用 AsyncContext 实现异步处理,避免阻塞主线程。通过 request.startAsync() 开启异步处理。结合 ScheduledExecutorService 定时推送消息。
@GetMapping(value = "/sse/notify/{userId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public void sseNotify(@PathVariable String userId, HttpServletRequest request, HttpServletResponse response) {// 启用异步支持AsyncContext asyncContext = request.startAsync();asyncContext.setTimeout(0); // 设置永不超时response.setContentType("text/event-stream");response.setCharacterEncoding("UTF-8");response.setHeader("Cache-Control", "no-cache");response.setHeader("Connection", "keep-alive");try {PrintWriter writer = response.getWriter();ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();executor.scheduleAtFixedRate(() -> {try {writer.write("event: notification\n");writer.write("data: {\"userId\":\"" + userId + "\",\"message\":\"新消息推送\",\"time\":" + System.currentTimeMillis() + "}\n");writer.write("id: " + System.currentTimeMillis() + "\n");writer.write("\n");writer.flush();if (writer.checkError()) {throw new IOException("客户端断开连接");}} catch (Exception e) {executor.shutdown();asyncContext.complete();}}, 0, 3, TimeUnit.SECONDS);} catch (Exception e) {e.printStackTrace();}}
3. 服务器实现关键点
- 响应头必须设置:
Content-Type: text/event-stream
、Cache-Control: no-cache
(禁止缓存)、Connection: keep-alive
(保持连接)。 - 需支持 “流式输出”(不能一次性返回所有数据,需分块发送),不同后端语言实现方式不同(如 Node.js 的
res.write()
、Java 的response.getWriter().write()
)。 - 需处理客户端断开连接的 “优雅关闭”(如监听 HTTP 连接关闭事件,释放服务器资源)。
4.遇到跨域问题请参考
解决跨域问题https://blog.csdn.net/2302_80222668/article/details/152377377?spm=1001.2014.3001.5502
五、SSE 与 WebSocket 的对比:该怎么选?
很多人会混淆 SSE 和 WebSocket,两者的核心区别决定了适用场景:
结论:
- 若场景是 “单向推数据”(如通知、监控),优先选 SSE(成本低、易实现);
- 若场景是 “双向交互”(如聊天、多人协同编辑),必须选 WebSocket。