Springboot集成SSE實(shí)現(xiàn)單工通信消息推送流程詳解
前言
通常在一些web項(xiàng)目中,會(huì)涉及到想客戶端推送消息,常見(jiàn)的有Ajax輪詢、webSocket,本篇文章主要使用Springboot集成SSE實(shí)現(xiàn)向客戶端持續(xù)推送信息。
SSE簡(jiǎn)介
服務(wù)發(fā)送事件SSE(Sever-Sent Event),就是基于 HTTP 的技術(shù),瀏覽器向服務(wù)器發(fā)送一個(gè)保持長(zhǎng)連接HTTP請(qǐng)求,服務(wù)器單向地向客戶端以流形式持續(xù)傳輸數(shù)據(jù) 。這樣可以節(jié)約網(wǎng)絡(luò)資源,不需要建立新連接。
優(yōu)點(diǎn)
服務(wù)端不需要其他的類庫(kù),開(kāi)發(fā)難度較低。
不用每次建立新連接,延遲較低。 數(shù)據(jù)通過(guò)簡(jiǎn)單且廣泛使用的HTTP協(xié)議而不是專有協(xié)議進(jìn)行同步。
對(duì)重新建立連接和事件ID功能的內(nèi)置支持。
對(duì)于利用單向通信的應(yīng)用程序和服務(wù)非常有用。
缺點(diǎn)
客戶端越多連接越多,會(huì)占用服務(wù)器大量?jī)?nèi)存和連接數(shù)。
SSE只支持UTF-8編碼,不支持二進(jìn)制數(shù)據(jù)。
對(duì)最大打開(kāi)連接數(shù)的嚴(yán)格限制可能使事情變得困難,每個(gè)瀏覽器都設(shè)置了限制。
SSE是單向的。
Springboot集成SSE簡(jiǎn)約版
客戶端發(fā)送請(qǐng)求到服務(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簡(jiǎn)約版</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();
//注意返回?cái)?shù)據(jù)必須以data:開(kāi)頭,"\n\n"結(jié)尾
pw.write("data:xdm帥氣值加" + i + "\n\n");
pw.flush();
//檢測(cè)異常時(shí)斷開(kāi)連接
if (pw.checkError()) {
log.error("客戶端斷開(kāi)連接");
return;
}
} catch (Exception e) {
e.printStackTrace();
}
i++;
}
}
效果:

Springboot集成SSE升級(jí)版
演示SSE的連接建立、接收數(shù)據(jù)和異常情況監(jiān)聽(tīng)處理。
注:若瀏覽器不兼容在頁(yè)面引入evensource.js。
<script src=/eventsource-polyfill.js></script>
客戶端代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title> Springboot集成SSE升級(jí)版</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);
}
//錯(cuò)誤監(jiān)聽(tīng)
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è)置超時(shí)時(shí)間,0表示不過(guò)期。默認(rèn)30000毫秒
//可以在客戶端一直斷網(wǎng)、直接關(guān)閉頁(yè)面但未提醒后端的情況下,服務(wù)端在一定時(shí)間等待后自動(dòng)關(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ù)說(shuō)明:
SseEmitter
.event()
.data("帥氣值暴增" + sseId)
.id("" + sseId)
.reconnectTime(3000)
SseEmitter.event()
用來(lái)得到一個(gè)記錄數(shù)據(jù)的容器。
.data("帥氣值暴增" + sseId)
發(fā)送給客戶端的數(shù)據(jù)。
.id("" + sseId)
記錄發(fā)送數(shù)據(jù)的標(biāo)識(shí),服務(wù)端可以通過(guò)HttpServletRequest的請(qǐng)求頭中拿到這個(gè)id,判斷是否中間有誤漏發(fā)數(shù)據(jù)。
.reconnectTime(3000)
定義在網(wǎng)絡(luò)連接斷開(kāi)后,客戶端向后端發(fā)起重連的時(shí)間間隔(以毫秒為單位)。
效果:

參考資料:
到此這篇關(guān)于Springboot集成SSE實(shí)現(xiàn)單工通信消息推送流程詳解的文章就介紹到這了,更多相關(guān)Springboot消息推送內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot3中Spring?WebFlux?SSE服務(wù)器發(fā)送事件的實(shí)現(xiàn)步驟
- Android?Springboot?實(shí)現(xiàn)SSE通信案例詳解
- Spring?Boot整合Kafka+SSE實(shí)現(xiàn)實(shí)時(shí)數(shù)據(jù)展示
- Spring Boot中使用Server-Sent Events (SSE) 實(shí)現(xiàn)實(shí)時(shí)數(shù)據(jù)推送教程
- springboot-assembly自定義打包全過(guò)程
- Spring Boot整合SSE實(shí)時(shí)通信的問(wèn)題小結(jié)
相關(guān)文章
基于令牌桶的限流器注解的簡(jiǎn)單實(shí)現(xiàn)詳解
令牌桶算法是一種常用的流量控制算法,用于限制請(qǐng)求或事件的發(fā)生速率,這篇文章主要介紹了如何基于令牌桶實(shí)現(xiàn)限流器注解,需要的可以參考一下2023-08-08
mybatisplus實(shí)現(xiàn)自動(dòng)創(chuàng)建/更新時(shí)間的項(xiàng)目實(shí)踐
Mybatis-Plus提供了自動(dòng)填充功能,可以通過(guò)實(shí)現(xiàn)MetaObjectHandler接口來(lái)實(shí)現(xiàn)自動(dòng)更新時(shí)間的功能,本文就來(lái)介紹一下mybatisplus實(shí)現(xiàn)自動(dòng)創(chuàng)建/更新時(shí)間的項(xiàng)目實(shí)踐,感興趣的可以了解下2024-01-01
java根據(jù)方法名稱取得反射方法的參數(shù)類型示例
利用java反射原理調(diào)用方法時(shí),常先需要傳入方法參數(shù)數(shù)組才能取得方法。該方法參數(shù)數(shù)組采用動(dòng)態(tài)取得的方式比較合適2014-02-02
MyBatis-Plus 查詢返回實(shí)體對(duì)象還是map
這篇文章主要介紹了MyBatis-Plus 查詢返回實(shí)體對(duì)象還是map,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
SpringBoot基于自定義注解實(shí)現(xiàn)切面編程
這篇文章主要介紹了SpringBoot基于自定義注解實(shí)現(xiàn)切面編程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11

