Server-Sent Events(SSE)技术
SSE(Server-Sent Events)是一种基于HTTP的服务器推送技术,允许服务器向客户端单向实时推送数据。相比WebSocket,SSE更简单且原生支持自动重连;相比长轮询,它具有单一长连接和更好的实时性优势。SSE协议通过特定响应头和数据格式(如text/event-stream)实现通信,适用于股票更新、消息通知等场景。在Java中可通过HttpServletResponse或Ss
目录
一、 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适用场景
-
实时数据流:如股票市场更新、新闻推送、体育比分更新等。
-
实时通知:如社交媒体消息提醒、新订单通知等。
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为例

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