1. 程式人生 > >spring websocket小結

spring websocket小結

WebSocket協議是基於TCP的一種新的網路協議。它實現了瀏覽器與伺服器全雙工(full-duplex)通訊——可以通俗的解釋為伺服器主動傳送資訊給客戶端。

websocket允許通過JavaScript建立與遠端伺服器的連線,從而實現客戶端與伺服器間雙向的通訊。

在websocket中有兩個方法:       1、send() 向遠端伺服器傳送資料     2、close() 關閉該websocket連線 websocket同時還定義了幾個監聽函式         1、onopen 當網路連線建立時觸發該事件     2、onerror 當網路發生錯誤時觸發該事件     3、onclose 當websocket被關閉時觸發該事件     4、onmessage 當websocket接收到伺服器發來的訊息時觸發的事件,也是通訊中最重要的一個監聽事件。 websocket還定義了一個readyState屬性,這個屬性可以返回websocket所處的狀態:     1、CONNECTING(0) websocket正嘗試與伺服器建立連線     2、OPEN(1) websocket與伺服器已經建立連線     3、CLOSING(2) websocket正在關閉與伺服器的連線     4、CLOSED(3) websocket已經關閉了與伺服器的連線  websocket的url開頭是ws

,如果需要ssl加密可以使用wss,當我們呼叫websocket的構造方法構建一個websocket物件(new WebSocket(url))之後,就可以進行即時通訊了。

spring4.0以上支援websocket,要求servlet-api必須是3.0+

WebSocket:

        <!-- WebSocket -->
        <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-websocket</artifactId>
           <version>4.0.2.RELEASE</version>
        </dependency>

        <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-messaging</artifactId>
           <version>4.0.2.RELEASE</version>
        </dependency>

Servlet-api

    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
    </dependency>

web.xml的namespace也要確保是3.0+

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://java.sun.com/xml/ns/javaee
        http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">
    // 使用<absolute-ordering />來選擇性地啟用或禁用Web片段(和SCI掃描)
    <absolute-ordering/>

</web-app>

如果要支援非同步的servlet3.x,可以在web.xml下的servlet和filter裡面加上

<async-supported>true</async-supported>

service服務端的具體實現

首先是配置檔案類(註解),給伺服器新增websocket服務

@Configuration
@EnableWebSocket
public class MyWebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {

    @Autowired
    MyWebSocketHandler handler;

    public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {

        //前臺 可以使用websocket環境
        webSocketHandlerRegistry.addHandler(handler, "/chats/connect/socket").addInterceptors(new MyHandShakeInterceptor());

        //前臺 不可以使用websocket環境,則使用sockjs進行模擬連線
        webSocketHandlerRegistry.addHandler(handler, "/chats/connect/socket/sockjs").addInterceptors(new MyHandShakeInterceptor()).withSockJS();
    }
}

然後是建立握手攔截器

public class MyHandShakeInterceptor implements HandshakeInterceptor {

    private static final Logger logger = LogManager.getLogger(MyWebSocketHandler.class);

    public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {
        if (serverHttpRequest instanceof ServletServerHttpRequest) {
            HttpServletRequest servletRequest = ((ServletServerHttpRequest) serverHttpRequest).getServletRequest();
            //當前的登入者
            Long userId = (Long) servletRequest.getSession().getAttribute("userId");
            if(userId!=null){
                map.put("uid", userId);//為伺服器建立WebSocketSession做準備
                logger.info("使用者id:"+userId+" 加入");
            }else{
                logger.error("沒有獲取session中的當前登陸者資訊");
            }
        }
        return true;
    }

    public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {
            logger.info("握手後");
    }
}

最後是建立websocket的處理類

@Component
public class MyWebSocketHandler implements WebSocketHandler {

    @Autowired
    private ChatService chatService;

    @Autowired
    private ChatMessageService chatMessageService;

    private static final Logger logger = LogManager.getLogger(MyWebSocketHandler.class);

    // 儲存所有的使用者session
    private final static Map<Long,WebSocketSession> sessions = new HashMap<>();

