Springboot集成SSE實現(xiàn)單工通信消息推送流程詳解
前言
通常在一些web項目中,會涉及到想客戶端推送消息,常見的有Ajax輪詢、webSocket,本篇文章主要使用Springboot集成SSE實現(xiàn)向客戶端持續(xù)推送信息。
SSE簡介
服務(wù)發(fā)送事件SSE(Sever-Sent Event),就是基于 HTTP 的技術(shù),瀏覽器向服務(wù)器發(fā)送一個保持長連接HTTP請求,服務(wù)器單向地向客戶端以流形式持續(xù)傳輸數(shù)據(jù) 。這樣可以節(jié)約網(wǎng)絡(luò)資源,不需要建立新連接。
優(yōu)點
服務(wù)端不需要其他的類庫,開發(fā)難度較低。
不用每次建立新連接,延遲較低。 數(shù)據(jù)通過簡單且廣泛使用的HTTP協(xié)議而不是專有協(xié)議進行同步。
對重新建立連接和事件ID功能的內(nèi)置支持。
對于利用單向通信的應(yīng)用程序和服務(wù)非常有用。
缺點
客戶端越多連接越多,會占用服務(wù)器大量內(nèi)存和連接數(shù)。
SSE只支持UTF-8編碼,不支持二進制數(shù)據(jù)。
對最大打開連接數(shù)的嚴格限制可能使事情變得困難,每個瀏覽器都設(shè)置了限制。
SSE是單向的。
Springboot集成SSE簡約版
客戶端發(fā)送請求到服務(wù)端,服務(wù)端以流的形式不斷向客戶端推送數(shù)據(jù)示例,增加帥氣值。
xml依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
html代碼:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Springboot集成SSE簡約版</title> <script type="text/javascript"> let source = new EventSource('/get'); source.onmessage = function (event) { console.info(event.data); document.getElementById('text').innerText = event.data }; </script> </head> <body> <div id="text"></div> </body> </html>
后端代碼:
@RequestMapping(value = "/get", produces = "text/event-stream;charset=UTF-8") public void push(HttpServletResponse response) { response.setContentType("text/event-stream"); response.setCharacterEncoding("utf-8"); int i = 0; while (true) { try { Thread.sleep(1000); PrintWriter pw = response.getWriter(); //注意返回數(shù)據(jù)必須以data:開頭,"\n\n"結(jié)尾 pw.write("data:xdm帥氣值加" + i + "\n\n"); pw.flush(); //檢測異常時斷開連接 if (pw.checkError()) { log.error("客戶端斷開連接"); return; } } catch (Exception e) { e.printStackTrace(); } i++; } }
效果:
Springboot集成SSE升級版
演示SSE的連接建立、接收數(shù)據(jù)和異常情況監(jiān)聽處理。
注:若瀏覽器不兼容在頁面引入evensource.js。
<script src=/eventsource-polyfill.js></script>
客戶端代碼:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title> Springboot集成SSE升級版</title> </head> <script> let source = null; const clientId = new Date().getTime(); if (!!window.EventSource) { source = new EventSource('/sse/create?clientId=' + clientId); //建立連接 source.onopen = function (event) { setMessageInnerHTML("建立連接" + event); } //接收數(shù)據(jù) source.onmessage = function (event) { setMessageInnerHTML(event.data); } //錯誤監(jiān)聽 source.onerror = function (event) { if (event.readyState === EventSource.CLOSED) { setMessageInnerHTML("連接關(guān)閉"); } else { console.log(event); } } } else { setMessageInnerHTML("瀏覽器不支持SSE"); } window.onbeforeunload = function () { close(); }; // 關(guān)閉 function close() { source.close(); const httpRequest = new XMLHttpRequest(); httpRequest.open('GET', '/sse/close/?clientId=' + clientId, true); httpRequest.send(); console.log("close"); } // 顯示消息 function setMessageInnerHTML(innerHTML) { document.getElementById('text').innerHTML += innerHTML + '<br/>'; } </script> <body> <button onclick="close()">關(guān)閉連接</button> <div id="text"></div> </body> </html>
服務(wù)端代碼:
private static Map<String, SseEmitter> cache = new ConcurrentHashMap<>(); String clientId; int sseId; @GetMapping("/create") public SseEmitter create(@RequestParam(name = "clientId", required = false) String clientId) { // 設(shè)置超時時間,0表示不過期。默認30000毫秒 //可以在客戶端一直斷網(wǎng)、直接關(guān)閉頁面但未提醒后端的情況下,服務(wù)端在一定時間等待后自動關(guān)閉網(wǎng)絡(luò)連接 SseEmitter sseEmitter = new SseEmitter(0L); // 是否需要給客戶端推送ID if (Strings.isBlank(clientId)) { clientId = UUID.randomUUID().toString(); } this.clientId = clientId; cache.put(clientId, sseEmitter); log.info("sse連接,當(dāng)前客戶端:{}", clientId); return sseEmitter; } @Scheduled(cron = "0/3 * * * * ? ") public void pushMessage() { try { sseId++; SseEmitter sseEmitter = cache.get(clientId); sseEmitter.send( SseEmitter .event() .data("帥氣值暴增" + sseId) .id("" + sseId) .reconnectTime(3000) ); } catch (Exception e) { log.error(e.getMessage()); sseId--; } } @GetMapping("/close") public void close(String clientId) { SseEmitter sseEmitter = cache.get(clientId); if (sseEmitter != null) { sseEmitter.complete(); cache.remove(clientId); } }
方法參數(shù)說明:
SseEmitter
.event()
.data("帥氣值暴增" + sseId)
.id("" + sseId)
.reconnectTime(3000)
SseEmitter.event()
用來得到一個記錄數(shù)據(jù)的容器。
.data("帥氣值暴增" + sseId)
發(fā)送給客戶端的數(shù)據(jù)。
.id("" + sseId)
記錄發(fā)送數(shù)據(jù)的標識,服務(wù)端可以通過HttpServletRequest的請求頭中拿到這個id,判斷是否中間有誤漏發(fā)數(shù)據(jù)。
.reconnectTime(3000)
定義在網(wǎng)絡(luò)連接斷開后,客戶端向后端發(fā)起重連的時間間隔(以毫秒為單位)。
效果:
參考資料:
到此這篇關(guān)于Springboot集成SSE實現(xiàn)單工通信消息推送流程詳解的文章就介紹到這了,更多相關(guān)Springboot消息推送內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
mybatisplus實現(xiàn)自動創(chuàng)建/更新時間的項目實踐
Mybatis-Plus提供了自動填充功能,可以通過實現(xiàn)MetaObjectHandler接口來實現(xiàn)自動更新時間的功能,本文就來介紹一下mybatisplus實現(xiàn)自動創(chuàng)建/更新時間的項目實踐,感興趣的可以了解下2024-01-01java根據(jù)方法名稱取得反射方法的參數(shù)類型示例
利用java反射原理調(diào)用方法時,常先需要傳入方法參數(shù)數(shù)組才能取得方法。該方法參數(shù)數(shù)組采用動態(tài)取得的方式比較合適2014-02-02