一、简介
Server-Sent Events (SSE) 是HTML5引入的一种轻量级的服务器向浏览器客户端单向推送实时数据的技术。在Spring Boot框架中,我们可以很容易地集成并利用SSE来实现实时通信。
二、依赖添加
在Spring Boot项目中,无需额外引入特定的依赖,因为Spring Web MVC模块已经内置了对SSE的支持。
辅助Maven
<!-- 集成beetl --><dependency><groupId>com.ibeetl</groupId><artifactId>beetl-framework-starter</artifactId><version>1.2.30.RELEASE</version></dependency><!-- 集成hutool工具类简便操作 --><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.3.10</version></dependency>
三、编写核心SSE Client
@Slf4j
@Component
public class SseClient {private static final Map<String, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();/*** 创建连接*/public SseEmitter createSse(String uid) {//默认30秒超时,设置为0L则永不超时SseEmitter sseEmitter = new SseEmitter(0l);//完成后回调sseEmitter.onCompletion(() -> {log.info("[{}]结束连接...................", uid);sseEmitterMap.remove(uid);});//超时回调sseEmitter.onTimeout(() -> {log.info("[{}]连接超时...................", uid);});//异常回调sseEmitter.onError(throwable -> {try {log.info("[{}]连接异常,{}", uid, throwable.toString());sseEmitter.send(SseEmitter.event().id(uid).name("发生异常!").data("发生异常请重试!").reconnectTime(3000));sseEmitterMap.put(uid, sseEmitter);} catch (IOException e) {e.printStackTrace();}});try {sseEmitter.send(SseEmitter.event().reconnectTime(5000));} catch (IOException e) {e.printStackTrace();}sseEmitterMap.put(uid, sseEmitter);log.info("[{}]创建sse连接成功!", uid);return sseEmitter;}/*** 给指定用户发送消息**/public boolean sendMessage(String uid,String messageId, String message) {if (StrUtil.isBlank(message)) {log.info("参数异常,msg为null", uid);return false;}SseEmitter sseEmitter = sseEmitterMap.get(uid);if (sseEmitter == null) {log.info("消息推送失败uid:[{}],没有创建连接,请重试。", uid);return false;}try {sseEmitter.send(SseEmitter.event().id(messageId).reconnectTime(1*60*1000L).data(message));log.info("用户{},消息id:{},推送成功:{}", uid,messageId, message);return true;}catch (Exception e) {sseEmitterMap.remove(uid);log.info("用户{},消息id:{},推送异常:{}", uid,messageId, e.getMessage());sseEmitter.complete();return false;}}/*** 断开* @param uid*/public void closeSse(String uid){if (sseEmitterMap.containsKey(uid)) {SseEmitter sseEmitter = sseEmitterMap.get(uid);sseEmitter.complete();sseEmitterMap.remove(uid);}else {log.info("用户{} 连接已关闭",uid);}}}
-
创建SSE 端点
创建一个SseEmitter,用uid进行标识,uid可以是用户标识符,也可以是业务标识符。可以理解为通信信道标识。
-
通过端点发送事件
可以定时或在事件发生时调用sseEmitter.send()方法来发送事件。
-
关闭端点连接
四、编写Controller
@Controller
public class IndexAction {@Autowiredprivate SseClient sseClient;@GetMapping("/")public String index(ModelMap model) {String uid = IdUtil.fastUUID();model.put("uid",uid);return "index";}@CrossOrigin@GetMapping("/createSse")public SseEmitter createConnect(String uid) {return sseClient.createSse(uid);}@CrossOrigin@GetMapping("/sendMsg")@ResponseBodypublic String sseChat(String uid) {for (int i = 0; i < 10; i++) {sseClient.sendMessage(uid, "no"+i,IdUtil.fastUUID());}return "ok";}/*** 关闭连接*/@CrossOrigin@GetMapping("/closeSse")public void closeConnect(String uid ){sseClient.closeSse(uid);}
}
1,打开页面默认页面,传递端点标识。
2,连接端点(/createSse),页面需要使用
3,通过ajax(/sendMsg),触发后端业务(循环十条数据发往页面),向页面发送消息。
4,主动关闭连接(/closeSse)
五、前端接收与处理
-
HTML & JavaScript
在前端页面,使用EventSource API订阅SSE endpoint:
Html
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Title</title> </head> <body> <div id="con"></div> <script>let chat = document.getElementById("con");if (window.EventSource) {//创建sseeventSource = new EventSource(`/createSse?uid=${uid}`);eventSource.onopen = function (event) {console.log('SSE链接成功');}eventSource.onmessage = function (event) {if(event.data){chat.innerHTML += event.data + '<br/>';//console.log('后端返回的数据:', data.value);}}eventSource.onerror = (error) => {console.log('SSE链接失败');};} else {alert("你的浏览器不支持SSE");} </script> </body> </html>
在这个例子中,前端每接收到一次SSE推送的事件,就会在id为"con"的元素中追加数据。
六、注意事项
- 当客户端断开连接时,SseEmitter会抛出IOException,所以务必捕获并处理这种异常,通常情况下我们会调用
emitter.complete()
或emitter.completeWithError()
来关闭SseEmitter。 - SSE连接是持久性的,长时间保持连接可能需要处理超时和重连问题。
- 考虑到资源消耗,对于大量的并发客户端,可能需要采用连接池或者其他优化策略。
总结,Spring Boot中利用SSE实现实时数据推送既简单又实用,特别适合实时更新频率不高、实时性要求不严苛的场景。同时,在高并发场景下需要注意资源管理和优化策略的选择。