SpringMvc中使用JSR356定義的WebSocket規範(tomcat8)與前端通訊
阿新 • • 發佈:2019-01-13
參考:
【Java Web開發學習】Spring MVC整合WebSocket通訊
很多時候,後端增刪改查了一個數據,前端需要實時進行資料重新整理,這時候,正常的Http請求就無法滿足要求了(不輪詢),就需要一個可以實現客戶端和伺服器端的長連線,雙向實時通訊。就是websocket。
websocket是java標準庫的一部分,位於javax包下,但它只是定義一些介面。
websocket有不同的實現,如Tomcat的,jetty的,Spring的,還有一個名叫TooTallNate組織釋出的java-websocket庫,atmosphere庫,socket.io的java版本等。
這裡使用web應用伺服器是tomcat8,在javax.websocket接口出來之前,tomcat7就已經對websocket提供支援了。於是在javax.websocket出來之後,tomcat8就開始廢棄tomcat7中定義的websocket,tomcat7關於websocket的包位於org.apache.catalina.websocket中。
首先,tomcat8使用的javax.websocket,所以可以直接引用tomcat安裝目錄下/lIbrary/websocket.jar這個jar包,也可以直接在pom.xml中定義好Maven的依賴
<!-- Web Socket--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>4.0.1.RELEASE</version> </dependency> <dependency> <groupId>javax.websocket</groupId> <artifactId>javax.websocket-api</artifactId> <version>1.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> </dependency>
下面直接看服務端程式碼:
package com.springapp.mvc.websocket; import com.springapp.mvc.util.JsonUtils; import org.apache.http.util.TextUtils; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.socket.server.standard.SpringConfigurator; import javax.websocket.*; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.concurrent.CopyOnWriteArraySet; /** * Created by qinyy on 12/10/2018. */ /** * 加configurator = SpringConfigurator.class是為了讓這個類中可以通過註解的方式 注入例項 * 比如如果不加這個選項的話,那麼使用@Autowired 註解引入的例項就是null,無法被spring注入 */ @ServerEndpoint(value = "/websocket/{id}", configurator = SpringConfigurator.class) public class WebSocketService { //靜態變數,用來記錄當前線上連線數。應該把它設計成執行緒安全的。 private static int onlineCount = 0; // 維護一個map,用來管理不同使用者的不同socket例項 public static HashMap<String,WebSocketService> webSocketMap = new HashMap<String, WebSocketService>(); //與某個客戶端的連線會話,需要通過它來給客戶端傳送資料 private Session session; private String currentKey; private org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(WebSocketService.class.getSimpleName()); /** * 連線建立成功呼叫的方法 * @param session 可選的引數。session為與某個客戶端的連線會話,需要通過它來給客戶端傳送資料 */ @OnOpen public void onOpen(Session session,@PathParam(value = "id")String id){ this.session = session; webSocketMap.put(id,this); // 加入map中 currentKey = id; addOnlineCount(); //線上數加1 logger.info("有新連線加入!當前線上人數為" + getOnlineCount()); } /** * 連線關閉呼叫的方法 */ @OnClose public void onClose(){ if(!TextUtils.isEmpty(currentKey)) webSocketMap.remove(currentKey); subOnlineCount(); //線上數減1 logger.info("有一連線關閉!當前線上人數為" + getOnlineCount()); } /** * 收到客戶端訊息後呼叫的方法 * @param message 客戶端傳送過來的訊息 * @param session 可選的引數 */ @OnMessage public void onMessage(String message, Session session) { logger.info("來自客戶端的訊息:" + message); } /** * 發生錯誤時呼叫 * @param session * @param error */ @OnError public void onError(Session session, Throwable error){ logger.info("發生錯誤"); error.printStackTrace(); } public void sendObject(Object o) throws IOException { try { this.session.getBasicRemote().sendText(JsonUtils.encode(o)); } catch (Exception e) { logger.error(e.getMessage()); } } /** * 向指定client傳送資訊 * @param id * @param o */ public static void sendObjectToSomebody(String id,Object o) throws IOException { if(webSocketMap != null && webSocketMap.containsKey(id)) webSocketMap.get(id).sendObject(o); } /** * 向所有的client傳送相同的資料 * @param o * @throws IOException */ public static void sendObjectToAll(Object o) throws IOException { Iterator iter = webSocketMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); WebSocketService ser = (WebSocketService) entry.getValue(); ser.sendObject(o); } } public static synchronized int getOnlineCount() { return onlineCount; } public static synchronized void addOnlineCount() { WebSocketService.onlineCount++; } public static synchronized void subOnlineCount() { WebSocketService.onlineCount--; } }
這裡實現了簡單的websocket的管理,每次websocket啟動,都會從啟動的url中獲取到client傳過來的id,用來標識一個唯一的client,這樣就可以實現向指定client傳送資訊了。這裡需要注意的是,取ID時用的註解一定是@ParamPath。不然編譯的時候會儲存。
下面看下前端js的程式碼:
// 連線websocket
var webSocket;
function initWebsocket(id)
{
webSocket = new WebSocket("ws://localhost:18081/websocket/{"+id+"}");
webSocket.session
webSocket.onmessage = function (event)
{
onWebsockMessage(event.data);
};
}
// 重寫websocket 客戶端接受函式
function onWebsockMessage(msg)
{
var bean = JSON.parse(msg);
if(bean.type == 1)
{
// 如果是有新的位置訊息上傳,更新下對應的operation狀態
// 根據operationId選擇行
var row = $("table.operation-table").DataTable()
.row( function ( idx, data, node ) {
return data.operationId == bean.data.operationId ?
true : false;
} );
row.data().excutetime = $.myTime.UnixToDate(bean.data.excutetime,true);
row.draw();
}
}
/**
* 獲取一個5位的隨機數
* @returns {string}
*/
function getRnd5()
{
// 生成一個5位隨機數並向server傳送
var rnd = "";
for(var i=0;i<5;i++)
rnd+=Math.floor(Math.random()*10);
return rnd;
}
var socketId = getRnd5();
initWebsocket(socketId);