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 秒),无需开发者额外实现。
  • 结构化数据 :服务器发送的数据需遵循特定格式(包含 eventdataidretry 等字段),客户端可按字段解析,支持 "事件分类"(如区分 "消息通知""数据更新" 等事件)。

二、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. 客户端实现
前端实例:
javascript 复制代码
<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 直接写入响应流。

java 复制代码
@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 相关的操作,如发送事件、处理连接断开等。

java 复制代码
    @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 定时推送消息。

java 复制代码
    @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-streamCache-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。
相关推荐
Terio_my3 小时前
IDEA自动构建与热部署配置
java·ide·intellij-idea
数智顾问3 小时前
Java坐标转换的多元实现路径:在线调用、百度与高德地图API集成及纯Java代码实现——纯Java代码实现与数学模型深度剖析
java·开发语言
武子康3 小时前
Java-138 深入浅出 MySQL Spring Boot 事务传播机制全解析:从 REQUIRED 到 NESTED 的实战详解 传播机制原理
java·大数据·数据库·spring boot·sql·mysql·事务
码神本神4 小时前
【附源码】基于Spring Boot的高校爱心捐助平台的设计与实现
java
真的想不出名儿4 小时前
登录前验证码校验实现
java·前端·python
珹洺4 小时前
Java-Spring入门指南(十九)thymeleaf基本概念
java·spring·状态模式
吹晚风吧4 小时前
什么是跨域?跨域怎么解决?跨域解决的是什么问题?
java·vue.js·js·cors
敲码图一乐4 小时前
流量安全——基于Sentinel实现限流,熔断,降级
java·开发语言·数据库
0xMinos4 小时前
Java 设计模式——单例模式
java·设计模式