    //握手實現連線後
    @Override
    public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception {
        logger.info("連線成功。。。。。。");
        Long userId = (Long) webSocketSession.getAttributes().get("uid");
        if(sessions.get(userId) == null)
            sessions.put(userId,webSocketSession);
        logger.info("session: "+sessions);
    }

    //傳送資訊前的處理
    @Override
    public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {

        logger.info("使用者"+webSocketSession.getAttributes().get("uid")+":傳送訊息:" + webSocketMessage.getPayload().toString());

        String message = webSocketMessage.getPayload().toString();
        String[] buf = message.split(":");
        if(buf.length == 2){ //切換聊天,用來標記傳送方的資訊已讀
            Long chatId = Long.valueOf(buf[1]);
            chatService.setRead(chatId,(Long) webSocketSession.getAttributes().get("uid"));
        }
        if(buf.length == 3){ //傳送訊息
            Long chatId = Long.valueOf(buf[1]);
            String content = buf[2];
            Chat chat = chatService.getChat(chatId);
            Long userFromId = (Long) webSocketSession.getAttributes().get("uid"); //當前登入者為傳送者
            Long userToId = null;
            if(userFromId == chat.getUserId()){
                userToId = chat.getOppositeUser().getId();
            }else{
                userToId = chat.getUserId();
            }
            //將資訊儲存至資料庫
            try {
                ChatMessage chatMessage = chatMessageService.addChatMessage(userFromId, chat.getId(), content);
                String sendMsg = "{\"chatId\":"+chatId+",\"message\":{\"uuid\":\""+chatMessage.getUuid()+"\",\"content\":\""+chatMessage.getContent()+
                        "\",\"createdAt\":"+chatMessage.getCreatedAt().getTime()+",\"user\":{\"id\":"+chatMessage.getUser().getId()+",\"slug\":\""+chatMessage.getUser().getSlug()+
                        "\",\"nickname\":\""+chatMessage.getUser().getNickname()+"\",\"avatar\":\""+chatMessage.getUser().getAvatar().getPath()+"\"}}}";
                logger.info("接收的訊息格式:"+sendMsg);
                //傳送Socket資訊
                sendMessageToUser(userToId, new TextMessage(sendMsg,true),webSocketSession);
            }catch (Exception e){
                e.printStackTrace();
            }
        }

        //將JSON格式的訊息通過Gson轉換成Map
        //通過getPayload().toString()獲取訊息具體內容
        //Map<String,String> msg = new Gson().fromJson(webSocketMessage.getPayload().toString(),new TypeToken<Map<String, String>>() {}.getType());

    }

    @Override
    public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception {
        if (webSocketSession.isOpen()) {
            webSocketSession.close();
        }
        sessions.remove(webSocketSession.getAttributes().get("uid"));
        logger.info("webSocket異常處理" + throwable.getMessage());
    }

    @Override
    public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception {
        logger.info("連線關閉");
        sessions.remove(webSocketSession.getAttributes().get("uid"));
    }

    @Override
    public boolean supportsPartialMessages() {
        return false;
    }

    //傳送資訊的實現
    public void sendMessageToUser(Long uid, TextMessage message,WebSocketSession webSocketSession) throws IOException {
        WebSocketSession userToSession = null;
        //遍歷所有已連線的使用者
        for (Map.Entry<Long, WebSocketSession> entry : sessions.entrySet()) {
            Long connectedUserId = (Long) entry.getValue().getAttributes().get("uid");
            if (connectedUserId == uid) {
                userToSession = entry.getValue();
                break;
            }
        }
        if(userToSession == null){
            logger.info("對方暫時不線上");
            webSocketSession.sendMessage(new TextMessage("對方暫時不線上"));
        }
        if (userToSession != null && userToSession.isOpen()) {
            logger.info("傳送訊息給:"+uid);
            userToSession.sendMessage(message);
        }
    }
}

client客戶端的實現

