目录

一、 SSE 是什么?

二、 SSE 协议格式

三、SSE实现方式(java)

四、SSE适用场景

SSE 为前后端带来的价值

SSE 对性能有提升吗

五、企业级SSE方案

链路层支持

以Nginx为例

数据层支持

以Tomcat为例


一、 SSE 是什么?

SSE是一种允许服务器通过HTTP连接向客户端主动推送数据的技术,

  • SSE vs. WebSocket

    • 通信模式:SSE是服务器单向推送,WebSocket是全双工双向通信

    • 协议:SSE基于HTTP,WebSocket是基于TCP的独立协议。

    • 复杂度:SSE更简单,原生浏览器API支持;WebSocket稍复杂。

    • 自动重连:SSE内置,WebSocket需手动实现。

  • SSE vs. 长轮询

    • 解释长轮询的原理和开销(频繁建立/断开HTTP连接)。

    • 突出SSE的单一长连接实时性优势。

其实SSE也不是什么新技术,早在11年浏览器就有了初步支持,慢慢被大众熟知,还是得益于AI技术的兴起,大家所看到的AI的连续输出,也都是基于SSE来做的。

二、 SSE 协议格式

SSE是一种协议(基于HTTP),也就是说客户端服务端遵循SSE协议,就能实现通信,不和任何语言框架绑定。通常SSE 服务端需要再返回中添加以下

响应头

        response.setContentType("text/event-stream");
        response.setHeader("Connection", "keep-alive");
        response.setHeader("Cache-Control", "no-cache");

数据格式:消息体的基本结构

  • data::消息内容,可多行。

  • event::自定义事件类型,默认是 message

  • id::消息ID,用于断线重连。

  • retry::客户端重连间隔(毫秒)。

deepSeek采用sse推送样例如下

三、SSE实现方式(java)

springboot项目引入web依赖即可,可以使用原生HttpServletResponse或者spring提供的SseEmitter两种方式实现SSE

前端代码:

<!DOCTYPE html>
<html>
<head>
    <title>SSE Demo</title>
</head>
<body>
    <h2>SSE演示</h2>
    
    <div>
        <h3>原生SSE:</h3>
        <button onclick="connectNativeSSE()">连接原生SSE</button>
        <button onclick="disconnectNativeSSE()">断开</button>
        <div id="native-messages" style="border:1px solid #ccc;height:150px;overflow:scroll;padding:10px;margin:10px 0;"></div>
    </div>

    <div>
        <h3>SseEmitter:</h3>
        <button onclick="connectSseEmitter()">连接SseEmitter</button>
        <button onclick="disconnectSseEmitter()">断开</button>
        <div id="emitter-messages" style="border:1px solid #ccc;height:150px;overflow:scroll;padding:10px;margin:10px 0;"></div>
    </div>

    <script>
        // 原生SSE
        let nativeEventSource = null;
        
        function connectNativeSSE() {
            if (nativeEventSource) return;
            
            nativeEventSource = new EventSource('/api/sse/native');
            
            nativeEventSource.onmessage = (event) => {
                addMessage('native-messages', '原生: ' + event.data);
            };
            
            nativeEventSource.onopen = () => {
                addMessage('native-messages', '原生SSE连接成功');
            };
        }

        function disconnectNativeSSE() {
            if (nativeEventSource) {
                nativeEventSource.close();
                nativeEventSource = null;
                addMessage('native-messages', '原生SSE断开');
            }
        }

        // SseEmitter
        let emitterEventSource = null;
        
        function connectSseEmitter() {
            if (emitterEventSource) return;
            
            emitterEventSource = new EventSource('/api/sse/emitter');
            
            emitterEventSource.onmessage = (event) => {
                addMessage('emitter-messages', 'Emitter: ' + event.data);
            };
            
            emitterEventSource.onopen = () => {
                addMessage('emitter-messages', 'SseEmitter连接成功');
            };
        }

        function disconnectSseEmitter() {
            if (emitterEventSource) {
                emitterEventSource.close();
                emitterEventSource = null;
                addMessage('emitter-messages', 'SseEmitter断开');
            }
        }

        function addMessage(containerId, msg) {
            const div = document.getElementById(containerId);
            div.innerHTML += `<div>[${new Date().toLocaleTimeString()}] ${msg}</div>`;
            div.scrollTop = div.scrollHeight;
        }
    </script>
</body>
</html>

原生实现:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@RestController
public class NativeSseController {

