SpringBoot+WebSocket實(shí)現(xiàn)即時(shí)通訊的方法詳解
環(huán)境信息
名稱(chēng) | 版本號(hào) |
---|---|
Spring Boot | 2.4.5 |
Idea | 2021.3.2 |
服務(wù)端實(shí)現(xiàn)
導(dǎo)入依賴(lài)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
注意:Spring Boot在父工程中已經(jīng)管理了websocket的版本信息,所以不用指定版本號(hào)也是可以的
創(chuàng)建配置類(lèi)
package com.fenzhichuanmei.configuration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; /** * @author Yi Dai 484201132@qq.com * @since 2022/5/13 11:34 */ @Configuration public class WebsocketConfiguration { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
創(chuàng)建此配置類(lèi)的目的只是為了把ServerEndpointExporter 這個(gè)類(lèi)的實(shí)例交給spring 容器進(jìn)行管理,您可以用任意一種方式交給容器,如使用@Import(ServerEndpointExporter.class)這種方式等進(jìn)行操作;此處只是我的編碼風(fēng)格如此;并非必須這樣操作
創(chuàng)建一個(gè)注解式的端點(diǎn)并在其中通過(guò)配套注解聲明回調(diào)方法
package com.fenzhichuanmei.websocket; import com.fenzhichuanmei.websocket.utils.SessionManager; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import javax.annotation.Resource; import javax.websocket.*; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; /** * @author Yi Dai 484201132@qq.com * @since 2022/3/7 15:47 */ @Slf4j @Component @ServerEndpoint("/arcticFoxServerEndpoint/{websocketClientType}") public class ArcticFoxServerEndpoint { private static SessionManager sessionManager; @Resource public void setProcessor(SessionManager sessionManager) { ArcticFoxServerEndpoint.sessionManager = sessionManager; } /** * 建立連接成功的回調(diào)方法 * * @param session 會(huì)話(huà)對(duì)象 * @param websocketClientType 此參數(shù)就是路徑中{websocketClientType}位置傳入的參數(shù) */ @OnOpen public void onOpen(Session session, @PathParam("websocketClientType") int websocketClientType) { sessionManager.onOpen(session, websocketClientType); } /** * 當(dāng)會(huì)話(huà)關(guān)閉時(shí)執(zhí)行的回調(diào)方法 * * @param session 會(huì)話(huà)對(duì)象 * @param websocketClientType 此參數(shù)就是路徑中{websocketClientType}位置傳入的參數(shù) */ @OnClose public void onClose(Session session, @PathParam("websocketClientType") int websocketClientType) { sessionManager.onClose(session, websocketClientType); } /** * 當(dāng)收到客戶(hù)端信息時(shí)執(zhí)行的回調(diào)方法 * * @param session 會(huì)話(huà)對(duì)象 * @param message 客戶(hù)端傳遞過(guò)來(lái)的信息 * @param websocketClientType 此參數(shù)就是路徑中{websocketClientType}位置傳入的參數(shù) */ @OnMessage public void onMessage(Session session, String message, @PathParam("websocketClientType") int websocketClientType) { sessionManager.onMessage(session, message, websocketClientType); } /** * 當(dāng)發(fā)生錯(cuò)誤時(shí)的回調(diào)方法 * * @param session 會(huì)話(huà)對(duì)象 * @param e 異常對(duì)象 * @param websocketClientType 此參數(shù)就是路徑中{websocketClientType}位置傳入的參數(shù) */ @OnError public void onError(Session session, Throwable e, @PathParam("websocketClientType") int websocketClientType) { sessionManager.onError(session, e, websocketClientType); } }
@ServerEndpoint注解標(biāo)注此類(lèi)為一個(gè)服務(wù)端的端點(diǎn)類(lèi),此注解有一個(gè)必須的參數(shù),用于指定客戶(hù)端訪(fǎng)問(wèn)的地址,本案例中為:/arcticFoxServerEndpoint,而路徑后面的/{websocketClientType}這個(gè)是路徑中參數(shù)的占位符,有點(diǎn)類(lèi)似與Spring Mvc中Rest接口和@PathVariable注解的作用
注意事項(xiàng): 一定要將此類(lèi)交給spring 容器進(jìn)行管理?。∵€有一個(gè)坑就是,此類(lèi)的實(shí)例時(shí)非單例的,所以如果要在此類(lèi)中注入其他的bean,不能使直接在屬性上使用@Resource注解或者@Autowired等注解進(jìn)行注入,否則會(huì)報(bào)錯(cuò)。正確操作應(yīng)該是把要注入的字段設(shè)置為靜態(tài)的,然后通過(guò)非靜態(tài)的set方法進(jìn)行注入,具體代碼請(qǐng)看上方實(shí)例
服務(wù)端主動(dòng)發(fā)送消息給客戶(hù)端
通過(guò)上面的代碼我們可以知道每個(gè)回調(diào)方法中都會(huì)收到一個(gè)Session對(duì)象,正如您所想,要向客戶(hù)端發(fā)送消息正是要借助此對(duì)象;Session對(duì)象有一個(gè)getAsyncRemote方法,調(diào)用此方法可以得到一個(gè)RemoteEndpoint.Async對(duì)象,查看此對(duì)象,發(fā)現(xiàn)有很多send打頭的方法;
是的,這些方法就是發(fā)送消息的方法,博主這個(gè)項(xiàng)目中主要是通過(guò)JSON來(lái)進(jìn)行交互的,所以我使用了sendText方法,示例代碼:
RemoteEndpoint.Async asyncRemote = session.getAsyncRemote(); asyncRemote.sendText(jsonString);
很顯然中轉(zhuǎn)變量asyncRemote 沒(méi)什么太大的用處,不如直接寫(xiě)成:
session.getAsyncRemote().sendText(jsonString);
通過(guò)方法名看到,似乎還可以發(fā)送對(duì)象,二進(jìn)制序列等,博主沒(méi)有深入研究,有興趣的小伙伴可以嘗試嘗試
客戶(hù)端實(shí)現(xiàn)
一般來(lái)講客戶(hù)端應(yīng)該是用Java Script實(shí)現(xiàn),但是博主這個(gè)項(xiàng)目比較特殊,需要用Java來(lái)實(shí)現(xiàn)客戶(hù)端,下面博主先以Java客戶(hù)端說(shuō)明其實(shí)現(xiàn)細(xì)節(jié),然后再說(shuō)再前端如何實(shí)現(xiàn)
Java客戶(hù)端實(shí)現(xiàn)
導(dǎo)入依賴(lài)
<dependency> <groupId>org.java-websocket</groupId> <artifactId>Java-WebSocket</artifactId> <version>1.5.3</version> </dependency>
其實(shí)Java中實(shí)現(xiàn)WebSocket的第三方包還有很多,博主這個(gè)地方使用的是Java-WebSocket
,有興趣的小伙伴可以試試其他的包
建立連接和處理回調(diào)
package com.fenzhichuanmei.websocket; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fenzhichuanmei.components.PaymentComponent; import com.fenzhichuanmei.pojo.Instructions; import com.fenzhichuanmei.utils.WebsocketClientType; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.java_websocket.client.WebSocketClient; import org.java_websocket.enums.ReadyState; import org.java_websocket.handshake.ServerHandshake; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.net.URI; import java.net.URISyntaxException; /** * @author Yi Dai 484201132@qq.com * @since 2022/5/13 10:16 */ @Slf4j @Component public class ArcticFoxWebSocketClient { @Resource private ObjectMapper objectMapper; @Resource private PaymentComponent paymentComponent; @Resource private ArcticFoxWebSocketClientProperties properties; public void establishConnection() throws URISyntaxException { WebSocketClient webSocketClient = new WebSocketClient(new URI(String.format("%s/%d", properties.getWebSocketServerUrl(), WebsocketClientType.PAYMENT_DEVICE))) { @Override public void onOpen(ServerHandshake serverHandshake) { log.info("WebSocketClient: onOpen : {}", serverHandshake); } @Override public void onMessage(String jsonString) { try { Instructions instructions = objectMapper.readValue(jsonString, Instructions.class); if (instructions.getType() == Instructions.NOTICE_PAYMENT) { paymentComponent.queryAnUnpaidOrdersAndPay(); } else { throw new RuntimeException("錯(cuò)誤的指令類(lèi)型"); } } catch (JsonProcessingException e) { e.printStackTrace(); } } @Override public void onClose(int i, String s, boolean b) { log.info("WebSocketClient: onClose : i:{},s:{},b:{}", i, s, b); try { Thread.sleep(1000 * 20); establishConnection(); } catch (InterruptedException | URISyntaxException e) { e.printStackTrace(); } } @Override public void onError(Exception e) { log.error("WebSocketClient: onError {}", e.getMessage()); } }; webSocketClient.connect(); while (!(webSocketClient.getReadyState() == ReadyState.OPEN)) { try { Thread.sleep(1000 * 2); } catch (InterruptedException e) { e.printStackTrace(); } } log.info("WebSocketClient: connection established successfully"); } @Data @Component @ConfigurationProperties("arctic-fox-web-socket-client.properties") public static class ArcticFoxWebSocketClientProperties { private String webSocketServerUrl; } }
代碼解釋?zhuān)?其實(shí)我的establishConnection方法中上來(lái)就實(shí)例化了一個(gè)WebSocketClient 類(lèi)的實(shí)例,請(qǐng)注意,此類(lèi)是個(gè)抽象類(lèi),我在這里用匿名實(shí)現(xiàn)類(lèi)的方式實(shí)現(xiàn)的,此類(lèi)有幾個(gè)抽象方法需要實(shí)現(xiàn),也就是onOpen,onMessage,onClose,onError四個(gè)方法,其作用其實(shí)已經(jīng)是很見(jiàn)名知意了,和服務(wù)端的回調(diào)方法一樣,就不過(guò)多解釋?zhuān)粚?shí)例化此類(lèi)需要傳入一個(gè)URI對(duì)象,這個(gè)URI對(duì)象其實(shí)就是封裝了對(duì)服務(wù)端連接的地址,由于博主不希望把服務(wù)端的地址給寫(xiě)死了,所以我配置到了配置文件中,然后通過(guò)String.format靜態(tài)方法配合占位符拼接url地址和參數(shù);路徑的規(guī)則是:協(xié)議名://IP地址(或域名):端口號(hào)/服務(wù)端聲明的地址/參數(shù);舉個(gè)例子:
ws://192.168.88.88:8080/arcticFoxServerEndpoint/1
ws://localhost:8080/arcticFoxServerEndpoint/2
ws://為協(xié)議;實(shí)例化WebSocketClient 類(lèi)的實(shí)例之后,調(diào)用其connect()方法即開(kāi)始建立連接,調(diào)用getReadyState()方法可以獲得其狀態(tài);由于我的服務(wù)端可能隨時(shí)都連不上,所以我在客戶(hù)端的onClose回調(diào)函數(shù)中進(jìn)行了一個(gè)遞歸(20秒后),用于重新連接。
客戶(hù)端向服務(wù)端發(fā)送消息
通過(guò)WebSocketClient 類(lèi)的實(shí)例,我們可以看到有以下方法,很明顯send方法就是用來(lái)發(fā)送消息使用的
示例代碼:
//判斷一下是否為空 if (Objects.nonNull(webSocketClient)) { try { //通過(guò)jackson將對(duì)象轉(zhuǎn)換為json字符串(非必須) String jsonString = objectMapper.writeValueAsString(feedback); //發(fā)送信息 webSocketClient.send(jsonString); } catch (JsonProcessingException e) { e.printStackTrace(); } } else { log.warn("no connection established"); }
在前端環(huán)境(vue)中使用websocket
安裝reconnecting-websocket包(非必須)
npm i --save reconnecting-websocket
安裝這個(gè)包是為了websocket能在斷線(xiàn)之后重新連接,其實(shí)不使用這個(gè)包也是可以用原生Java Script實(shí)現(xiàn)的;但是他和原生的api幾乎一樣;
示例代碼:
import ReconnectingWebSocket from "reconnecting-websocket"; export default function initializationWebsocket() { let reconnectingWebSocket = new ReconnectingWebSocket(`ws://localhost:8080/arcticFoxServerEndpoint/${2}`); reconnectingWebSocket.onopen = event => { console.log("on open :", event); }; reconnectingWebSocket.onmessage = event => { //event對(duì)象中data存儲(chǔ)的就是服務(wù)端發(fā)送過(guò)來(lái)的消息 let parse = JSON.parse(event.data); console.log("webSocket on message :", parse); }; reconnectingWebSocket.onclose = event => { console.log(event); }; reconnectingWebSocket.onerror = event => { console.log(event); }; //窗口關(guān)閉時(shí)斷開(kāi)連接 window.onbeforeunload = function () { reconnectingWebSocket.close(); } }
在前端中實(shí)現(xiàn)websocket就比較簡(jiǎn)單了,就上面的幾行代碼即可,不用調(diào)用其他函數(shù)進(jìn)行連接,實(shí)例化之后就開(kāi)始連接了
想服務(wù)端發(fā)送信息
在前端中發(fā)送信息就更簡(jiǎn)單了,直接調(diào)用reconnectingWebSocket
的send
方法,傳入要發(fā)送的數(shù)據(jù)即可
到此這篇關(guān)于SpringBoot+WebSocket實(shí)現(xiàn)即時(shí)通訊的方法詳解的文章就介紹到這了,更多相關(guān)SpringBoot WebSocket即時(shí)通訊內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring boot + mybatis + Vue.js 
這篇文章主要介紹了Spring boot + mybatis + Vue.js + ElementUI 實(shí)現(xiàn)數(shù)據(jù)的增刪改查實(shí)例代碼(二),非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-05-05springboot整合持久層的方法實(shí)現(xiàn)
本文主要介紹了springboot整合持久層的方法實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09詳解Java中方法next()和nextLine()的區(qū)別與易錯(cuò)點(diǎn)
這篇文章主要介紹了詳解Java中方法next()和nextLine()的區(qū)別與易錯(cuò)點(diǎn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11Java的四種常見(jiàn)線(xiàn)程池及Scheduled定時(shí)線(xiàn)程池實(shí)現(xiàn)詳解
這篇文章主要介紹了Java的四種常見(jiàn)線(xiàn)程池及Scheduled定時(shí)線(xiàn)程池實(shí)現(xiàn)詳解,在Java中,我們可以通過(guò)Executors類(lèi)來(lái)創(chuàng)建ScheduledThreadPool,Executors類(lèi)提供了幾個(gè)靜態(tài)方法來(lái)創(chuàng)建不同類(lèi)型的線(xiàn)程池,包括ScheduledThreadPool,需要的朋友可以參考下2023-09-09Java中ExecutorService和ThreadPoolExecutor運(yùn)行原理
本文主要介紹了Java中ExecutorService和ThreadPoolExecutor運(yùn)行原理,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08spring cloud 阿波羅 apollo 本地開(kāi)發(fā)環(huán)境搭建過(guò)程
Apollo(阿波羅)是攜程框架部門(mén)研發(fā)的配置管理平臺(tái),能夠集中化管理應(yīng)用不同環(huán)境、不同集群的配置,配置修改后能夠?qū)崟r(shí)推送到應(yīng)用端,并且具備規(guī)范的權(quán)限、流程治理等特性2018-01-01