登入頁面

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登入頁面</title>
</head>
<body>
<h1>登入</h1>
<table>
    <tr>
        <td>使用者名稱</td>
        <td>
            <input type="text" name="email" id="email">
        </td>
    </tr>
    <tr>
        <td>密碼</td>
        <td>
            <input type="password" name="password" id="password">
        </td>
    </tr>
    <tr>
        <td>
            驗證碼
        </td>
        <td>
            <input type="text" name="token" id="token">
            <!-- 驗證碼地址 -->
            <img src="/token/get_token?type=login">
        </td>
    </tr>
    <tr>
        <td>
            <button id="submit_btn">登入</button>
        </td>
        <td></td>
    </tr>
</table>
<script type="text/javascript" src="/js/jquery-3.1.1.js"></script>
<script type="text/javascript">
    $(function() {
        var $email = $('#email'),
            $password = $('#password'),
            $token = $('#token');
        $("#token").blur(function() {
            var token = {token: $token.val()};
            console.log(token);
            $.ajax({
                url: '/token/check?type=login', // 驗證碼校驗地址
                type: 'POST',
                data: JSON.stringify(token),
                contentType: 'application/json;charset=utf-8',
                dataType: 'json',
                success: function (resp) {
                    alert('成功');
                    console.log(resp)
                },
                error: function(resp) {
                    alert('失敗');
                    console.log(resp)
                }
            });
        });
        $("#submit_btn").click(function() {
            var data = {email: $email.val(), password: $password.val(), token: $token.val()};
            console.log(data);
            $.ajax({
                url: '/users/login', // 登入地址
                type: 'POST',
                data: JSON.stringify(data),
                contentType: 'application/json;charset=utf-8',
                dataType: 'json',
                success: function (resp) {
                    alert('成功');
                    console.log(resp)
                },
                error: function(resp) {
                    alert('失敗');
                    console.log(resp)
                }
            });
        })
    })
</script>
</body>
</html>

聊天室頁面展示

chat.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- 引入 JQuery  -->
    <script type="text/javascript" src="/js/jquery-3.1.1.js"></script>
    <!-- 引入 sockJS  -->
    <script type="text/javascript" src="/js/sockjs.min.js" ></script>
    <!-- 自定義JS檔案 -->
    <script type="text/javascript" src="/js/chat.js"></script>
</head>
<body>
    <!-- 最外邊框 -->
    <div style="margin: 20px auto; border: 1px solid blue; width: 300px; height: 500px;">

        <!-- 訊息展示框 -->
        <div id="msg" style="width: 100%; height: 70%; border: 1px solid yellow;overflow: auto;"></div>

        <!-- 訊息編輯框 -->
        <textarea id="tx" style="width: 100%; height: 20%;"></textarea>

        <!-- 訊息傳送按鈕 -->
        <button id="TXBTN" style="width: 100%; height: 8%;">傳送資料</button>
    </div>
</body>
</html>

JS-客戶端主要的實現

chat.js

$(function() {

    var websocket;

    // 首先判斷是否支援WebSocket
    if('WebSocket' in window) {
        websocket = new WebSocket("ws://localhost:8080/chats/connect/socket");
    } else if('MozWebSocket' in window) {
        websocket = new MozWebSocket("ws://localhost:8080/chats/connect/socket");
    } else {
        websocket = new SockJS("http://localhost:8080/chats/connect/socket/sockjs");
    }

    // 開啟時
    websocket.onopen = function(event) {
        console.log("連線成功");
        console.log(event);
    };

    // 處理訊息時
    websocket.onmessage = function(event) {
        $("#msg").append(event.data);
        console.log("處理訊息");
        console.log(event);
    };

    websocket.onerror = function(event) {
        console.log("連線失敗");
        console.log(event);
    };

    websocket.onclose = function(event) {
        console.log("socket連線斷開");
        console.log(event);
    };

    // 點選了傳送訊息按鈕的響應事件
    $("#TXBTN").click(function(){
        // 獲取訊息內容
        var text = $("#tx").val();
        // 判斷
        if(text == null || text == ""){
            alert(" content  can not empty!!");
            return false;
        }
        //var msg = {
        //    content: text
        //};
        // 傳送訊息
        //websocket.send(JSON.stringify(msg));
        websocket.send("message:"+1+":"+text);
    });

});

參考文章: