java通過(guò)SSE實(shí)現(xiàn)消息推送
一、什么是SSE
嚴(yán)格地說(shuō),HTTP 協(xié)議無(wú)法做到服務(wù)器主動(dòng)推送信息。但是,有一種變通方法,就是服務(wù)器向客戶端聲明,接下來(lái)要發(fā)送的是流信息(streaming)。
也就是說(shuō),發(fā)送的不是一次性的數(shù)據(jù)包,而是一個(gè)數(shù)據(jù)流,會(huì)連續(xù)不斷地發(fā)送過(guò)來(lái)。這時(shí),客戶端不會(huì)關(guān)閉連接,會(huì)一直等著服務(wù)器發(fā)過(guò)來(lái)的新的數(shù)據(jù)流,視頻播放就是這樣的例子。本質(zhì)上,這種通信就是以流信息的方式,完成一次用時(shí)很長(zhǎng)的下載。
SSE 就是利用這種機(jī)制,使用流信息向?yàn)g覽器推送信息。它基于 HTTP 協(xié)議,目前除了 IE/Edge,其他瀏覽器都支持。
二、如何在java中實(shí)現(xiàn)SSE
在Spring Boot項(xiàng)目中,無(wú)需額外引入特定的依賴,因?yàn)镾pring Web MVC模塊已經(jīng)內(nèi)置了對(duì)SSE的支持。
1、編寫SSE服務(wù),來(lái)進(jìn)行創(chuàng)建鏈接和發(fā)送消息
package com.smart3dmap.prjpntmanage.service;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Service
public class SSEService {
private static final Map<String,SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();
public SseEmitter crateSse(String uid) {
SseEmitter sseEmitter = new SseEmitter(0L);
sseEmitter.onCompletion(() -> {
log.info("[{}]結(jié)束鏈接" , uid);
sseEmitterMap.remove(uid);
});
sseEmitter.onTimeout(() -> {
log.info("[{}]鏈接超時(shí)",uid);
});
sseEmitter.onError(throwable -> {
try{
log.info("[{}]鏈接異常,{}",uid,throwable.toString());
sseEmitter.send(SseEmitter.event()
.id(uid)
.name("發(fā)生異常")
.data("發(fā)生異常請(qǐng)重試")
.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("[{}]創(chuàng)建sse連接成功!",uid);
return sseEmitter;
}
public boolean sendMessage(String uid,String messageId,String message){
if(StringUtils.isEmpty(message)){
log.info("[{}]參數(shù)異常,msg為空",uid);
return false;
}
SseEmitter sseEmitter = sseEmitterMap.get(uid);
if(sseEmitter == null){
log.info("[{}]sse連接不存在",uid);
return false;
}
try{
sseEmitter.send(SseEmitter.event().id(messageId).reconnectTime(60000).data(message));
log.info("用戶{},消息ID:{},推送成功:{}",uid,messageId,message);
return true;
}catch (IOException e){
sseEmitterMap.remove(uid);
log.info("用戶{},消息ID:{},消息推送失?。簕}",uid,messageId,message);
sseEmitter.complete();
return false;
}
}
public void closeSse(String uid){
if(sseEmitterMap.containsKey(uid)){
SseEmitter sseEmitter = sseEmitterMap.get(uid);
sseEmitter.complete();
sseEmitterMap.remove(uid);
}else {
log.info("用戶{}連接已關(guān)閉",uid);
}
}
}2、編寫對(duì)應(yīng)的Controller來(lái)實(shí)現(xiàn)具體業(yè)務(wù)
package com.test.controller;
import cn.hutool.core.util.IdUtil;
import com.test.service.SSEService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
@Controller
@RequestMapping("test")
public class TestController {
@Autowired
private SSEService sseService;
@GetMapping("createSse")
@CrossOrigin
public SseEmitter createSse(String uid)
{
return sseService.crateSse(uid);
}
@GetMapping("sendMsg")
@ResponseBody
@CrossOrigin
public String sseChat(String uid){
for (int i = 0; i < 10; i++) {
sseService.sendMessage(uid,"消息"+i,IdUtil.fastUUID().replace("-",""));
}
return "OK";
}
@GetMapping("closeSse")
@CrossOrigin
public void closeSse(String uid){
sseService.closeSse(uid);
}
}三、前端實(shí)現(xiàn)消息的監(jiān)聽
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>SSE消息推送監(jiān)聽</title>
</head>
<body>
<div id="conMsg"></div>
<script>
let uid = 1;
let chat = document.getElementById("conMsg");
if(window.EventSource){
var eventSource = new EventSource(`http://127.0.0.1:8090/test/createSse?uid=${uid}`);
eventSource.onopen = ()=>{
console.log("鏈接成功");
}
eventSource.onmessage = (ev)=>{
if(ev.data){
chat.innerHTML += ev.data+"<br>";
}
}
eventSource.onerror = ()=>{
console.log("sse鏈接失敗")
}
}else{
alert("當(dāng)前瀏覽器不支持sse")
}
</script>
</body>
</html>到此這篇關(guān)于java通過(guò)SSE實(shí)現(xiàn)消息推送的文章就介紹到這了,更多相關(guān)java SSE消息推送內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot實(shí)現(xiàn)i18n國(guó)際化的兩種企業(yè)級(jí)方案
在全球化業(yè)務(wù)場(chǎng)景下,系統(tǒng)適配多語(yǔ)言已成為標(biāo)配需求,SpringBoot作為主流的Java開發(fā)框架,提供了完善的國(guó)際化,本文將從實(shí)戰(zhàn)角度出發(fā),完整講解兩種企業(yè)級(jí)i18n實(shí)現(xiàn)方案:基于配置文件的靜態(tài)實(shí)現(xiàn)和基于數(shù)據(jù)庫(kù)的動(dòng)態(tài)實(shí)現(xiàn),需要的朋友可以參考下2026-01-01
Java生成動(dòng)態(tài)版驗(yàn)證碼的方法實(shí)例
這篇文章主要給大家介紹了利用Java生成動(dòng)態(tài)版驗(yàn)證碼的方法實(shí)例,本文生成的是GIF格式 + 干擾元素,讓驗(yàn)證碼破解難度又上了一個(gè)層次,文中給出了詳細(xì)的示例代碼,并在文末給出了完整的實(shí)例代碼供大家下載學(xué)習(xí),需要的朋友們下面來(lái)一起看看吧。2017-04-04
Springboot自定義mvc組件如何實(shí)現(xiàn)
這篇文章主要介紹了Springboot自定義mvc組件如何實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11
java 查詢oracle數(shù)據(jù)庫(kù)所有表DatabaseMetaData的用法(詳解)
下面小編就為大家?guī)?lái)一篇java 查詢oracle數(shù)據(jù)庫(kù)所有表DatabaseMetaData的用法(詳解)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-11-11
IDEA的Swing可視化插件JFormDesigner詳解
JFormDesigner是一個(gè)專業(yè)的軟件應(yīng)用程序,專門用于幫助您開發(fā)Java?Swing用戶界面,而無(wú)需具備編程技能。它可作為獨(dú)立實(shí)用程序使用,也可以將其用作各種IDE的插件,本文給大家介紹idea?Swing可視化插件,感興趣的朋友一起看看吧2022-06-06
Springboot jar文件如何打包zip在linux環(huán)境運(yùn)行
這篇文章主要介紹了Springboot jar文件如何打包zip在linux環(huán)境運(yùn)行,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02
Spring?Security?2026?構(gòu)建安全、可靠的企業(yè)應(yīng)用實(shí)踐指南
文章概述了SpringSecurity2026的核心特性、認(rèn)證與授權(quán)的最佳實(shí)踐、安全防護(hù)和會(huì)話管理措施,以及在微服務(wù)架構(gòu)中的應(yīng)用,并探討了未來(lái)的安全趨勢(shì),強(qiáng)調(diào)通過(guò)合理配置和實(shí)踐構(gòu)建更安全的企業(yè)應(yīng)用2026-04-04

