做后端开发的同学,大概率听过「SSE」这个词------尤其是在做实时推送、流式输出场景时,经常会和WebSocket、轮询放在一起讨论。但很多人对它的理解只停留在"服务端推数据",不清楚它的原理、适用场景,也不知道怎么落地实现。
今天就彻底搞懂SSE:从核心定义、工作原理,到Java(Spring Boot)、Node.js(Express)服务端实现,再到Vue客户端调用,最后对比WebSocket,帮你快速上手,再也不用被问起时支支吾吾。
一、先搞懂:SSE到底是什么?
SSE 全称 Server-Sent Events(服务器发送事件) ,是一种基于HTTP长连接的单向实时推送技术,核心作用是让服务端能够主动、持续地向客户端发送数据,而客户端只需要被动接收即可。
很多人会把它和WebSocket搞混,但记住一个关键:SSE是"单向通信",而WebSocket是"双向通信"------这也是两者最核心的区别,后续会详细对比。
核心特性(必记)
- 单向通信:只能服务端→客户端推送数据;客户端若要向服务端发数据,需单独走普通HTTP请求(如Axios、AJAX)。
- 基于HTTP协议 :无需新增协议、无需额外端口,用标准GET请求发起,响应头指定
Content-Type: text/event-stream即可,防火墙、代理服务器友好,部署成本低。 - 持久长连接:客户端发起一次请求后,服务端会保持连接不关闭,后续有新数据直接通过这个连接推送,避免频繁建立/断开连接的开销。
- 浏览器原生支持 :客户端可直接用
EventSourceAPI调用,自带断线自动重连(默认重连间隔3秒),无需手动写心跳检测、重连逻辑。 - 轻量简单:协议简单、开发成本低,无需像WebSocket那样处理握手、帧解析等复杂逻辑,适合纯单向推送场景。
工作原理(一句话讲透)
-
客户端通过GET请求,向服务端发起SSE连接,请求头携带相关配置(如重连间隔);
-
服务端接收请求后,返回响应头
Content-Type: text/event-stream,并保持连接不关闭; -
当服务端有新数据时,按SSE标准格式(
data: 数据内容\n\n),将数据写入连接流; -
客户端通过
EventSource监听数据,收到后触发onmessage事件,解析并展示数据; -
若连接断开(如网络异常),客户端会自动重连,无需开发者干预。
二、实战落地:3种场景全示例(Java+Node+Vue)
光懂理论不够,直接上可运行代码------覆盖Java(Spring Boot)、Node.js(Express)服务端,以及Vue客户端,复制就能跑通。
场景说明
做一个「实时时间推送」demo:服务端每秒生成当前时间,推送给客户端,客户端实时展示------模拟AI流式输出、实时通知等真实场景。
1. 服务端1:Java(Spring Boot)实现
Spring Boot 本身支持SSE,无需额外引入第三方依赖(核心是通过ResponseEntity输出text/event-stream流)。
java
import org.springframework.http.MediaType;
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;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@RestController
public class SseController {
// 定时任务,每秒推送一次时间
private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
@GetMapping("/sse/java/stream")
public void sseStream(HttpServletResponse response) throws IOException {
// 1. 设置响应头,指定SSE格式
response.setContentType(MediaType.TEXT_EVENT_STREAM_VALUE);
response.setCharacterEncoding("UTF-8");
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Connection", "keep-alive");
PrintWriter writer = response.getWriter();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 2. 每秒推送一次当前时间,持续推送(实际场景可替换为业务数据)
executor.scheduleAtFixedRate(() -> {
try {
// SSE标准格式:data: 数据\n\n(必须换行两次,否则客户端无法解析)
String time = sdf.format(new Date());
writer.write("data: " + time + "\n\n");
// 强制刷新,避免数据缓存
writer.flush();
// 模拟异常断开(可选,测试重连)
// if (System.currentTimeMillis() % 10000 == 0) {
// throw new IOException("连接异常断开");
// }
} catch (Exception e) {
// 关闭连接,客户端会自动重连
executor.shutdown();
writer.close();
}
}, 0, 1, TimeUnit.SECONDS);
}
}
注意点:
- 必须设置
Content-Type: text/event-stream,否则客户端无法识别为SSE流; - 数据格式必须是
data: 内容\n\n(两个换行符不可少),客户端才能正常解析; - 用
PrintWriter.write()写入数据后,需调用flush()强制刷新,避免数据缓存导致推送延迟; - 异常时关闭连接和线程池,客户端会自动重连。
2. 服务端2:Node.js(Express)实现
之前给过简单示例,这里完善异常处理和标准格式,确保可直接运行。
先安装依赖(仅需express):
css
npm install express --save
核心代码:
javascript
const express = require('express');
const app = express();
const port = 3001;
// SSE接口:每秒推送当前时间
app.get('/sse/node/stream', (req, res) => {
// 设置响应头
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.setHeader('Access-Control-Allow-Origin', '*'); // 跨域允许(开发环境)
// 每秒推送一次时间
const interval = setInterval(() => {
try {
const time = new Date().toLocaleString();
// SSE标准格式:data: 数据\n\n
res.write(`data: ${time}\n\n`);
} catch (err) {
// 异常时清除定时任务,关闭连接
clearInterval(interval);
res.end();
}
}, 1000);
// 客户端断开连接时,清除定时任务
req.on('close', () => {
clearInterval(interval);
res.end();
console.log('客户端断开SSE连接');
});
});
app.listen(port, () => {
console.log(`Node.js SSE服务启动,端口:${port}`);
});
3. 客户端:Vue3项目调用示例(最常用)
Vue项目中,可直接使用浏览器原生EventSource,也可封装成工具类,方便全局调用。这里提供两种方式,按需选择。
方式1:直接在组件中使用(简单场景)
xml
<template>
<div class="sse-demo">
<h3>SSE实时时间推送(Vue3)</h3>
<p>当前服务端时间:{{ serverTime }}</p>
</div>
</template>
<script setup>
import { ref, onUnmounted } from 'vue';
const serverTime = ref('');
let eventSource = null;
// 初始化SSE连接
const initSSE = () => {
// 注意:替换为自己的服务端接口地址(Java或Node)
const url = 'http://localhost:8080/sse/java/stream'; // Java服务端
// const url = 'http://localhost:3001/sse/node/stream'; // Node服务端
// 实例化EventSource
eventSource = new EventSource(url);
// 监听连接成功
eventSource.onopen = () => {
console.log('SSE连接成功');
};
// 监听服务端推送的数据(核心)
eventSource.onmessage = (e) => {
// e.data 就是服务端推送的内容(字符串格式)
serverTime.value = e.data;
};
// 监听连接错误
eventSource.onerror = (err) => {
console.error('SSE连接异常:', err);
// 异常时可手动重连(可选,原生已自带重连)
eventSource.close();
setTimeout(initSSE, 3000);
};
};
// 组件挂载时初始化SSE
initSSE();
// 组件卸载时关闭SSE连接,避免内存泄漏
onUnmounted(() => {
if (eventSource) {
eventSource.close();
console.log('SSE连接关闭');
}
});
</script>
<style scoped>
.sse-demo {
margin: 20px;
padding: 20px;
border: 1px solid #eee;
border-radius: 8px;
line-height: 1.8;
}
</style>
方式2:封装成工具类(全局复用)
创建src/utils/sse.js,封装SSE的初始化、监听、关闭方法:
javascript
/**
* SSE工具类(Vue3全局复用)
* @param {String} url - SSE服务端接口地址
* @param {Function} onMessage - 接收数据的回调函数
* @param {Function} onError - 错误回调函数
* @returns {Object} - 包含close方法的对象
*/
export const createSSE = (url, onMessage, onError) => {
if (!window.EventSource) {
alert('当前浏览器不支持SSE,请更换浏览器!');
return null;
}
const eventSource = new EventSource(url);
// 连接成功
eventSource.onopen = () => {
console.log('SSE连接成功');
};
// 接收数据
eventSource.onmessage = (e) => {
onMessage && onMessage(e.data);
};
// 连接错误
eventSource.onerror = (err) => {
console.error('SSE连接异常:', err);
onError && onError(err);
// 自动重连
eventSource.close();
setTimeout(() => createSSE(url, onMessage, onError), 3000);
};
// 关闭SSE连接
const closeSSE = () => {
if (eventSource) {
eventSource.close();
console.log('SSE连接关闭');
}
};
return { closeSSE };
};
在组件中使用工具类:
xml
<template>
<div class="sse-demo">
<h3>SSE实时时间推送(工具类复用版)</h3>
<p>当前服务端时间:{{ serverTime }}</p>
</div>
</template>
<script setup>
import { ref, onUnmounted } from 'vue';
import { createSSE } from '@/utils/sse'; // 引入封装的SSE工具类
const serverTime = ref('');
let sseInstance = null;
// 初始化SSE连接(调用工具类)
const initSSE = () => {
// 替换为自己的服务端接口地址(Java或Node均可)
const sseUrl = 'http://localhost:8080/sse/java/stream';
// const sseUrl = 'http://localhost:3001/sse/node/stream';
sseInstance = createSSE(
sseUrl,
// 接收服务端推送数据的回调
(data) => {
serverTime.value = data; // 将推送的时间赋值给页面变量
},
// 连接异常的回调
(err) => {
console.error('SSE连接出现异常,已自动重连', err);
}
);
};
// 组件挂载时初始化SSE
initSSE();
// 组件卸载时关闭SSE连接,避免内存泄漏(关键步骤)
onUnmounted(() => {
if (sseInstance) {
sseInstance.closeSSE(); // 调用工具类的关闭方法
}
});
</script>
<style scoped>
.sse-demo {
margin: 20px;
padding: 20px;
border: 1px solid #eee;
border-radius: 8px;
line-height: 1.8;
}
</style>
三、SSE vs WebSocket(必看选型指南)
很多人纠结什么时候用SSE,什么时候用WebSocket,直接看对比表,一目了然:
| 特性 | SSE | WebSocket |
|---|---|---|
| 通信方向 | 单向(服务端→客户端) | 双向(服务端↔客户端) |
| 底层协议 | 基于HTTP(GET请求) | 独立协议(ws:// / wss://) |
| 浏览器支持 | 原生支持(EventSource),IE不支持 | 大部分浏览器支持,需用API调用 |
| 自动重连 | ✅ 原生内置,无需手动实现 | ❌ 需手动实现心跳、重连逻辑 |
| 数据格式 | 仅支持文本(需自行序列化JSON) | 支持文本、二进制(如图片、文件) |
| 开发成本 | 低(无需处理握手、帧解析) | 高(需处理握手、断线重连、帧解析) |
| 部署成本 | 低(复用HTTP端口,防火墙友好) | 高(需单独配置端口、处理代理) |
| 适用场景 | 单向推送(AI流式输出、实时通知、行情监控、日志输出) | 双向交互(聊天、实时协作、游戏、弹幕) |
选型建议(一句话总结)
如果你的需求是「服务端推数据,客户端只接收」,用SSE(开发快、部署简单);如果需要「客户端和服务端互相通信」,用WebSocket(功能强、灵活)。
四、常见问题&避坑指南
1. 客户端无法接收数据?
-
检查服务端响应头是否正确设置
Content-Type: text/event-stream; -
检查数据格式是否为
data: 内容\n\n(两个换行符不可少); -
检查跨域配置(服务端需设置
Access-Control-Allow-Origin)。
2. 连接频繁断开?
-
服务端需避免数据缓存,确保
Cache-Control: no-cache; -
若服务端长时间无数据推送,可定期推送空数据(
data: \n\n),保持连接; -
检查服务器是否有超时断开长连接的配置(如Nginx的
proxy_read_timeout)。
3. 浏览器不支持SSE?
-
IE浏览器不支持
EventSource,可使用第三方polyfill(如event-source-polyfill); -
移动端浏览器(Chrome、Safari)均支持,无需额外处理。
五、总结
SSE作为一种轻量、简单的实时推送技术,在单向数据流场景中,比WebSocket更具优势------开发快、部署简单、无需处理复杂的双向通信逻辑。
本文从原理到实战,覆盖了Java、Node.js服务端,以及Vue客户端的完整示例,复制代码就能跑通。下次再遇到实时推送需求,不用再纠结,根据是否需要双向通信,直接选择SSE或WebSocket即可。
欢迎点赞、收藏、转发,关注我。