前言
在现代Web应用中,实时消息推送已成为提升用户体验的关键功能。无论是即时聊天、通知提醒还是实时数据更新,都需要一种高效的服务器到客户端的通信机制。本文将详细介绍如何使用Server-Sent Events (SSE)技术实现后端向前端的实时消息推送,并提供一个完整的实现方案。
什么是SSE?
Server-Sent Events (SSE) 是一种允许服务器向客户端推送实时更新的Web技术。与WebSocket不同,SSE是基于HTTP的单向通信(服务器→客户端),实现简单且天然支持断线重连,非常适合需要服务器推送但客户端不需要频繁发送数据的场景。
SSE的主要优势:
-
简单易用:基于标准HTTP协议,无需额外协议
-
自动重连:内置断线重连机制
-
轻量级:相比WebSocket更轻量
-
文本友好:特别适合推送文本消息
完整实现方案
下面我将通过一个完整的示例来展示如何实现SSE消息推送,包括后端服务和前端界面。
1. 后端实现
1.1 SSE服务核心类
@Service
public class SseService {private final Map<String, SseEmitter> emitters = new ConcurrentHashMap<>();private final ExecutorService executor = Executors.newCachedThreadPool();// 客户端订阅public SseEmitter subscribe(String clientId) {SseEmitter emitter = new SseEmitter(180_000L); // 3分钟超时// 设置各种回调emitter.onCompletion(() -> removeEmitter(clientId));emitter.onTimeout(() -> removeEmitter(clientId));emitter.onError(e -> removeEmitter(clientId));emitters.put(clientId, emitter);// 发送初始化事件try {emitter.send(SseEmitter.event().id("0").name("connect").data("Connected!"));} catch (IOException e) {removeEmitter(clientId);}return emitter;}// 广播消息给所有客户端public void broadcast(String message) {List<String> deadEmitters = new ArrayList<>();emitters.forEach((clientId, emitter) -> {executor.execute(() -> {try {if (emitter != null) {synchronized (emitter) {emitter.send(SseEmitter.event().id(String.valueOf(System.currentTimeMillis())).name("message").data(message));}}} catch (Exception e) {deadEmitters.add(clientId);}});});// 清理无效连接deadEmitters.forEach(this::removeEmitter);}// 移除指定客户端private synchronized void removeEmitter(String clientId) {SseEmitter emitter = emitters.remove(clientId);if (emitter != null) {try {emitter.complete();} catch (Exception e) {// 忽略关闭时的异常}}}// 应用关闭时清理资源@PreDestroypublic void destroy() {emitters.keySet().forEach(this::removeEmitter);executor.shutdownNow();}
}
1.2 SSE控制器
@RestController
@RequestMapping("/api/sse")
public class SseController {@Autowiredprivate SseService sseService;// 订阅接口@GetMapping("/subscribe")public SseEmitter subscribe(@RequestParam String clientId) {return sseService.subscribe(clientId);}// 测试发送消息接口@PostMapping("/send")public ResponseEntity<Void> sendMessage(@RequestParam String message) {try {sseService.broadcast(message);return ResponseEntity.ok().build();} catch (Exception e) {return ResponseEntity.internalServerError().build();}}
}
2. 前端实现
<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8"><title>消息监听</title><style>#messageBox {height: 400px;overflow-y: auto;border: 1px solid #ccc;padding: 10px;margin-bottom: 10px;}.message {margin: 5px 0;padding: 5px;background-color: #f5f5f5;border-radius: 4px;}</style>
</head>
<body><h2>实时消息监听</h2><div id="messageBox"></div><div><label><input type="checkbox" id="enableVoice" checked>启用语音播报</label><button onclick="testVoice()">测试语音</button></div><script>const clientId = 'client_' + Math.random().toString(36).substr(2, 9);const messageBox = document.getElementById('messageBox');const enableVoiceCheckbox = document.getElementById('enableVoice');const synth = window.speechSynthesis;// 初始化语音合成function loadVoices() {voices = synth.getVoices();}loadVoices();if (speechSynthesis.onvoiceschanged !== undefined) {speechSynthesis.onvoiceschanged = loadVoices;}// 语音播报功能function speak(text) {if (!enableVoiceCheckbox.checked) return;synth.cancel(); // 取消之前的语音const utterance = new SpeechSynthesisUtterance(text);const chineseVoice = voices.find(voice => voice.lang.includes('zh'));if (chineseVoice) {utterance.voice = chineseVoice;}utterance.lang = 'zh-CN';utterance.onerror = (event) => {console.error('语音播报错误:', event);};synth.speak(utterance);}// 连接SSEfunction connectSSE() {const eventSource = new EventSource(`/api/sse/subscribe?clientId=${clientId}`);eventSource.addEventListener('message', function(event) {// 显示消息const messageDiv = document.createElement('div');messageDiv.className = 'message';messageDiv.textContent = event.data;messageBox.insertBefore(messageDiv, messageBox.firstChild);// 语音播报speak(event.data);});// 错误处理与重连eventSource.onerror = function(error) {console.error('SSE连接错误:', error);eventSource.close();setTimeout(connectSSE, 5000); // 5秒后重连};}// 启动连接connectSSE();// 页面隐藏时停止语音document.addEventListener('visibilitychange', function() {if (document.hidden) {synth.cancel();}});</script>
</body>
</html>
与WebSocket的对比
特性 | SSE | WebSocket |
---|---|---|
通信方向 | 单向(服务器→客户端) | 双向 |
协议 | HTTP | 独立协议 |
断线重连 | 内置支持 | 需手动实现 |
二进制数据 | 不支持 | 支持 |
浏览器兼容性 | IE不支持 | 更广泛 |
实现复杂度 | 简单 | 较复杂 |
适用场景
SSE特别适合以下场景:
-
服务器向客户端推送通知
-
实时数据更新(如股票价格、体育比分)
-
新闻/社交媒体feed更新
-
需要简单实现的实时功能
总结
SSE提供了一种简单高效的服务器推送技术方案,本文通过完整的代码示例展示了如何从零开始实现一个功能完善的SSE推送系统。相比WebSocket,SSE在实现简单性、自动重连等方面具有明显优势,是许多实时应用场景的理想选择。
希望本文能帮助你理解并应用SSE技术。如果你有任何问题或建议,欢迎在评论区留言讨论!