    /**
     * 原生SSE实现 - 手动写入HTTP响应流
     */
    @GetMapping(value = "/api/sse/native", produces = "text/event-stream")
    public void nativeSse(HttpServletResponse response) throws IOException {
        // 设置SSE必需的响应头
        response.setContentType("text/event-stream");
        response.setCharacterEncoding("UTF-8");
        response.setHeader("Cache-Control", "no-cache");
        response.setHeader("Connection", "keep-alive");

        PrintWriter writer = response.getWriter();
        
        // 立即发送连接成功消息
        writer.write("data: 原生SSE连接已建立\n\n");
        writer.flush();

        // 模拟发送5条消息后自动结束
        for (int i = 1; i <= 5; i++) {
            try {
                Thread.sleep(2000); // 等待2秒
                
                String message = "原生SSE消息 " + i + " - 时间: " + java.time.LocalTime.now();
                writer.write("data: " + message + "\n\n");
                writer.flush();
                
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }

        // 发送结束消息
        writer.write("data: 原生SSE连接结束\n\n");
        writer.flush();
    }
}

SseEmitter方式

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@RestController
public class SseEmitterController {

    /**
     * SseEmitter方式 - 存储连接并手动推送
     */
    @GetMapping(value = "/api/sse/emitter", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter sseEmitter() {
        SseEmitter emitter = new SseEmitter(60_000L); // 60秒超时
        emitters.add(emitter);

        try {
            // 立即发送欢迎消息
            emitter.send("SseEmitter连接已建立");
        } catch (IOException e) {
            // 忽略发送错误
        }

        // 设置回调清理资源
        emitter.onCompletion(() -> {
            emitters.remove(emitter);
            System.out.println("SseEmitter连接完成");
        });
        
        emitter.onTimeout(() -> {
            emitters.remove(emitter);
            System.out.println("SseEmitter连接超时");
        });

        return emitter;
    }


}

四、SSE适用场景

  1. 实时数据流:如股票市场更新、新闻推送、体育比分更新等。

  2. 实时通知:如社交媒体消息提醒、新订单通知等。

SSE 为前后端带来的价值

  • 传统的轮询方式,会有大量的HTTP建立连接,SSE减少了大量无意义的短连接和查询

  • 提升实时性,服务端有数据就会立马推送,没有了轮询等待

SSE 对性能有提升吗

sse本质是基于Http长连接,也需要建立连接->响应数据传输。在传输通道和数据压缩方式保持不变的情况下,响应数据传输耗时也不会明显变化。

sse 的核心性能优势在于减少了请求发送的次数,其性能增益取决于具体的使用场景:

  • 服务端响应耗时大于网络传输耗时,性能提升有限。

  • 当网络传输耗时大于服务端处理耗时,减少请求次数可以显著降低整体延迟

如何理解第一句话呢,举一个例子

网上定制一个家具,非常复杂,卖家制作、打包需要2个小时(服务端处理时间),而快递只需要30分钟(网络传输耗时),,总耗时2小时30分钟,如果你想整个流程快一点,催快递公司他们只能把30分钟压缩到15分钟,整体性能提升有限,处理的瓶颈是在服务端

如何理解第二句话呢,举一个例子

买的是一个现成的螺丝刀,卖家只需要花10秒钟扫描一下条码就能打包好(服务端处理耗时)。但是,从卖家仓库到你家,快递依然需要30分钟(网络传输耗时)

方案A(发两次请求、传统轮询):你先下单买螺丝刀,等快递员花了30分钟送来后,你再下单买螺丝,快递员又花了30分钟送来。总共耗时60分钟

方案B(合并为一次请求):你一次过把螺丝刀和螺丝都加入购物车,一起下单。卖家处理(扫描两件商品)可能只需要20秒,几乎可以忽略。快递员只需要跑一趟,总共耗时30分钟

可以看到当网络传输本身很耗时(是主要瓶颈)时,你把多个小请求合并成一个大请求,就能避免重复支付那漫长的“快递时间”。减少请求次数可以显著降低整体延迟

五、企业级SSE方案

企业级应用时,在非直连多层网络架构的环境下,应用SSE不仅需要考虑前后端的使用,还需要考虑链路层、框架层、数据层等多环节的支持

链路层支持

以Nginx为例

Nginx 会缓存代理服务器的响应(聚合类型),服务推送的数据被 Nginx 缓存到缓冲区,导致客户端没有实时收到数据,而是等到服务所有数据推送完后,客户端才一次性收到了所有数据。

适配方案:

1. 禁用缓存功能,服务端响应时除了设置 SSE 所必须的 Response Header 外,还需要添加非标 Header:X-Accel-Buffering=no,告知 Nginx 不缓存响应,确保数据实时发送到客户端。

数据层支持

数据传输需注意代理服务器或 Web 容器(Nginx、Tomcat)对SSE MIME Type:text/event-stream的支持,未正确配置,服务端推送的数据不会经过任何压缩,传输数据大,导致客户端响应耗时增加

以Tomcat为例

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