0.需求
? ? ? ? 后端定時向前端看板推送數(shù)據(jù),每10秒或者30秒推送一次。
1.前言知識
????????HTTP協(xié)議是一個應(yīng)用層協(xié)議,它的特點(diǎn)是無狀態(tài)、無連接和單向的。在HTTP協(xié)議中,客戶端發(fā)起請求,服務(wù)器則對請求進(jìn)行響應(yīng)。這種請求-響應(yīng)的模式意味著服務(wù)器無法主動向客戶端發(fā)送消息。
????????這種單向通信的缺點(diǎn)在于,如果服務(wù)器有持續(xù)的狀態(tài)變化,客戶端要獲取這些變化就很困難。為了解決這個問題,許多Web應(yīng)用采用了一種叫做長輪詢的技術(shù),即頻繁地通過AJAX和XML發(fā)起異步請求來檢查服務(wù)器的狀態(tài)。但這種方式效率較低,也很浪費(fèi)資源,因?yàn)樾枰粩嗟亟⑦B接或保持連接打開。
????????而WebSocket則是一種不同的通信協(xié)議,它允許客戶端和服務(wù)器之間進(jìn)行全雙工通信。這意味著無論是客戶端還是服務(wù)器,都可以隨時通過已經(jīng)建立的連接向?qū)Ψ桨l(fā)送數(shù)據(jù)。而且,WebSocket只需要建立一次連接就可以保持通信狀態(tài),無需頻繁地建立和斷開連接,因此效率大大提高。
????????總結(jié)一下,HTTP協(xié)議雖然廣泛應(yīng)用,但因其單向通信的局限性,在處理服務(wù)器狀態(tài)持續(xù)變化的情況時顯得力不從心。而WebSocket協(xié)議則通過全雙工通信的方式,有效地解決了這個問題,提高了通信效率。
2.后端實(shí)現(xiàn)
2.1不帶參數(shù)
2.1.1添加依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2.1.2websocket配置:
/**
* 通過EnableWebSocketMessageBroker
* 開啟使用STOMP協(xié)議來傳輸基于代理(message broker)的消息,
* 此時瀏覽器支持使用@MessageMapping 就像支持@RequestMapping一樣。
*/
//WebSocket的配置類
@Configuration
//開啟對WebSocket的支持
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer{
/**
* 注冊stomp的端點(diǎn)
* 注冊一個STOMP協(xié)議的節(jié)點(diǎn),并映射到指定的URL
*/
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
//endPoint 注冊協(xié)議節(jié)點(diǎn),并映射指定的URl點(diǎn)對點(diǎn)-用
//注冊一個名字為"/endpointSocket" 的endpoint,并指定 SockJS協(xié)議。
//允許使用socketJs方式訪問,訪問點(diǎn)為webSocketServer,允許跨域
//連接前綴
//配置客戶端嘗試連接地址
//廣播
registry.addEndpoint("/ws/public").setAllowedOriginPatterns("*").withSockJS();
//點(diǎn)對點(diǎn)
registry.addEndpoint("/ws/private").setAllowedOriginPatterns("*").withSockJS();
}
/**
* 通過實(shí)現(xiàn) WebSocketMessageBrokerConfigurer 接口和加上 @EnableWebSocketMessageBroker 來進(jìn)行 stomp 的配置與注解掃描。
* 其中覆蓋 registerStompEndpoints 方法來設(shè)置暴露的 stomp 的路徑,其它一些跨域、客戶端之類的設(shè)置。
* 覆蓋 configureMessageBroker 方法來進(jìn)行節(jié)點(diǎn)的配置。
* 其中 enableSimpleBroker配置的廣播節(jié)點(diǎn),也就是服務(wù)端發(fā)送消息,客戶端訂閱就能接收消息的節(jié)點(diǎn)。
* 覆蓋setApplicationDestinationPrefixes方法,設(shè)置客戶端向服務(wù)端發(fā)送消息的節(jié)點(diǎn)。
* 覆蓋 setUserDestinationPrefix 方法,設(shè)置一對一通信的節(jié)點(diǎn)。
*
* @param registry
*/
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
//配置消息代理,即設(shè)置廣播節(jié)點(diǎn)
registry.enableSimpleBroker("/topic","/user");
//后端接收的主題前綴,即客戶端向服務(wù)端發(fā)送消息需要有/client前綴
// registry.setApplicationDestinationPrefixes("/client");
//指定用戶發(fā)送(一對一)的前綴/user/
// registry.setUserDestinationPrefix("/user/");
}
}
2.1.3后端代碼?
????????一個是訂閱請求接口,一個是關(guān)閉定時任務(wù)接口。這段代碼實(shí)現(xiàn)了一個基于WebSocket的定時推送機(jī)制,允許通過發(fā)送WebSocket消息來啟動和關(guān)閉定時任務(wù),從而每30秒推送一次數(shù)據(jù)。
/**
* 看板接口-不帶參數(shù)
* 定時任務(wù)(每30秒推送一次)
*/
@MessageMapping("/backend/produce/summary")
public void pushProduceSummary() {
log.info("服務(wù)端接收到消息: {}");
if (scheduledTask.get("pushProduceSummary") == null) {
ScheduledFuture<?> future = executorService.scheduleAtFixedRate(() -> {
ProgressVO progressVO = progressSummaryService.summary();
String destination = "/topic/backend/produce/summary";
template.convertAndSend(destination, progressVO);
log.info("已推送信息,每30秒推送一次:{}");
}, 1, 30, TimeUnit.SECONDS);
scheduledTask.put("pushProduceSummary", future);
} else {
log.info("定時任務(wù)已開始!");
}
}
/**
* 關(guān)閉/backend/produce/summary接口的定時任務(wù)
*
* @author weiq
*/
@MessageMapping("/close/backend/produce/summary")
public void cancelPushProduceSummary() {
scheduledTask.forEach((StringKey, future) -> {
if (future != null && !future.isCancelled() && StringKey.equals("pushProduceSummary")) {
// 清除定時任務(wù)的引用
scheduledTask.remove("pushProduceSummary");
boolean cancel = future.cancel(true);
if (cancel) {
log.info("已關(guān)閉定時任務(wù)Key={}",StringKey);
}else{
log.info("失敗關(guān)閉定時任務(wù)Key={}",StringKey);
}
}
});
}
2.2帶參數(shù)
????????一個是訂閱請求接口,一個是關(guān)閉定時任務(wù)接口。文章來源:http://www.zghlxwxcb.cn/news/detail-857290.html
- 當(dāng)客戶端向?
/backend/produce/runEfficiency/{startTime}/{endTime}
?這個 WebSocket 地址發(fā)送消息時,pushProduceRunEfficiency
?方法會被調(diào)用。 - 這個方法會檢查是否已有一個定時任務(wù)在運(yùn)行。如果沒有,它會創(chuàng)建一個新的定時任務(wù),該任務(wù)會每30秒從?
runEfficiencyService
?獲取運(yùn)行效率數(shù)據(jù),并通過 WebSocket 發(fā)送到指定的主題(destination
)。 - 前端(或任何監(jiān)聽該主題的 WebSocket 客戶端)需要事先訂閱這個主題,以便能夠接收后端發(fā)送的數(shù)據(jù)。
/**
* (看板)
*定時任務(wù)(每30秒推送一次)
* @param startTime
* @param endTime
*/
@MessageMapping("/backend/produce/runEfficiency/{startTime}/{endTime}")
public void pushProduceRunEfficiency(@DestinationVariable Long startTime, @DestinationVariable Long endTime) {
log.info("服務(wù)端接收到消息: startTime={},endTime={}", startTime, endTime);
if (scheduledTask.get("pushProduceRunEfficiency") == null) {
ScheduledFuture<?> future = executorService.scheduleAtFixedRate(() -> {
List<RunVO> runVOList = runEfficiencyService.run(startTime, endTime);
String destination = "/topic/backend/produce/runEfficiency" + "/" + startTime + "/" + endTime;
template.convertAndSend(destination, runVOList);
log.info("已推送信息,每30秒推送一次:{}");
}, 1, 30, TimeUnit.SECONDS);
scheduledTask.put("pushProduceRunEfficiency", future);
}else{
log.info("定時任務(wù)已開啟!");
}
}
/**
* 關(guān)閉/backend/produce/runEfficiency/{startTime}/{endTime}接口的定時任務(wù)
*
* @author weiq
*/
@MessageMapping("/close/backend/produce/runEfficiency")
public void cancelPushProduceRunEfficiency() {
scheduledTask.forEach((StringKey, future) -> {
if (future != null && !future.isCancelled() && StringKey.equals("pushProduceRunEfficiency")) {
// 清除定時任務(wù)的引用
scheduledTask.remove("pushProduceRunEfficiency");
boolean cancel = future.cancel(true);
if (cancel) {
log.info("已關(guān)閉定時任務(wù)Key={}",StringKey);
} else {
log.info("失敗定時任務(wù)Key={}",StringKey);
}
}
});
}
3.前端驗(yàn)證
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script>
<script src="https://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
<script src="https://code.jquery.com/jquery-3.2.0.min.js"
integrity="sha256-JAW99MJVpJBGcbzEuXk4Az05s/XyDdBomFqNlM3ic+I=" crossorigin="anonymous"></script>
<script type="text/javascript">
var stompClient = null;
function setConnected(connected) {
document.getElementById("connect").disabled = connected;
document.getElementById("disconnect").disabled = !connected;
$("#response").html();
}
function connect() {
console.log("開始連接吧")
var socket = new SockJS("http://localhost:8501/ws/public");
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected: ' + frame);
//前端連接完成后,開始訂閱主題
// stompClient.subscribe('/topic/all', function (response) {
stompClient.subscribe('/topic/backend/produce/summary', function (response) {
var responseData = document.getElementById('responseData');
var p = document.createElement('p');
p.style.wordWrap = 'break-word';
p.appendChild(document.createTextNode(response.body));
responseData.appendChild(p);
});
}, {});
}
function disconnect() {
if (stompClient != null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
//請求地址,向WebSocket 地址發(fā)送消息
function sendMsg() {
var content = document.getElementById('content').value;
// stompClient.send("/all", {}, JSON.stringify({'content': content}));
stompClient.send("/backend/produce/summary", {}, JSON.stringify({'content': content }));
}
//關(guān)閉WebSocket 請求的定時任務(wù)
function sendMsg1() {
var content = document.getElementById('content').value;
// stompClient.send("/all", {}, JSON.stringify({'content': content}));
stompClient.send("/close/backend/produce/summary", {}, JSON.stringify({'content': content }));
}
// function sendMsg1() {
// var content = document.getElementById('content').value;
// // stompClient.send("/all", {}, JSON.stringify({'content': content}));
// stompClient.send("/close/scene/stepActualTime/128", {}, JSON.stringify({'content': content }));
// }
//
// function sendMsg2() {
// var content = document.getElementById('content').value;
// // stompClient.send("/all", {}, JSON.stringify({'content': content}));
// stompClient.send("/close/scene/stepActualTime/219", {}, JSON.stringify({'content': content }));
// }
</script>
</head>
<body notallow="disconnect()">
<noscript>
<h2 style="color: #ff0000">
Seems your browser doesn't support Javascript! Websocket relies on Javascript being
enabled. Please enable
Javascript and reload this page!
</h2>
</noscript>
<div>
<div>
<labal>連接廣播頻道</labal>
<button id="connect" onclick="connect()">Connect</button>
<labal>取消連接</labal>
<button id="disconnect" disabled="disabled" onclick="disconnect()">Disconnect</button>
</div>
<div id="conversationDiv">
<labal>廣播消息</labal>
<input type="text" id="content"/>
<button id="sendMsg" onclick="sendMsg();">Send</button>
</div>
<div id="conversationDiv1">
<labal>廣播消息1</labal>
<input type="text" id="content1"/>
<button id="sendMsg1" onclick="sendMsg1();">Send</button>
</div>
<!-- <div id="conversationDiv2">-->
<!-- <labal>廣播消息2</labal>-->
<!-- <input type="text" id="content2"/>-->
<!-- <button id="sendMsg2" onclick="sendMsg2();">Send</button>-->
<!-- </div>-->
<div>
<labal>接收到的消息:</labal>
<p id="responseData"></p>
</div>
</div>
</body>
</html>
后端啟動,打開HTML測試頁面,可看到運(yùn)行結(jié)果!文章來源地址http://www.zghlxwxcb.cn/news/detail-857290.html
到了這里,關(guān)于前端訂閱后端推送WebSocket定時任務(wù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!