SpringBoot集成WebSocket實(shí)現(xiàn)后臺(tái)向前端推送信息
SpringBoot 集成 WebSocket,實(shí)現(xiàn)后臺(tái)向前端推送信息
在一次項(xiàng)目開(kāi)發(fā)中,使用到了Netty網(wǎng)絡(luò)應(yīng)用框架,以及MQTT進(jìn)行消息數(shù)據(jù)的收發(fā),這其中需要后臺(tái)來(lái)將獲取到的消息主動(dòng)推送給前端,于是就使用到了MQTT,特此記錄一下。
1、什么是websocket?
WebSocket協(xié)議是基于TCP的一種新的網(wǎng)絡(luò)協(xié)議。它實(shí)現(xiàn)了客戶(hù)端與服務(wù)器全雙工通信,學(xué)過(guò)計(jì)算機(jī)網(wǎng)絡(luò)都知道,既然是全雙工,就說(shuō)明了服務(wù)器可以主動(dòng)發(fā)送信息給客戶(hù)端 。這與我們的推送技術(shù)或者是多人在線(xiàn)聊天的功能不謀而合。
為什么不使用HTTP 協(xié)議呢?這是因?yàn)镠TTP是單工通信,通信只能由客戶(hù)端發(fā)起,客戶(hù)端請(qǐng)求一下,服務(wù)器處理一下,這就太麻煩了。于是websocket應(yīng)運(yùn)而生。
下面我們就直接開(kāi)始使用Springboot開(kāi)始整合。以下案例都在我自己的電腦上測(cè)試成功,你可以根據(jù)自己的功能進(jìn)行修改即可。
2、使用步驟
2.1 添加依賴(lài)
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.0</version> <relativePath/> </parent> <groupId>com.example</groupId> <artifactId>springboot-websocket</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot-websocket</name> <description>springboot-websocket</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.25</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
2.2 啟用Springboot對(duì)WebSocket的支持
package com.example.websocket; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; /** * @Description: 開(kāi)啟WebSocket支持 */ @Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
2.3 核心配置WebSocketServer
因?yàn)閃ebSocket是類(lèi)似客戶(hù)端服務(wù)端的形式(采用ws協(xié)議),那么這里的WebSocketServer其實(shí)就相當(dāng)于一個(gè)ws協(xié)議的Controller。
- @ServerEndpoint 注解是一個(gè)類(lèi)層次的注解,它的功能主要是將目前的類(lèi)定義成一個(gè)websocket服務(wù)器端,注解的值將被用于監(jiān)聽(tīng)用戶(hù)連接的終端訪(fǎng)問(wèn)URL地址,客戶(hù)端可以通過(guò)這個(gè)URL來(lái)連接到WebSocket服務(wù)器端。
- 新建一個(gè)ConcurrentHashMap webSocketMap 用于接收當(dāng)前userId的WebSocket,方便傳遞之間對(duì)userId進(jìn)行推送消息。
下面是具體業(yè)務(wù)代碼:
package com.example.websocket; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import javax.websocket.*; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.concurrent.CopyOnWriteArraySet; /** * @Description: * @ServerEndpoint 注解是一個(gè)類(lèi)層次的注解,它的功能主要是將目前的類(lèi)定義成一個(gè)websocket服務(wù)器端, * 注解的值將被用于監(jiān)聽(tīng)用戶(hù)連接的終端訪(fǎng)問(wèn)URL地址,客戶(hù)端可以通過(guò)這個(gè)URL來(lái)連接到WebSocket服務(wù)器端 */ @Component @Slf4j @Service @ServerEndpoint("/api/websocket/{sid}") public class WebSocketServer { // 靜態(tài)變量,用來(lái)記錄當(dāng)前在線(xiàn)連接數(shù)。應(yīng)該把它設(shè)計(jì)成線(xiàn)程安全的。 private static int onlineCount = 0; //concurrent包的線(xiàn)程安全Set,用來(lái)存放每個(gè)客戶(hù)端對(duì)應(yīng)的MyWebSocket對(duì)象。 private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>(); //與某個(gè)客戶(hù)端的連接會(huì)話(huà),需要通過(guò)它來(lái)給客戶(hù)端發(fā)送數(shù)據(jù) private Session session; //接收sid private String sid = ""; /** * 連接建立成功調(diào)用的方法 */ @OnOpen public void onOpen(Session session, @PathParam("sid") String sid) { this.session = session; //加入set中 webSocketSet.add(this); this.sid = sid; //在線(xiàn)數(shù)加1 addOnlineCount(); try { sendMessage("conn_success"); log.info("有新窗口開(kāi)始監(jiān)聽(tīng):" + sid + ",當(dāng)前在線(xiàn)人數(shù)為:" + getOnlineCount()); } catch (IOException e) { log.error("websocket IO Exception"); } } /** * 連接關(guān)閉調(diào)用的方法 */ @OnClose public void onClose() { //從set中刪除 webSocketSet.remove(this); //在線(xiàn)數(shù)減1 subOnlineCount(); //斷開(kāi)連接情況下,更新主板占用情況為釋放 log.info("釋放的sid為:" + sid); //這里寫(xiě)你 釋放的時(shí)候,要處理的業(yè)務(wù) log.info("有一連接關(guān)閉!當(dāng)前在線(xiàn)人數(shù)為" + getOnlineCount()); } /** * 收到客戶(hù)端消息后調(diào)用的方法 * * @Param message 客戶(hù)端發(fā)送過(guò)來(lái)的消息 */ @OnMessage public void onMessage(String message, Session session) { log.info("收到來(lái)自窗口" + sid + "的信息:" + message); //群發(fā)消息 for (WebSocketServer item : webSocketSet) { try { item.sendMessage(message); } catch (IOException e) { e.printStackTrace(); } } } /** * @Param session * @Param error */ @OnError public void onError(Session session, Throwable error) { log.error("發(fā)生錯(cuò)誤"); error.printStackTrace(); } /** * 實(shí)現(xiàn)服務(wù)器主動(dòng)推送 */ public void sendMessage(String message) throws IOException { this.session.getBasicRemote().sendText(message); } /** * 群發(fā)自定義消息 */ public static void sendInfo(String message, @PathParam("sid") String sid) throws IOException { log.info("推送消息到窗口" + sid + ",推送內(nèi)容:" + message); for (WebSocketServer item : webSocketSet) { try { //這里可以設(shè)定只推送給這個(gè)sid的,為null則全部推送 if (sid == null) { // item.sendMessage(message); } else if (item.sid.equals(sid)) { item.sendMessage(message); } } catch (IOException e) { continue; } } } public static synchronized int getOnlineCount() { return onlineCount; } public static synchronized void addOnlineCount() { WebSocketServer.onlineCount++; } public static synchronized void subOnlineCount() { WebSocketServer.onlineCount--; } public static CopyOnWriteArraySet<WebSocketServer> getWebSocketSet() { return webSocketSet; } }
配置文件
spring.web.resources.static-locations=classpath:/static
2.4 測(cè)試Controller
package com.example.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class IndexController { @RequestMapping("/index") public String index() { return "index.html"; } }
2.5 啟動(dòng)類(lèi)
package com.example; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
2.6 測(cè)試頁(yè)面index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Java后端WebSocket的Tomcat實(shí)現(xiàn)</title> <script type="text/javascript" src="js/jquery.min.js"></script> </head> <body> <h1>Welcome</h1> <input id="text" type="text" /> <button onclick="send()">發(fā)送消息</button> <hr/> <button onclick="closeWebSocket()">關(guān)閉WebSocket連接</button> <hr/> <div id="message"></div> </body> <script type="text/javascript"> var websocket = null; //判斷當(dāng)前瀏覽器是否支持WebSocket if('WebSocket' in window) { //改成你的地址 websocket = new WebSocket("ws://127.0.0.1:8080/api/websocket/100"); } else { alert('當(dāng)前瀏覽器 Not support websocket') } //連接發(fā)生錯(cuò)誤的回調(diào)方法 websocket.onerror = function() { setMessageInnerHTML("WebSocket連接發(fā)生錯(cuò)誤"); }; //連接成功建立的回調(diào)方法 websocket.onopen = function() { setMessageInnerHTML("WebSocket連接成功"); } var U01data, Uidata, Usdata //接收到消息的回調(diào)方法 websocket.onmessage = function(event) { console.log(event); setMessageInnerHTML(event); setechart() } //連接關(guān)閉的回調(diào)方法 websocket.onclose = function() { setMessageInnerHTML("WebSocket連接關(guān)閉"); } //監(jiān)聽(tīng)窗口關(guān)閉事件,當(dāng)窗口關(guān)閉時(shí),主動(dòng)去關(guān)閉websocket連接,防止連接還沒(méi)斷開(kāi)就關(guān)閉窗口,server端會(huì)拋異常。 window.onbeforeunload = function() { closeWebSocket(); } //將消息顯示在網(wǎng)頁(yè)上 function setMessageInnerHTML(innerHTML) { document.getElementById('message').innerHTML += innerHTML + ' '; } //關(guān)閉WebSocket連接 function closeWebSocket() { websocket.close(); } //發(fā)送消息 function send() { var message = document.getElementById('text').value; websocket.send('{"msg":"' + message + '"}'); setMessageInnerHTML(message + " "); } </script> </html>
2.7 結(jié)果展示
啟動(dòng)瀏覽器窗口訪(fǎng)問(wèn):http://localhost:8080/index
后臺(tái)出現(xiàn):
2022-08-16 18:18:11.931 INFO 796 --- [nio-8080-exec-3] com.example.websocket.WebSocketServer : 有新窗口開(kāi)始監(jiān)聽(tīng):100,當(dāng)前在線(xiàn)人數(shù)為:1
多次訪(fǎng)問(wèn):
2022-08-16 18:18:11.931 INFO 796 --- [nio-8080-exec-3] com.example.websocket.WebSocketServer : 有新窗口開(kāi)始監(jiān)聽(tīng):100,當(dāng)前在線(xiàn)人數(shù)為:1 2022-08-16 18:19:27.054 INFO 796 --- [nio-8080-exec-6] com.example.websocket.WebSocketServer : 釋放的sid為:100 2022-08-16 18:19:27.055 INFO 796 --- [nio-8080-exec-6] com.example.websocket.WebSocketServer : 有一連接關(guān)閉!當(dāng)前在線(xiàn)人數(shù)為0 2022-08-16 18:19:27.107 INFO 796 --- [nio-8080-exec-9] com.example.websocket.WebSocketServer : 有新窗口開(kāi)始監(jiān)聽(tīng):100,當(dāng)前在線(xiàn)人數(shù)為:1 2022-08-16 18:19:28.332 INFO 796 --- [io-8080-exec-10] com.example.websocket.WebSocketServer : 釋放的sid為:100 2022-08-16 18:19:28.332 INFO 796 --- [io-8080-exec-10] com.example.websocket.WebSocketServer : 有一連接關(guān)閉!當(dāng)前在線(xiàn)人數(shù)為0 2022-08-16 18:19:28.383 INFO 796 --- [nio-8080-exec-3] com.example.websocket.WebSocketServer : 有新窗口開(kāi)始監(jiān)聽(tīng):100,當(dāng)前在線(xiàn)人數(shù)為:1 2022-08-16 18:19:29.811 INFO 796 --- [nio-8080-exec-4] com.example.websocket.WebSocketServer : 釋放的sid為:100 2022-08-16 18:19:29.811 INFO 796 --- [nio-8080-exec-4] com.example.websocket.WebSocketServer : 有一連接關(guān)閉!當(dāng)前在線(xiàn)人數(shù)為0 2022-08-16 18:19:29.860 INFO 796 --- [nio-8080-exec-7] com.example.websocket.WebSocketServer : 有新窗口開(kāi)始監(jiān)聽(tīng):100,當(dāng)前在線(xiàn)人數(shù)為:1
前臺(tái)顯示:
發(fā)送消息:
后臺(tái)顯示:
2022-08-16 18:21:58.602 INFO 796 --- [nio-8080-exec-4] com.example.websocket.WebSocketServer : 收到來(lái)自窗口100的信息:{"msg":"hello world"} 2022-08-16 18:22:03.098 INFO 796 --- [nio-8080-exec-5] com.example.websocket.WebSocketServer : 收到來(lái)自窗口100的信息:{"msg":"hihao"}
3、總結(jié)
這中間我遇到一個(gè)問(wèn)題,就是說(shuō)WebSocket啟動(dòng)的時(shí)候優(yōu)先于spring容器,從而導(dǎo)致在WebSocketServer中調(diào)用業(yè)務(wù)Service會(huì)報(bào)空指針異常,所以需要在WebSocketServer中將所需要用到的service給靜態(tài)初始化一下:如下所示:
@Component @Slf4j @Service @ServerEndpoint("/api/websocket/{sid}") public class WebSocketServer { // 靜態(tài)初始化用到的Service public static IDetRecordService detRecordService; }
還需要做如下配置:
@Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } @Autowired // 提前注入Spring private void setRedisService(IDetRecordService iDetRecordService){ WebSocketServer.detRecordService= iDetRecordService; } }
以上就是SpringBoot集成WebSocket實(shí)現(xiàn)后臺(tái)向前端推送信息的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot WebSocket推送消息的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- SpringBoot集成WebSocket實(shí)現(xiàn)后臺(tái)向前端推送信息的示例
- SpringBoot2.0集成WebSocket實(shí)現(xiàn)后臺(tái)向前端推送信息
- Spring?Boot?使用?SSE?方式向前端推送數(shù)據(jù)詳解
- SpringBoot整合WebSocket實(shí)現(xiàn)后端向前端主動(dòng)推送消息方式
- SpringBoot使用WebSocket實(shí)現(xiàn)向前端推送消息功能
- SpringBoot+WebSocket向前端推送消息的實(shí)現(xiàn)示例
- 利用Netty+SpringBoot實(shí)現(xiàn)定時(shí)后端向前端推送數(shù)據(jù)
相關(guān)文章
Java GUI圖形界面開(kāi)發(fā)實(shí)現(xiàn)小型計(jì)算器流程詳解
本文章向大家介紹Java GUI圖形界面開(kāi)發(fā)實(shí)現(xiàn)小型計(jì)算器,主要包括布局管理器使用實(shí)例、應(yīng)用技巧、基本知識(shí)點(diǎn)總結(jié)和需要注意事項(xiàng),具有一定的參考價(jià)值,需要的朋友可以參考一下2022-08-08Springboot整合Spring Cloud Kubernetes讀取ConfigMap支持自動(dòng)刷新配置的教程
這篇文章主要介紹了Springboot整合Spring Cloud Kubernetes讀取ConfigMap支持自動(dòng)刷新配置,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09如何解決Spring事務(wù)注解@Transactional在類(lèi)內(nèi)部方法調(diào)用不生效
這篇文章主要介紹了如何解決Spring事務(wù)注解@Transactional在類(lèi)內(nèi)部方法調(diào)用不生效問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08如何使用jmeter錄制瀏覽器Https請(qǐng)求過(guò)程圖解
這篇文章主要介紹了基于jmeter錄制瀏覽器Https請(qǐng)求過(guò)程圖解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04Java純代碼實(shí)現(xiàn)導(dǎo)出pdf合并單元格
這篇文章主要為大家詳細(xì)介紹了Java如何純代碼實(shí)現(xiàn)導(dǎo)出pdf與合并單元格功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-12-12