WebSocket的故事(六)—— Springboot中,實現更靈活的WebSocket

概述

WebSocket的故事系列計劃分五大篇六章,旨在由淺入深的介紹WebSocket以及在Springboot中如何快速構建和使用WebSocket提供的能力。本系列計劃包含以下幾篇文章:javascript

第一篇,什麼是WebSocket以及它的用途
第二篇,Spring中如何利用STOMP快速構建WebSocket廣播式消息模式
第三篇,Springboot中,如何利用WebSocket和STOMP快速構建點對點的消息模式(1)
第四篇,Springboot中,如何利用WebSocket和STOMP快速構建點對點的消息模式(2)
第五篇,Springboot中,實現網頁聊天室之自定義WebSocket消息代理
第六篇,Springboot中,實現更靈活的WebSockethtml

本篇的主線

本篇是這個系列的最後一篇,將介紹另外一種實現WebSocket的方式。仍然會以一個簡單聊天室爲例子進行講述。至此咱們也能夠根據具體狀況,選擇不一樣的實現方式。java

本篇適合的讀者

想了解如何在Springboot上自定義實現更爲複雜的WebSocket產品邏輯的各路有志青年。git

使用Tomcat提供的WebSocket支持

早在Java EE 7時,就發佈了JSR356規範。Tomcat7.0.47開始,也支持了統一的WebSocket接口。在使用Springboot時,也能夠輕鬆的使用Tomcat提供的這些API。今天咱們就來體驗一把Tomcat實現的WebSocketgithub

1. 引入依賴

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
複製代碼

Springboot內置了tomcat,咱們直接引入spring的這個高級組件便可。順便多說一句,Springboot的高級組件會自動引用基礎的組件,像spring-boot-starter-websocket就引入了spring-boot-starter-webspring-boot-starter,因此不要重複引入。web

2. 使用@ServerEndpoint建立WebSocket Endpoint

首先要注入ServerEndpointExporter,這個Bean會自動註冊使用了@ServerEndpoint註解聲明的WebSocket Endpoint。spring

package com.draw.wsdraw.websocket;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}
複製代碼

而後,咱們動手實現WebSocket服務的實現類,這裏是WebSocketServer,注意別忘了用@ServerEndpint@Component聲明下。雖然@Component默認是單例模式的,但Springboot仍是會爲每一個WebSocket鏈接初始化一個Bean,因此能夠用一個靜態Map保存起來。換句話說,每當有一個用戶向服務器發起鏈接時,都會建立一個WebSocketServer對象,將此對象按roomId保存在HashMap中,方便後續使用。tomcat

建立ServerEndpoint時,須要對應實現其所需的幾個功能性方法:OnOpen、OnMessage、OnClose、OnError服務器

  • @OnOpen:客戶端向服務端發起創建鏈接時,服務端調用,可傳入的參數爲Session(WebSocket的Session)和EndpointConfig。另外,還能夠加入帶@PathParam註解的參數。這裏咱們註解的參數是roomId,即在創建鏈接時,攜帶的請求地址上的參數,與咱們上一篇中介紹的{INFO}是同樣的做用。
  • @OnMessage:客戶端消息到來時調用,包含會話Session,根據消息的形式,若是是文本消息,傳入String類型參數或者Reader,若是是二進制消息,傳入byte[]類型參數或者InputStream。
  • @OnClose:當斷開鏈接,關閉WebSocket時調用。
  • @OnError:當發生錯誤時調用,傳入異常Session和錯誤信息。

重寫上述方法,便可實現WebSocket的服務端業務邏輯。websocket

@ServerEndpoint("/webSocket/{roomId}")
@Component
public class WebSocketServer {
    private static ConcurrentHashMap<String, List<WebSocketServer>> webSocketMap =
            new ConcurrentHashMap<>(3);

    //與某個客戶端的鏈接會話,須要經過它來給客戶端發送數據
    private Session session;

    //接收roomId
    private String roomId = "";

    /** * 鏈接創建成功調用的方法 */
    @OnOpen
    public void onOpen(Session session, EndpointConfig config, @PathParam("roomId") String roomId) {
        if (roomId == null || roomId.isEmpty()) return;
        this.session = session;
        this.roomId = roomId;
        addSocketServer2Map(this);
        try {
            sendMessage("鏈接成功", true);
        } catch (IOException e) {
        }
    }

    /** * 鏈接關閉調用的方法 */
    @OnClose
    public void onClose() {
        List<WebSocketServer> wssList = webSocketMap.get(roomId);
        if (wssList != null) {
            for (WebSocketServer item : wssList) {
                if (item.session.getId().equals(session.getId())) {
                    wssList.remove(item);
                    if (wssList.isEmpty()) {
                        webSocketMap.remove(roomId);
                    }
                    break;
                }
            }
        }
    }
    
    /** * 收到客戶端消息後調用的方法 */
    @OnMessage
    public void onMessage(String message, Session session) {
        //羣發消息
        String msg = filterMessage(message);
        if (msg != null) {
            sendInfo(msg, roomId, session);
        }
    }

    /** * 發生錯誤時,調用的方法 */
    @OnError
    public void onError(Session session, Throwable error) {
    }

複製代碼

這樣,服務端的代碼就實現完了,這裏僅貼出來部分源碼,文後會給出項目源碼地址。

3. 實現客戶端頁面

<script type="text/javascript"> var ws; function setConnected(connected){ document.getElementById('connect').disabled = connected; document.getElementById('disconnect').disabled = !connected; document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden'; $("#response").html(); } function connect(){ var roomId = $('#roomId').val(); ws = new WebSocket('ws://localhost:8080/webSocket/' + roomId); ws.onopen = WSonOpen; ws.onmessage = WSonMessage; ws.onclose = WSonClose; ws.onerror = WSonError; } function WSonOpen() { var message = { name:'Server', chatContent:'成功鏈接' } setConnected(true); showResponse(message) }; function WSonMessage(event) { var message = { name:'Server', chatContent:event.data } showResponse(message) }; function WSonClose() { var message = { name:'Server', chatContent:'已斷開' } showResponse(message) }; function WSonError() { var message = { name:'Server', chatContent:'鏈接錯誤!' } showResponse(message) }; function disconnect(){ ws.close() setConnected(false); console.log("Disconnected"); } function sendMessage(){ var chatContent = $("#chatContent").val(); var roomId = $('#roomId').val(); ws.send(JSON.stringify({'roomId':roomId,'chatContent':chatContent})) } function showResponse(message){ var response = $("#response").val(); $("#response").val(response+message.name+': '+message.chatContent+'\n'); } </script>
複製代碼

客戶端頁面實現了簡單的鏈接、斷開和消息發送功能。這部分就不詳細介紹了。

4. 演示截圖

源碼地址

本篇的源碼地址:

另外一種WebSocket的實現方式

總結

本篇直接使用了Tomcat提供的WebSocket,也是一種相對靈活的實現方式,只須要按照上述步驟來實現便可。集中精力編寫業務邏輯代碼。

整個一個系列下來,咱們介紹了幾種實現WebSocket的方式,有的集成度高,有些相對靈活。你們能夠按實際業務需求來選取合適的方式。至此,這個系列就結束了。感謝你們閱讀。

小銘出品,必屬精品

歡迎關注xNPE技術論壇,更多原創乾貨每日推送。

相關文章
相關標籤/搜索