1. 程式人生 > >SpringMvc中使用JSR356定義的WebSocket規範(tomcat8)與前端通訊

SpringMvc中使用JSR356定義的WebSocket規範(tomcat8)與前端通訊

參考:

WebSocket初探

【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);