<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
名稱 | 版本號 |
---|---|
Spring Boot | 2.4.5 |
Idea | 2021.3.2 |
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
注意:Spring Boot在父工程中已經管理了websocket的版本資訊,所以不用指定版本號也是可以的
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(); } }
建立此設定類的目的只是為了把ServerEndpointExporter 這個類的範例交給spring 容器進行管理,您可以用任意一種方式交給容器,如使用@Import(ServerEndpointExporter.class)這種方式等進行操作;此處只是我的編碼風格如此;並非必須這樣操作
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; } /** * 建立連線成功的回撥方法 * * @param session 對談物件 * @param websocketClientType 此引數就是路徑中{websocketClientType}位置傳入的引數 */ @OnOpen public void onOpen(Session session, @PathParam("websocketClientType") int websocketClientType) { sessionManager.onOpen(session, websocketClientType); } /** * 當對談關閉時執行的回撥方法 * * @param session 對談物件 * @param websocketClientType 此引數就是路徑中{websocketClientType}位置傳入的引數 */ @OnClose public void onClose(Session session, @PathParam("websocketClientType") int websocketClientType) { sessionManager.onClose(session, websocketClientType); } /** * 當收到使用者端資訊時執行的回撥方法 * * @param session 對談物件 * @param message 使用者端傳遞過來的資訊 * @param websocketClientType 此引數就是路徑中{websocketClientType}位置傳入的引數 */ @OnMessage public void onMessage(Session session, String message, @PathParam("websocketClientType") int websocketClientType) { sessionManager.onMessage(session, message, websocketClientType); } /** * 當發生錯誤時的回撥方法 * * @param session 對談物件 * @param e 異常物件 * @param websocketClientType 此引數就是路徑中{websocketClientType}位置傳入的引數 */ @OnError public void onError(Session session, Throwable e, @PathParam("websocketClientType") int websocketClientType) { sessionManager.onError(session, e, websocketClientType); } }
@ServerEndpoint註解標註此類為一個伺服器端的端點類,此註解有一個必須的引數,用於指定使用者端存取的地址,本案例中為:/arcticFoxServerEndpoint,而路徑後面的/{websocketClientType}這個是路徑中引數的預留位置,有點類似與Spring Mvc中Rest介面和@PathVariable註解的作用
注意事項: 一定要將此類交給spring 容器進行管理!!還有一個坑就是,此類的範例時非單例的,所以如果要在此類中注入其他的bean,不能使直接在屬性上使用@Resource註解或者@Autowired等註解進行注入,否則會報錯。正確操作應該是把要注入的欄位設定為靜態的,然後通過非靜態的set方法進行注入,具體程式碼請看上方範例
通過上面的程式碼我們可以知道每個回撥方法中都會收到一個Session物件,正如您所想,要向用戶端傳送訊息正是要藉助此物件;Session物件有一個getAsyncRemote方法,呼叫此方法可以得到一個RemoteEndpoint.Async物件,檢視此物件,發現有很多send打頭的方法;
是的,這些方法就是傳送訊息的方法,博主這個專案中主要是通過JSON來進行互動的,所以我使用了sendText方法,範例程式碼:
RemoteEndpoint.Async asyncRemote = session.getAsyncRemote(); asyncRemote.sendText(jsonString);
很顯然中轉變數asyncRemote 沒什麼太大的用處,不如直接寫成:
session.getAsyncRemote().sendText(jsonString);
通過方法名看到,似乎還可以傳送物件,二進位制序列等,博主沒有深入研究,有興趣的小夥伴可以嘗試嘗試
一般來講使用者端應該是用Java Script實現,但是博主這個專案比較特殊,需要用Java來實現使用者端,下面博主先以Java使用者端說明其實現細節,然後再說再前端如何實現
匯入依賴
<dependency> <groupId>org.java-websocket</groupId> <artifactId>Java-WebSocket</artifactId> <version>1.5.3</version> </dependency>
其實Java中實現WebSocket的第三方包還有很多,博主這個地方使用的是Java-WebSocket
,有興趣的小夥伴可以試試其他的包
建立連線和處理回撥
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("錯誤的指令型別"); } } 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; } }
程式碼解釋: 其實我的establishConnection方法中上來就範例化了一個WebSocketClient 類的範例,請注意,此類是個抽象類,我在這裡用匿名實現類的方式實現的,此類有幾個抽象方法需要實現,也就是onOpen,onMessage,onClose,onError四個方法,其作用其實已經是很見名知意了,和伺服器端的回撥方法一樣,就不過多解釋;範例化此類需要傳入一個URI物件,這個URI物件其實就是封裝了對伺服器端連線的地址,由於博主不希望把伺服器端的地址給寫死了,所以我設定到了組態檔中,然後通過String.format靜態方法配合預留位置拼接url地址和引數;路徑的規則是:協定名://IP地址(或域名):埠號/伺服器端宣告的地址/引數;舉個例子:
ws://192.168.88.88:8080/arcticFoxServerEndpoint/1
ws://localhost:8080/arcticFoxServerEndpoint/2
ws://為協定;範例化WebSocketClient 類的範例之後,呼叫其connect()方法即開始建立連線,呼叫getReadyState()方法可以獲得其狀態;由於我的伺服器端可能隨時都連不上,所以我在使用者端的onClose回撥函數中進行了一個遞迴(20秒後),用於重新連線。
使用者端向伺服器端傳送訊息
通過WebSocketClient 類的範例,我們可以看到有以下方法,很明顯send方法就是用來傳送訊息使用的
範例程式碼:
//判斷一下是否為空 if (Objects.nonNull(webSocketClient)) { try { //通過jackson將物件轉換為json字串(非必須) String jsonString = objectMapper.writeValueAsString(feedback); //傳送資訊 webSocketClient.send(jsonString); } catch (JsonProcessingException e) { e.printStackTrace(); } } else { log.warn("no connection established"); }
安裝reconnecting-websocket包(非必須)
npm i --save reconnecting-websocket
安裝這個包是為了websocket能在斷線之後重新連線,其實不使用這個包也是可以用原生Java Script實現的;但是他和原生的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物件中data儲存的就是伺服器端傳送過來的訊息 let parse = JSON.parse(event.data); console.log("webSocket on message :", parse); }; reconnectingWebSocket.onclose = event => { console.log(event); }; reconnectingWebSocket.onerror = event => { console.log(event); }; //視窗關閉時斷開連線 window.onbeforeunload = function () { reconnectingWebSocket.close(); } }
在前端中實現websocket就比較簡單了,就上面的幾行程式碼即可,不用呼叫其他函數進行連線,範例化之後就開始連線了
想伺服器端傳送資訊
在前端中傳送資訊就更簡單了,直接呼叫reconnectingWebSocket
的send
方法,傳入要傳送的資料即可
到此這篇關於SpringBoot+WebSocket實現即時通訊的方法詳解的文章就介紹到這了,更多相關SpringBoot WebSocket即時通訊內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45