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。
相关推荐
启山智软7 小时前
【中大企业选择源码部署商城系统】
java·spring·商城开发
我真的是大笨蛋7 小时前
深度解析InnoDB如何保障Buffer与磁盘数据一致性
java·数据库·sql·mysql·性能优化
怪兽源码7 小时前
基于SpringBoot的选课调查系统
java·spring boot·后端·选课调查系统
恒悦sunsite7 小时前
Redis之配置只读账号
java·redis·bootstrap
梦里小白龙7 小时前
java 通过Minio上传文件
java·开发语言
人道领域7 小时前
javaWeb从入门到进阶(SpringBoot事务管理及AOP)
java·数据库·mysql
sheji52618 小时前
JSP基于信息安全的读书网站79f9s--程序+源码+数据库+调试部署+开发环境
java·开发语言·数据库·算法
毕设源码-邱学长8 小时前
【开题答辩全过程】以 基于Java Web的电子商务网站的用户行为分析与个性化推荐系统为例,包含答辩的问题和答案
java·开发语言
摇滚侠8 小时前
Java项目教程《尚庭公寓》java项目从开发到部署,技术储备,MybatisPlus、MybatisX
java·开发语言
€8118 小时前
Java入门级教程24——Vert.x的学习
java·开发语言·学习·thymeleaf·数据库操作·vert.x的路由处理机制·datadex实战