消息推送——SSE(Server-Sent Events)详解与实战
消息推送——SSE(Server-Sent Events)详解与实战
在现代 Web 开发中,实时通信已经成为众多应用的核心需求之一。从聊天室到股票行情更新,从通知系统到物联网数据展示,我们常常需要服务器将数据“推”给客户端。
虽然 WebSocket 是一种强大的双向通信协议,但在很多场景下,我们只需要 服务器向客户端的单向推送 功能即可。这时候,一种轻量级、高效的替代方案 —— SSE(Server-Sent Events) 就派上了用场。
一、什么是 SSE?
SSE(Server-Sent Events) 是 HTML5 提供的一种浏览器与服务器之间的通信技术,允许服务器向浏览器主动推送信息。它基于标准的 HTTP 协议,实现简单,适合于服务器频繁或持续地向客户端发送数据的场景。
✅ 特点总结:
特性 | 描述 |
---|---|
单向通信 | 仅支持服务器向客户端推送消息 |
基于 HTTP | 使用普通的 HTTP/HTTPS 协议 |
自动重连 | 客户端自动尝试重新连接 |
轻量高效 | 数据格式为文本,解析容易 |
支持事件类型 | 可以定义多个事件名(如 message 、update 、notification 等) |
二、SSE 的基本使用
1. 前端代码(JavaScript)
const eventSource = new EventSource('http://your-api.com/sse');
// 监听默认的 'message' 事件
eventSource.onmessage = function(event) {
console.log("收到消息:", event.data);
};
// 监听自定义事件
eventSource.addEventListener('notification', function(event) {
console.log("收到通知:", event.data);
});
// 错误处理
eventSource.onerror = function(err) {
console.error("发生错误:", err);
};
⚠️ 注意:前端不能通过 JavaScript 主动向服务端发送消息(SSE 不支持客户端发送),这是与 WebSocket 的一个重要区别。
2. 后端代码(Spring Boot Java 示例)
使用 SseEmitter
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
@RestController
public class SseController {
@GetMapping(value = "/sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter createConnection() {
SseEmitter emitter = new SseEmitter(60_000L); // 设置超时时间为 60 秒
new Thread(() -> {
try {
// 发送一条普通 message 类型的消息
emitter.send("Hello, this is a message.");
// 发送一条自定义事件类型的通知
emitter.send(SseEmitter.event()
.name("notification")
.data("You have a new notification!"));
emitter.complete(); // 完成连接
} catch (IOException e) {
emitter.completeWithError(e);
}
}).start();
return emitter;
}
}
三、SSE 的工作原理
SSE 的通信过程如下:
- 客户端使用
EventSource(url)
创建一个与服务器的连接。 - 服务器响应头设置为
Content-Type: text/event-stream
,表示这是一个流式响应。 - 服务器保持连接打开,并可以随时向客户端发送数据。
- 客户端监听事件并作出响应。
- 如果连接中断,客户端会自动尝试重新连接(除非明确关闭)。
📈 典型的数据格式示例:
data: This is a simple message.
event: notification
data: {"title": "New Message", "content": "You've got mail!"}
四、SSE vs WebSocket
对比项 | SSE | WebSocket |
---|---|---|
协议 | HTTP | TCP |
通信方向 | 单向(服务器 → 客户端) | 双向 |
是否自动重连 | ✅ 是 | ❌ 需手动实现 |
实现复杂度 | ⭐ 简单 | ⭐⭐⭐ 复杂 |
浏览器兼容性 | 广泛支持(IE 不支持) | Chrome/Firefox/Edge 支持良好 |
适用场景 | 推送为主(如通知、日志、仪表盘) | 实时交互(如聊天、游戏、协同编辑) |
五、SSE 应用场景
- 实时通知系统(如邮件、订单提醒)
- 数据仪表盘(股票价格、CPU 使用率等)
- 日志查看工具(远程日志输出到前端页面)
- 表单协同预览 / 编辑
- 游戏排行榜更新推送
- IoT 设备状态监控
六、进阶技巧:多客户端管理 + 异步推送
当有多个用户连接 SSE 接口时,通常我们需要一个全局的 Map<String, SseEmitter>
来保存所有连接,并根据业务逻辑向特定用户发送消息。
前端代码
前端js代码,前端客户端通过输入clinetId,调用createSSEClient方法创建SSE连接。在输入框中输入输入自定义客户端id,发送请求,后端会创建一个SseEmitter实例。
<script lang="ts" setup >
const data = ref('')
const messageList = ref<string[]>([])
const message = ref('')
/**
* 创建sse客户端
*/
function createSSEClient(clientId: string) {
var sseSource = new EventSource("http://localhost:9901/message/connect?clientId="+clientId);
// 连接打开
sseSource.onopen = function () {
ElMessage.success("sse连接成功:" + cilendId.value);
console.log("连接打开");
}
// 连接错误
sseSource.onerror = function (err) {
ElMessage.error("sse连接失败:"+ cilendId.value);
console.log("连接失败",err );
}
// 接收到数据
sseSource.onmessage = function (event) {
console.log("接收到数据data:", event.data);
data.value = event.data
messageList.value.push(data.value)
console.log("转换的数据",data.value)
handleReceiveData(event.data)
}
}
// 处理服务器返回的数据
function handleReceiveData(data:string) {
//设置消息处理策略
}
// 通过http发送消息
function sendMessage() {
console.log("发送消息")
const data = {
message: message.value,
clientId:cilendId.value
}
fetch('http://localhost:9901/message/sendMessage', {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
},
body: JSON.stringify(data)
})
}
</script>
后端代码
Service服务端代码,在实际使用中用户可以根据cilentId区分选择响应的方法处理并响应消息。
@Service
public class SseService {
/**
* k:客户端id v:SseEmitter
*/
private static Map<String, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();
public SseEmitter connect(String clientId) {
//6小时后断开
SseEmitter sseEmitter = new SseEmitter(6 * 60 * 60 * 1000L);
if (sseEmitterMap.containsKey(clientId)){
throw new BusinessException("该clientId已绑定指定的客户端!clientId:" + clientId);
}
if (sseEmitterMap.size() > 1000) {
throw new BusinessException("当前连接数已满!");
}
try {
sseEmitter.send("【"+clientId+"】"+"连接成功", MediaType.APPLICATION_JSON);
} catch (IOException e) {
log.error("sse连接发送数据时出现异常:", e);
throw new BusinessException("sse连接发送数据时出现异常!");
}
// 连接报错
sseEmitter.onError((throwable) -> {
log.info("sse连接异常:", throwable);
sseEmitterMap.remove(clientId);
});
sseEmitterMap.put(clientId, sseEmitter);
return sseEmitter;
}
public void activeSendMessage(SseMessage message) {
if (ObjectUtil.isEmpty(sseEmitterMap)) {
return;
}
if (ObjectUtil.isEmpty(message.getClientId())){
return;
}
SseEmitter sseEmitter1 = sseEmitterMap.get(message.getClientId());
if (ObjectUtil.isNotEmpty(sseEmitter1)){
try {
sseEmitter1.send("【"+message.getClientId() + "】发送消息:" + message.getMessage(), MediaType.APPLICATION_JSON);
} catch (IOException e) {
log.error("sse连接发送数据时出现异常:", e);
}
}
}
}
七、SSE事件监听
在前端 JavaScript 中,我们通过 EventSource
对象来与服务器建立连接,并监听其发送过来的消息。以下是几种主要的事件监听方式
//1. 监听默认的 `message` 事件: 当服务器发送的数据没有指定特定的事件名时,默认会触发 `message` 事件。
const eventSource = new EventSource('/sse-endpoint');
eventSource.onmessage = function(event) {
console.log("收到默认消息:", event.data);
};
//2. 使用 `addEventListener` 监听特定事件: 如果服务器发送的数据指定了事件名称(即 `event` 字段),你可以通过 `addEventListener` 方法来监听这些特定事件。
const eventSource = new EventSource('/sse-endpoint');
// 监听名为 'customEvent' 的事件
eventSource.addEventListener('customEvent', function(event) {
console.log("收到自定义事件: ", event.data);
});
//3.错误处理: 为了确保应用能够正确处理网络中断或其他错误情况,添加一个错误处理器是很重要的。
eventSource.onerror = function(error) {
console.error("发生错误: ", error);
};
事件监听示例
前端代码
<script lang="ts" setup >
const eventList = ref<string[]>([])
function eventSSE(event:string){
const sse = new EventSource('http://localhost:9901/message/event?eventName='+event)
sse.addEventListener('open', (e) => {
console.log("edit",e.target)
})
/**
* 监听message事件:message是默认事件,没有指定监听的主题时,会默认发送一个message事件
*/
sse.addEventListener(event, (e) => {
eventList.value.push(e.data)
console.log("edit2",e.data)
})
}
</script>
后端代码
/**
* SSE事件监听
* @return
*/
@GetMapping(path = "/event", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter event(@RequestParam @NotNull(message = "eventName不能为空") String eventName) {
log.info("sse实时推送新用户连接:{}", eventName);
SseEmitter emitter = new SseEmitter();
threadPoolExecutor.execute(() -> {
try {
for (int i = 0; i < 50; i++) {
emitter.send(SseEmitter.event().name(eventName).data("【"+ eventName+ "】:" + i));
Thread.sleep(3000);
}
emitter.complete();
}catch (Exception e){
emitter.completeWithError(e);
}
});
return emitter;
}
八、常见问题和注意事项
✅ 如何避免连接被浏览器或服务器中断?
- 设置合理的超时时间(例如
new SseEmitter(60_000L)
); - 在后端定时发送心跳/空消息防止连接闲置断开。
✅ 如何保证消息顺序性和可靠性?
- SSE 本身不提供复杂的 QoS 机制(如重传、确认),如果需要更强可靠性,建议结合数据库记录+重试策略。
✅ 如何测试 SSE 接口?
使用curl 查看原始输出:
curl http://localhost:8080/sse
或使用 Postman,选择
Get
请求并设置接受类型为text/event-stream
。
九、总结
SSE 是一种轻量级、易于实现的服务器到客户端的实时通信方式,特别适合那些不需要全双工通信的业务场景。相比 WebSocket,它更简单、更易维护,在大多数情况下已经足够满足需求。
微信公众号 作者微信
版权所有
版权归属:Lzc