SpringBoot整合WebSocket實現(xiàn)實時通信功能
什么是WebSocket?
WebSocket是一種在單個TCP連接上進行全雙工通信的協(xié)議。與傳統(tǒng)的HTTP請求-響應模式不同,WebSocket允許服務器主動向客戶端推送數(shù)據(jù),實現(xiàn)了實時通信的功能。WebSocket協(xié)議基于HTTP協(xié)議,通過在握手階段升級協(xié)議,使得服務器和客戶端可以直接進行數(shù)據(jù)交換,而無需頻繁的HTTP請求。
Spring Boot中的WebSocket支持
Spring Boot提供了對WebSocket的支持,通過集成Spring WebSocket模塊,我們可以輕松地實現(xiàn)WebSocket功能。在Spring Boot中,我們可以使用注解來定義WebSocket的處理器和消息處理方法,從而實現(xiàn)實時通信。
WebSocket和HTTP優(yōu)劣勢
WebSocket的優(yōu)勢:
1.實時性:
WebSocket是一種全雙工通信協(xié)議,可以實現(xiàn)服務器主動向客戶端推送數(shù)據(jù),實現(xiàn)實時通信。相比之下,HTTP是一種請求-響應模式 的協(xié)議,需要客戶端主動發(fā)起請求才能獲取數(shù)據(jù)。
2.較低的延遲:
由于WebSocket使用單個TCP連接進行通信,避免了HTTP的握手和頭部信息的重復傳輸,因此具有較低的延遲。
3.較小的數(shù)據(jù)傳輸量:
WebSocket使用二進制數(shù)據(jù)幀進行傳輸,相比于HTTP的文本數(shù)據(jù)傳輸,可以減少數(shù)據(jù)傳輸量,提高傳輸效率。
4.更好的兼容性:
WebSocket協(xié)議可以在多種瀏覽器和平臺上使用,具有較好的兼容性。
HTTP的優(yōu)勢:
1.簡單易用:
? HTTP是一種簡單的請求-響應協(xié)議,易于理解和使用。相比之下,WebSocket需要進行握手和協(xié)議升級等復雜操作。
2.更廣泛的應用:
HTTP協(xié)議廣泛應用于Web開發(fā)中,支持各種類型的請求和響應,可以用于傳輸文本、圖片、視頻等多種數(shù)據(jù)格式。
3.更好的安全性:
HTTP協(xié)議支持HTTPS加密傳輸,可以保證數(shù)據(jù)的安全性。
綜上,WebSocket適用于需要實時通信和較低延遲的場景,而HTTP適用于傳輸各種類型的數(shù)據(jù)和簡單的請求-響應模式。在實際應用中,可以根據(jù)具體需求選擇合適的協(xié)議。
示例
版本依賴
模塊 | 版本 |
---|---|
SpringBoot | 3.1.0 |
JDK | 17 |
代碼
WebSocketConfig
@Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
WebSocketServer
@Component @ServerEndpoint("/server/{uid}") @Slf4j public class WebSocketServer { /** * 記錄當前在線連接數(shù) */ private static int onlineCount = 0; /** * 使用線程安全的ConcurrentHashMap來存放每個客戶端對應的WebSocket對象 */ private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>(); /** * 與某個客戶端的連接會話,需要通過它來給客戶端發(fā)送數(shù)據(jù) */ private Session session; /** * 接收客戶端消息的uid */ private String uid = ""; /** * 連接建立成功調(diào)用的方法 * @param session * @param uid */ @OnOpen public void onOpen(Session session, @PathParam("uid") String uid) { this.session = session; this.uid = uid; if (webSocketMap.containsKey(uid)) { webSocketMap.remove(uid); //加入到set中 webSocketMap.put(uid, this); } else { //加入set中 webSocketMap.put(uid, this); //在線數(shù)加1 addOnlineCount(); } log.info("用戶【" + uid + "】連接成功,當前在線人數(shù)為:" + getOnlineCount()); try { sendMsg("連接成功"); } catch (IOException e) { log.error("用戶【" + uid + "】網(wǎng)絡異常!", e); } } /** * 連接關閉調(diào)用的方法 */ @OnClose public void onClose() { if (webSocketMap.containsKey(uid)) { webSocketMap.remove(uid); //從set中刪除 subOnlineCount(); } log.info("用戶【" + uid + "】退出,當前在線人數(shù)為:" + getOnlineCount()); } /** * 收到客戶端消息后調(diào)用的方法 * @param message 客戶端發(fā)送過來的消息 * @param session 會話 */ @OnMessage public void onMessage(String message, Session session) { log.info("用戶【" + uid + "】發(fā)送報文:" + message); //群發(fā)消息 //消息保存到數(shù)據(jù)庫或者redis if (StringUtils.isNotBlank(message)) { try { //解析發(fā)送的報文 ObjectMapper objectMapper = new ObjectMapper(); Map<String, String> map = objectMapper.readValue(message, new TypeReference<Map<String, String>>(){}); //追加發(fā)送人(防止串改) map.put("fromUID", this.uid); String toUID = map.get("toUID"); //傳送給對應的toUserId用戶的WebSocket if (StringUtils.isNotBlank(toUID) && webSocketMap.containsKey(toUID)) { webSocketMap.get(toUID).sendMsg(objectMapper.writeValueAsString(map)); } else { //若果不在這個服務器上,可以考慮發(fā)送到mysql或者redis log.error("請求目標用戶【" + toUID + "】不在該服務器上"); } } catch (Exception e) { log.error("用戶【" + uid + "】發(fā)送消息異常!", e); } } } /** * 處理錯誤 * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { log.error("用戶【" + this.uid + "】處理消息錯誤,原因:" + error.getMessage()); error.printStackTrace(); } /** * 實現(xiàn)服務器主動推送 * @param msg * @throws IOException */ private void sendMsg(String msg) throws IOException { this.session.getBasicRemote().sendText(msg); } /** * 發(fā)送自定義消息 * @param message * @param uid * @throws IOException */ public static void sendInfo(String message, @PathParam("uid") String uid) throws IOException { log.info("發(fā)送消息到用戶【" + uid + "】發(fā)送的報文:" + message); if (!StringUtils.isEmpty(uid) && webSocketMap.containsKey(uid)) { webSocketMap.get(uid).sendMsg(message); } else { log.error("用戶【" + uid + "】不在線!"); } } private static synchronized int getOnlineCount() { return onlineCount; } private static synchronized void addOnlineCount() { WebSocketServer.onlineCount++; } private static synchronized void subOnlineCount() { WebSocketServer.onlineCount--; } }
WebSocketController
@RestController public class WebSocketController { @GetMapping("/page") public ModelAndView page() { return new ModelAndView("webSocket"); } @RequestMapping("/push/{toUID}") public ResponseEntity<String> pushToClient(String message, @PathVariable String toUID) throws Exception { WebSocketServer.sendInfo(message, toUID); return ResponseEntity.ok("Send Success!"); } }
webSocket.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>WebSocket消息通知</title> </head> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script> <script> var socket; //打開WebSocket function openSocket() { if (typeof (WebSocket) === "undefined") { console.log("您的瀏覽器不支持WebSocket"); } else { console.log("您的瀏覽器支持WebSocket"); //實現(xiàn)化WebSocket對象,指定要連接的服務器地址與端口,建立連接. var socketUrl = "http://localhost:8080/socket/server/" + $("#uid").val(); //將https與http協(xié)議替換為ws協(xié)議 socketUrl = socketUrl.replace("https", "ws").replace("http", "ws"); console.log(socketUrl); if (socket != null) { socket.close(); socket = null; } socket = new WebSocket(socketUrl); //打開事件 socket.onopen = function () { console.log("WebSocket已打開"); //socket.send("這是來自客戶端的消息" + location.href + new Date()); }; //獲得消息事件 socket.onmessage = function (msg) { console.log(msg.data); //發(fā)現(xiàn)消息進入,開始處理前端觸發(fā)邏輯 }; //關閉事件 socket.onclose = function () { console.log("WebSocket已關閉"); }; //發(fā)生了錯誤事件 socket.onerror = function () { console.log("WebSocket發(fā)生了錯誤"); } } } //發(fā)送消息 function sendMessage() { if (typeof (WebSocket) === "undefined") { console.log("您的瀏覽器不支持WebSocket"); } else { console.log("您的瀏覽器支持WebSocket"); console.log('{"toUID":"' + $("#toUID").val() + '","Msg":"' + $("#msg").val() + '"}'); socket.send('{"toUID":"' + $("#toUID").val() + '","Msg":"' + $("#msg").val() + '"}'); } } </script> <body> <p>【uid】: <div><input id="uid" name="uid" type="text" value="1"></div> <p>【toUID】: <div><input id="toUID" name="toUID" type="text" value="2"></div> <p>【Msg】: <div><input id="msg" name="msg" type="text" value="hello WebSocket2"></div> <p>【第一步操作:】: <div> <button onclick="openSocket()">開啟socket</button> </div> <p>【第二步操作:】: <div> <button onclick="sendMessage()">發(fā)送消息</button> </div> </body> </html>
測試
打開2個頁面
第一個:
http://localhost:8080/socket/page
第二個:
http://localhost:8080/socket/page
都點擊開啟socket
都點擊發(fā)送
至此示例發(fā)送完成
總結(jié)
通過本文的介紹,我們了解了Spring Boot中如何集成WebSocket,實現(xiàn)實時通信的功能。
WebSocket作為一種高效的實時通信協(xié)議,為開發(fā)者提供了更好的用戶體驗和交互性。
希望本文能夠幫助快速掌握Spring Boot整合WebSocket的方法,為應用程序添加實時通信功能。
以上就是SpringBoot整合WebSocket實現(xiàn)實時通信功能的詳細內(nèi)容,更多關于SpringBoot WebSocket通信的資料請關注腳本之家其它相關文章!
相關文章
Eclipse 出現(xiàn)A configuration with this name already exists問題解決方
這篇文章主要介紹了Eclipse 出現(xiàn)A configuration with this name already exists問題解決方法的相關資料,需要的朋友可以參考下2016-11-11java配置變量的解釋,搬運他人優(yōu)質(zhì)評論(推薦)
這篇文章主要介紹了java配置變量,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-04-04