WebSocket的故事(六)—— Springboot中,實現更靈活的WebSocket
Socket/">WebSocket的故事系列計劃分五大篇六章,旨在由淺入深的介紹WebSocket以及在Springboot中如何快速構建和使用WebSocket提供的能力。本系列計劃包含如下幾篇文章:
ofollow,noindex">第一篇,什麼是WebSocket以及它的用途
第二篇,Spring中如何利用STOMP快速構建WebSocket廣播式訊息模式
第三篇,Springboot中,如何利用WebSocket和STOMP快速構建點對點的訊息模式(1)
第四篇,Springboot中,如何利用WebSocket和STOMP快速構建點對點的訊息模式(2)
第五篇,Springboot中,實現網頁聊天室之自定義WebSocket訊息代理
第六篇,Springboot中,實現更靈活的WebSocket
本篇的主線
本篇是這個系列的最後一篇,將介紹另一種實現WebSocket的方式。仍然會以一個簡單聊天室為例子進行講述。至此我們也可以根據具體情況,選擇不同的實現方式。
本篇適合的讀者
想了解如何在Springboot上自定義實現更為複雜的WebSocket產品邏輯的各路有志青年。
使用Tomcat提供的WebSocket支援
早在 Java EE 7
時,就釋出了 JSR356
規範。Tomcat7.0.47開始,也支援了統一的 WebSocket
介面。在使用 Springboot
時,也可以輕鬆的使用 Tomcat
提供的這些API。今天我們就來體驗一把 Tomcat
實現的 WebSocket
。
1. 引入依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> 複製程式碼
Springboot內建了tomcat,我們直接引入spring的這個高階元件即可。順便多說一句,Springboot的高階元件會自動引用基礎的元件,像 spring-boot-starter-websocket
就引入了 spring-boot-starter-web
和 spring-boot-starter
,所以不要重複引入。
2. 使用@ServerEndpoint建立WebSocket Endpoint
首先要注入 ServerEndpointExporter
,這個Bean會自動註冊使用了 @ServerEndpoint
註解宣告的WebSocket Endpoint。
package com.draw.wsdraw.websocket; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; @Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } } 複製程式碼
然後,我們動手實現WebSocket服務的實現類,這裡是 WebSocketServer
,注意別忘了用 @ServerEndpint
和 @Component
宣告下。雖然 @Component
預設是單例模式的,但Springboot還是會為每個WebSocket連線初始化一個 Bean
,所以可以用一個靜態Map儲存起來。換句話說,每當有一個使用者向伺服器發起連線時,都會建立一個 WebSocketServer
物件,將此物件按 roomId
儲存在 HashMap
中,方便後續使用。
建立ServerEndpoint時,需要對應實現其所需的幾個功能性方法: OnOpen、OnMessage、OnClose、OnError
。
@OnOpen @OnMessage @OnClose @OnError
重寫上述方法,即可實現WebSocket的服務端業務邏輯。
@ServerEndpoint("/webSocket/{roomId}") @Component public class WebSocketServer { private static ConcurrentHashMap<String, List<WebSocketServer>> webSocketMap = new ConcurrentHashMap<>(3); //與某個客戶端的連線會話,需要通過它來給客戶端傳送資料 private Session session; //接收roomId private String roomId = ""; /** * 連線建立成功呼叫的方法 */ @OnOpen public void onOpen(Session session, EndpointConfig config, @PathParam("roomId") String roomId) { if (roomId == null || roomId.isEmpty()) return; this.session = session; this.roomId = roomId; addSocketServer2Map(this); try { sendMessage("連線成功", true); } catch (IOException e) { } } /** * 連線關閉呼叫的方法 */ @OnClose public void onClose() { List<WebSocketServer> wssList = webSocketMap.get(roomId); if (wssList != null) { for (WebSocketServer item : wssList) { if (item.session.getId().equals(session.getId())) { wssList.remove(item); if (wssList.isEmpty()) { webSocketMap.remove(roomId); } break; } } } } /** * 收到客戶端訊息後呼叫的方法 */ @OnMessage public void onMessage(String message, Session session) { //群發訊息 String msg = filterMessage(message); if (msg != null) { sendInfo(msg, roomId, session); } } /** * 發生錯誤時,呼叫的方法 */ @OnError public void onError(Session session, Throwable error) { } 複製程式碼
這樣,服務端的程式碼就實現完了,這裡僅貼出來部分原始碼,文後會給出專案原始碼地址。
3. 實現客戶端頁面
<script type="text/javascript"> var ws; function setConnected(connected){ document.getElementById('connect').disabled = connected; document.getElementById('disconnect').disabled = !connected; document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden'; $("#response").html(); } function connect(){ var roomId = $('#roomId').val(); ws = new WebSocket('ws://localhost:8080/webSocket/' + roomId); ws.onopen = WSonOpen; ws.onmessage = WSonMessage; ws.onclose = WSonClose; ws.onerror = WSonError; } function WSonOpen() { var message = { name:'Server', chatContent:'成功連線' } setConnected(true); showResponse(message) }; function WSonMessage(event) { var message = { name:'Server', chatContent:event.data } showResponse(message) }; function WSonClose() { var message = { name:'Server', chatContent:'已斷開' } showResponse(message) }; function WSonError() { var message = { name:'Server', chatContent:'連線錯誤!' } showResponse(message) }; function disconnect(){ ws.close() setConnected(false); console.log("Disconnected"); } function sendMessage(){ var chatContent = $("#chatContent").val(); var roomId = $('#roomId').val(); ws.send(JSON.stringify({'roomId':roomId,'chatContent':chatContent})) } function showResponse(message){ var response = $("#response").val(); $("#response").val(response+message.name+': '+message.chatContent+'\n'); } </script> 複製程式碼
客戶端頁面實現了簡單的連線、斷開和訊息傳送功能。這部分就不詳細介紹了。
4. 演示截圖

原始碼地址
本篇的原始碼地址:
總結
本篇直接使用了Tomcat提供的WebSocket,也是一種相對靈活的實現方式,只需要按照上述步驟來實現即可。集中精力編寫業務邏輯程式碼。
整個一個系列下來,我們介紹了幾種實現WebSocket的方式,有的整合度高,有些相對靈活。大家可以按實際業務需求來選取合適的方式。至此,這個系列就結束了。感謝大家閱讀。
小銘出品,必屬精品
歡迎關注xNPE技術論壇,更多原創乾貨每日推送。
