前言:使用此webscoket務必確保生產環境能相容/支援。使用此webscoket務必確保生產環境能相容/支援。使用此webscoket務必確保生產環境能相容/支援。主要是tomcat的相容與支援。
有個需求:APP使用者產生某個操作,需要讓後台管理系統部分人員感知(表現為一個頁面訊息)。
最早版本是後台管理系統輪訓,每隔一段時間輪訓一次,由於訊息重要,每隔幾秒就查一次。這樣做明顯很不雅。會消耗大量資源,並且大部分請求是沒有用的(查不到資料進來),很藍瘦。
後來,想著用訊息推送的方式來處理這個邏輯。使用者在app產生了目標操作,即產生一個訊息,推送給後台管理系統的對應使用者。
然後我就找各種資料,一開始同事推薦dwz,後來發現不太適用於目前的項目(也許能實現只是我不知道如何?)。
後來瞭解到WebSocket,網上看了很多文檔都是類似聊天室的情境,有些不同。在此,我主要側重介紹下 伺服器主動推送,由服務端來觸發。
WebSocket 主要能實現的情境:
1、網頁聊天室
2、伺服器訊息即時通知
WebSocket 使用方法應該有很多,在次介紹下使用 tomcat8+h5 環境下的實現。
ps:我自己的測試環境是tomcat7這樣寫是不行的。wang115032337《https://blog.csdn.net/wang115032337》這位朋友在他的環境下,tomcat7/8都可以用本文章的寫法,只不過需要去除WebSocketConfig類(有文章表示tomcat7和8對websocket的支援是不同的,本人未深入瞭解)
話不多說,直接上代碼,想深入瞭解WebSocket 的請查閱相關介紹。
1.pom
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
2. 使用@ServerEndpoint創立websocket endpoint(
wang115032337這位朋友在他的環境下加入@ServerEndpoint類會報錯,直接刪除了仍可用)
@Configurationpublic class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); }}3.具體實作類別
可自己選擇url要不要帶參數
package com.star.manager.service;import java.io.IOException;import java.util.concurrent.CopyOnWriteArraySet;import javax.websocket.OnClose;import javax.websocket.OnError;import javax.websocket.OnMessage;import javax.websocket.OnOpen;import javax.websocket.Session;import javax.websocket.server.ServerEndpoint;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Component;@Slf4j//@ServerEndpoint("/websocket/{user}")@ServerEndpoint(value = "/websocket")@Componentpublic class WebSocketServer { //靜態變數,用來記錄當前線上串連數。應該把它設計成安全執行緒的。 private static int onlineCount = 0; //concurrent包的安全執行緒Set,用來存放每個用戶端對應的MyWebSocket對象。 private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>(); //與某個用戶端的串連會話,需要通過它來給用戶端發送資料 private Session session; /** * 串連建立成功調用的方法*/ @OnOpen public void onOpen(Session session) { this.session = session; webSocketSet.add(this); //加入set中 addOnlineCount(); //線上數加1 log.info("有新串連加入。當前線上人數為" + getOnlineCount()); try { sendMessage("串連成功"); } catch (IOException e) { log.error("websocket IO異常"); } }////串連開啟時執行//@OnOpen//public void onOpen(@PathParam("user") String user, Session session) {//currentUser = user;//System.out.println("Connected ... " + session.getId());//} /** * 串連關閉調用的方法 */ @OnClose public void onClose() { webSocketSet.remove(this); //從set中刪除 subOnlineCount(); //線上數減1 log.info("有一串連關閉。當前線上人數為" + getOnlineCount()); } /** * 收到用戶端訊息後調用的方法 * * @param message 用戶端發送過來的訊息*/ @OnMessage public void onMessage(String message, Session session) { log.info("來自用戶端的訊息:" + message); //群發訊息 for (WebSocketServer item : webSocketSet) { try { item.sendMessage(message); } catch (IOException e) { e.printStackTrace(); } } }/** * * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { log.error("發生錯誤"); error.printStackTrace(); } public void sendMessage(String message) throws IOException { this.session.getBasicRemote().sendText(message); } /** * 群發自訂訊息 * */ public static void sendInfo(String message) throws IOException { log.info(message); for (WebSocketServer item : webSocketSet) { try { item.sendMessage(message); } catch (IOException e) { continue; } } } public static synchronized int getOnlineCount() { return onlineCount; } public static synchronized void addOnlineCount() { WebSocketServer.onlineCount++; } public static synchronized void subOnlineCount() { WebSocketServer.onlineCount--; }}
產生一個訊息:產生訊息情境有多種,http(s),定時任務,mq等,這貼一個httpq請求的controller代碼
@RequestMapping(value="/pushVideoListToWeb",method=RequestMethod.POST,consumes = "application/json") public @ResponseBody Map<String,Object> pushVideoListToWeb(@RequestBody Map<String,Object> param) { Map<String,Object> result =new HashMap<String,Object>(); try { WebSocketServer.sendInfo("有新客戶呼入,sltAccountId:"+CommonUtils.getValue(param, "sltAccountId")); result.put("operationResult", true); }catch (IOException e) { result.put("operationResult", true); } return result; }
重要的地方我都加粗了,主要是這段,使用這個方法,可以實現伺服器主動推送。
public void sendMessage(String message) throws IOException { this.session.getBasicRemote().sendText(message); }
4.js(html就不寫了,隨便找個能觸發這個js的就可以)
//socket = new WebSocket("ws://localhost:9094/starManager/websocket/張三");var socket;if(typeof(WebSocket) == "undefined") {console.log("您的瀏覽器不支援WebSocket");}else{console.log("您的瀏覽器支援WebSocket");//實現化WebSocket對象,指定要已連線的服務器地址與連接埠 建立串連//socket = new WebSocket("ws://localhost:9094/starManager/websocket/張三")
socket = new WebSocket("ws://localhost:9094/starManager/websocket");//開啟事件socket.onopen = function() {console.log("Socket 已開啟");//socket.send("這是來自用戶端的訊息" + location.href + new Date());};//獲得訊息事件socket.onmessage = function(msg) {console.log(msg.data);//發現訊息進入 調後台擷取getCallingList();};//關閉事件socket.onclose = function() {console.log("Socket已關閉");};//發生了錯誤事件socket.onerror = function() {alert("Socket發生了錯誤");} $(window).unload(function(){ socket.close();});// $("#btnSend").click(function() {// socket.send("這是來自用戶端的訊息" + location.href + new Date());// });//// $("#btnClose").click(function() {// socket.close();// });}
簡單說說:
通過前端代碼
socket = new WebSocket("ws://localhost:9094/starManager/websocket");
其中,starManager是工程名,/webscoket是訪問路徑名
建立串連,前端調用scoket.open() 會使後台在靜態成員變數webSocketSet裡面增加一個元素,相當於一個緩衝。後台服務調用sendMessage
(指定某個使用者,定向)或sendInfo(遍曆webSocketSet逐個發送,類似群發)方法,即可向已登入的用戶端推送訊息。
代碼就這麼多。我的用這些代碼就跑的起來。做的時候出現過頁面報404等錯誤,如果也是spring boot+h5,仔細核對下和我代碼有無區別,加配置 路徑是有ok,問題應該不大。
如果你恰好也有可以用WebSocket實現的類似情境,希望對你有協助。如有寫的不對或不夠好的地方,歡迎指正。