spring boot Websocket(使用筆記)
使用websocket有兩種方式:1是使用sockjs,2是使用h5的標準。使用Html5標準自然更方便簡單,所以記錄的是配合h5的使用方法。
1、pom
核心是@ServerEndpoint這個註解。這個註解是Javaee標準裡的註解,tomcat7以上已經對其進行了實現,如果是用傳統方法使用tomcat釋出專案,只要在pom檔案中引入javaee標準即可使用。
<dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> <scope>provided</scope> </dependency>
但使用springboot的內建tomcat時,就不需要引入javaee-api了,spring-boot已經包含了。使用springboot的websocket功能首先引入springboot元件。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> <version>1.3.5.RELEASE</version> </dependency>
順便說一句,springboot的高階元件會自動引用基礎的元件,像spring-boot-starter-websocket就引入了spring-boot-starter-web和spring-boot-starter,所以不要重複引入。
2、使用@ServerEndpoint創立websocket endpoint
首先要注入ServerEndpointExporter,這個bean會自動註冊使用了@ServerEndpoint註解宣告的Websocket endpoint。要注意,如果使用獨立的servlet容器,而不是直接使用springboot的內建容器,就不要注入ServerEndpointExporter,因為它將由容器自己提供和管理。
@Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
接下來就是寫websocket的具體實現類,很簡單,直接上程式碼:
@ServerEndpoint(value = "/websocket") @Component public class MyWebSocket { //靜態變數,用來記錄當前線上連線數。應該把它設計成執行緒安全的。 private static int onlineCount = 0; //concurrent包的執行緒安全Set,用來存放每個客戶端對應的MyWebSocket物件。 private static CopyOnWriteArraySet<MyWebSocket> webSocketSet = new CopyOnWriteArraySet<MyWebSocket>(); //與某個客戶端的連線會話,需要通過它來給客戶端傳送資料 private Session session; /** * 連線建立成功呼叫的方法*/ @OnOpen public void onOpen(Session session) { this.session = session; webSocketSet.add(this); //加入set中 addOnlineCount(); //線上數加1 System.out.println("有新連線加入!當前線上人數為" + getOnlineCount()); try { sendMessage(CommonConstant.CURRENT_WANGING_NUMBER.toString()); } catch (IOException e) { System.out.println("IO異常"); } } /** * 連線關閉呼叫的方法 */ @OnClose public void onClose() { webSocketSet.remove(this); //從set中刪除 subOnlineCount(); //線上數減1 System.out.println("有一連線關閉!當前線上人數為" + getOnlineCount()); } /** * 收到客戶端訊息後呼叫的方法 * * @param message 客戶端傳送過來的訊息*/ @OnMessage public void onMessage(String message, Session session) { System.out.println("來自客戶端的訊息:" + message); //群發訊息 for (MyWebSocket item : webSocketSet) { try { item.sendMessage(message); } catch (IOException e) { e.printStackTrace(); } } } /** * 發生錯誤時呼叫 @OnError public void onError(Session session, Throwable error) { System.out.println("發生錯誤"); error.printStackTrace(); } public void sendMessage(String message) throws IOException { this.session.getBasicRemote().sendText(message); //this.session.getAsyncRemote().sendText(message); } /** * 群發自定義訊息 * */ public static void sendInfo(String message) throws IOException { for (MyWebSocket item : webSocketSet) { try { item.sendMessage(message); } catch (IOException e) { continue; } } } public static synchronized int getOnlineCount() { return onlineCount; } public static synchronized void addOnlineCount() { MyWebSocket.onlineCount++; } public static synchronized void subOnlineCount() { MyWebSocket.onlineCount--; } } 使用springboot的唯一區別是要@Component宣告下,而使用獨立容器是由容器自己管理websocket的,但在springboot中連容器都是spring管理的。 雖然@Component預設是單例模式的,但springboot還是會為每個websocket連線初始化一個bean,所以可以用一個靜態set儲存起來。 3、前端程式碼 <!DOCTYPE HTML> <html> <head> <title>My WebSocket</title> </head> <body> Welcome<br/> <input id="text" type="text" /><button onclick="send()">Send</button> <button onclick="closeWebSocket()">Close</button> <div id="message"> </div> </body> <script type="text/javascript"> var websocket = null; //判斷當前瀏覽器是否支援WebSocket if('WebSocket' in window){ websocket = new WebSocket("ws://localhost:8084/websocket"); } else{ alert('Not support websocket') } //連線發生錯誤的回撥方法 websocket.onerror = function(){ setMessageInnerHTML("error"); }; //連線成功建立的回撥方法 websocket.onopen = function(event){ setMessageInnerHTML("open"); } //接收到訊息的回撥方法 websocket.onmessage = function(event){ setMessageInnerHTML(event.data); } //連線關閉的回撥方法 websocket.onclose = function(){ setMessageInnerHTML("close"); } //監聽視窗關閉事件,當視窗關閉時,主動去關閉websocket連線,防止連線還沒斷開就關閉視窗,server端會拋異常。 window.onbeforeunload = function(){ websocket.close(); } //將訊息顯示在網頁上 function setMessageInnerHTML(innerHTML){ document.getElementById('message').innerHTML += innerHTML + '<br/>'; } //關閉連線 function closeWebSocket(){ websocket.close(); } //傳送訊息 function send(){ var message = document.getElementById('text').value; websocket.send(message); } </script> </html>
4、總結
springboot已經做了深度的整合和優化,要注意是否添加了不需要的依賴、配置或宣告。由於很多講解元件使用的文章是和spring整合的,會有一些配置,在使用springboot時,由於springboot已經有了自己的配置,再這些配置有可能導致各種各樣的異常。