溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

SpringBoot中怎么使用WebSocket創(chuàng)建一個聊天室

發(fā)布時間:2021-06-15 14:16:14 來源:億速云 閱讀:224 作者:Leah 欄目:大數(shù)據(jù)

今天就跟大家聊聊有關(guān)SpringBoot中怎么使用WebSocket創(chuàng)建一個聊天室,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

一、什么是 WebSocket

1. 簡介

WebSocket 協(xié)議是基于 TCP 的一種網(wǎng)絡(luò)協(xié)議,它實現(xiàn)了瀏覽器與服務(wù)器全雙工(Full-duplex)通信——允許服務(wù)器主動發(fā)送信息給客戶端。

以前,很多網(wǎng)站為了實現(xiàn)推送技術(shù),所用的技術(shù)都是輪詢。輪詢是在特定的的時間間隔(如每 1 秒),由瀏覽器對服務(wù)器發(fā)出 HTTP 請求,然后由服務(wù)器返回最新的數(shù)據(jù)給客戶端的瀏覽器。這種傳統(tǒng)的模式帶來很明顯的缺點(diǎn),即瀏覽器需要不斷地向服務(wù)器發(fā)出請求,然而 HTTP 請求可能包含較長的頭部,其中真正有效的數(shù)據(jù)可能只是很小的一部分,顯然這樣會浪費(fèi)很多的帶寬等資源。

在這種情況下,HTML 5 定義了 WebSocket 協(xié)議,能更好得節(jié)省服務(wù)器資源和帶寬,并且能夠更實時地進(jìn)行通訊。WebSocket 協(xié)議在 2008 年誕生,2011 年成為國際標(biāo)準(zhǔn),現(xiàn)在主流的瀏覽器都已經(jīng)支持。

它的最大特點(diǎn)就是,服務(wù)器可以主動向客戶端推送信息,客戶端也可以主動向服務(wù)器發(fā)送信息,是真正的雙向平等對話,屬于服務(wù)器推送技術(shù)的一種。在 WebSocket API 中,瀏覽器和服務(wù)器只需要完成一次握手,兩者之間就直接可以創(chuàng)建持久性的連接,并進(jìn)行雙向數(shù)據(jù)傳輸。

2. 優(yōu)點(diǎn)

  • 較少的控制開銷

在連接創(chuàng)建后,服務(wù)器和客戶端之間交換數(shù)據(jù)時,用于協(xié)議控制的數(shù)據(jù)包頭部相對較小。在不包含擴(kuò)展的情況下,對于服務(wù)器到客戶端的內(nèi)容,此頭部大小只有 2 至 10 字節(jié)(和數(shù)據(jù)包長度有關(guān));對于客戶端到服務(wù)器的內(nèi)容,此頭部還需要加上額外的 4 字節(jié)的掩碼。相對于 HTTP 請求每次都要攜帶完整的頭部,此項開銷顯著減少了。

  • 更強(qiáng)的實時性

由于協(xié)議是全雙工的,所以服務(wù)器可以隨時主動給客戶端下發(fā)數(shù)據(jù)。相對于 HTTP 請求需要等待客戶端發(fā)起請求服務(wù)端才能響應(yīng),延遲明顯更少;即使是和 Comet 等類似的長輪詢比較,其也能在短時間內(nèi)更多次地傳遞數(shù)據(jù)。

  • 保持連接狀態(tài)

與 HTTP 不同的是,Websocket 需要先創(chuàng)建連接,這就使得其成為一種有狀態(tài)的協(xié)議,之后通信時可以省略部分狀態(tài)信息,而 HTTP 請求可能需要在每個請求都攜帶狀態(tài)信息(如身份認(rèn)證等)。

  • 更好的二進(jìn)制支持

Websocket 定義了二進(jìn)制幀,相對 HTTP,可以更輕松地處理二進(jìn)制內(nèi)容。 可以支持?jǐn)U展。Websocket 定義了擴(kuò)展,用戶可以擴(kuò)展協(xié)議、實現(xiàn)部分自定義的子協(xié)議。如部分瀏覽器支持壓縮等。

  • 更好的壓縮效果

相對于 HTTP 壓縮,Websocket 在適當(dāng)?shù)臄U(kuò)展支持下,可以沿用之前內(nèi)容的上下文,在傳遞類似的數(shù)據(jù)時,可以顯著地提高壓縮率。

WebSocket 在握手之后便直接基于 TCP 進(jìn)行消息通信,但 WebSocket 只是 TCP 上面非常輕的一層,它僅僅將 TCP 的字節(jié)流轉(zhuǎn)換成消息流(文本或二進(jìn)制),至于怎么解析這些消息的內(nèi)容完全依賴于應(yīng)用本身。

因此為了協(xié)助 Client 與 Server 進(jìn)行消息格式的協(xié)商,WebSocket 在握手的時候保留了一個子協(xié)議字段。

二、Stomp 和 WebSocket

STOMP 即 Simple(or Streaming)Text Orientated Messaging Protocol,簡單(流)文本定向消息協(xié)議,它提供了一個可互操作的連接格式,允許 STOMP 客戶端與任意 STOMP 消息代理(Broker)進(jìn)行交互。STOMP 協(xié)議由于設(shè)計簡單,易于開發(fā)客戶端,因此在多種語言和多種平臺上得到了廣泛的應(yīng)用。

STOMP 協(xié)議并不是為 Websocket 設(shè)計的,它是屬于消息隊列的一種協(xié)議,它和 Amqp、Jms 平級。只不過由于它的簡單性恰巧可以用于定義 Websocket 的消息體格式。可以這么理解,Websocket 結(jié)合 Stomp 子協(xié)議段,來讓客戶端和服務(wù)器在通信上定義的消息內(nèi)容達(dá)成一致。

STOMP 協(xié)議分為客戶端和服務(wù)端,具體如下。

1. STOMP 服務(wù)端

STOMP 服務(wù)端被設(shè)計為客戶端可以向其發(fā)送消息的一組目標(biāo)地址。STOMP 協(xié)議并沒有規(guī)定目標(biāo)地址的格式,它由使用協(xié)議的應(yīng)用自己來定義。例如,/topic/a、/queue/a、queue-a 對于 STOMP 協(xié)議來說都是正確的。應(yīng)用可以自己規(guī)定不同的格式以此來表明不同格式代表的含義。比如應(yīng)用自己可以定義以 /topic 打頭的為發(fā)布訂閱模式,消息會被所有消費(fèi)者客戶端收到,以 /user 開頭的為點(diǎn)對點(diǎn)模式,只會被一個消費(fèi)者客戶端收到。

2. STOMP 客戶端

  • 對于 STOMP 協(xié)議來說,客戶端會扮演下列兩種角色的任意一種:

    • 作為生產(chǎn)者,通過 SEND 幀發(fā)送消息到指定的地址;

    • 作為消費(fèi)者,通過發(fā)送 SUBSCRIBE 幀到已知地址來進(jìn)行消息訂閱,而當(dāng)生產(chǎn)者發(fā)送消息到這個訂閱地址后,訂閱該地址的其他消費(fèi)者會受到通過 MESSAGE 幀收到該消息。

  • 實際上,WebSocket 結(jié)合 STOMP 相當(dāng)于構(gòu)建了一個消息分發(fā)隊列,客戶端可以在上述兩個角色間轉(zhuǎn)換,訂閱機(jī)制保證了一個客戶端消息可以通過服務(wù)器廣播到多個其他客戶端,作為生產(chǎn)者,又可以通過服務(wù)器來發(fā)送點(diǎn)對點(diǎn)消息。

3. STOMP 幀結(jié)構(gòu)

COMMAND

header1:value1

header2:value2

Body^@

其中,^@ 表示行結(jié)束符。

  • 一個 STOMP 幀由三部分組成:命令、Header(頭信息)、Body(消息體)。

    • 命令使用 UTF-8 編碼格式,命令有 SEND、SUBSCRIBE、MESSAGE、CONNECT、CONNECTED 等。

    • Header 也使用 UTF-8 編碼格式,它類似 HTTP 的 Header,有 content-length、content-type 等。

    • Body 可以是二進(jìn)制也可以是文本,注意 Body 與 Header 間通過一個空行(EOL)來分隔。

  • 來看一個實際的幀例子:

SEND
destination:/broker/roomId/1
content-length:57

{“type":"OUT","content":"ossxxxxx-wq-yyyyyyyy"}
  • 第 1 行:表明此幀為 SEND 幀,是 COMMAND 字段。

  • 第 2 行:Header 字段,消息要發(fā)送的目的地址,是相對地址。

  • 第 3 行:Header 字段,消息體字符長度。

  • 第 4 行:空行,間隔 Header 與 Body。

  • 第 5 行:消息體,為自定義的 JSON 結(jié)構(gòu)。

更多 STOMP 協(xié)議細(xì)節(jié),可以參考 STOMP官網(wǎng)

三、WebSocket 事件

  • Websocket 使用 ws 或 wss 的統(tǒng)一資源標(biāo)志符,類似于 HTTPS,其中 wss 表示在 TLS 之上的 Websocket。例如:

ws://example.com/wsapi
wss://secure.example.com/
  • Websocket 使用和 HTTP 相同的 TCP 端口,可以繞過大多數(shù)防火墻的限制。默認(rèn)情況下,

  • Websocket 協(xié)議使用 80 端口;運(yùn)行在 TLS 之上時,默認(rèn)使用 443 端口。

事件事件處理程序描述
openSokcket onopen連接建立時觸發(fā)
messageSokcket onmessage客戶端接收服務(wù)端數(shù)據(jù)時觸發(fā)
errorSokcket onerror通訊發(fā)生錯誤時觸發(fā)
closeSokcket onclose鏈接關(guān)閉時觸發(fā)

下面是一個頁面使用 Websocket 的示例:

var ws = new WebSocket("ws://localhost:8080");

ws.onopen = function(evt) { 
  console.log("Connection open ..."); 
  ws.send("Hello WebSockets!");
};

ws.onmessage = function(evt) {
  console.log( "Received Message: " + evt.data);
  ws.close();
};

ws.onclose = function(evt) {
  console.log("Connection closed.");
};
  • Spring Boot 提供了 Websocket 組件 spring-boot-starter-websocket,用來支持在 Spring Boot 環(huán)境下對 Websocket 的使用。

四、Websocket 聊天室

  • Websocket 雙相通訊的特性非常適合開發(fā)在線聊天室,這里以在線多人聊天室為示例,演示 Spring Boot Websocket 的使用。

  • 首先我們梳理一下聊天室都有什么功能:

    • 支持用戶加入聊天室,對應(yīng)到 Websocket 技術(shù)就是建立連接 onopen

    • 支持用戶退出聊天室,對應(yīng)到 Websocket 技術(shù)就是關(guān)閉連接 onclose

    • 支持用戶在聊天室發(fā)送消息,對應(yīng)到 Websocket 技術(shù)就是調(diào)用 onmessage 發(fā)送消息

    • 支持異常時提示,對應(yīng)到 Websocket 技術(shù) onerror

1. 頁面開發(fā)

  • 利用前端框架 Bootstrap 渲染頁面,使用 HTML 搭建頁面結(jié)構(gòu),完整頁面內(nèi)容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>chat room websocket</title>
    <link rel="stylesheet" href="bootstrap.min.css">
    <script src="jquery-3.2.1.min.js" ></script>
</head>
<body class="container" >
<div class="form-group" ></br>
    <h6>聊天室</h6>
    <textarea id="message_content"  class="form-control"  readonly="readonly" cols="50" rows="10"></textarea>
</div>
<div class="form-group" >
    <label for="in_user_name">用戶姓名 &nbsp;</label>
    <input id="in_user_name" value="" class="form-control" /></br>
    <button id="user_join" class="btn btn-success" >加入聊天室</button>
    <button id="user_exit" class="btn btn-warning" >離開聊天室</button>
</div>
<div class="form-group" >
    <label for="in_room_msg" >群發(fā)消息 &nbsp;</label>
    <input id="in_room_msg" value="" class="form-control" /></br>
    <button id="user_send_all" class="btn btn-info" >發(fā)送消息</button>
</div>
</body>
</html>
  • 最上面使用 textarea 畫一個對話框,用來顯示聊天室的內(nèi)容;中間部分添加用戶加入聊天室和離開聊天室的按鈕,按鈕上面是輸入用戶名的入口;頁面最下面添加發(fā)送消息的入口,頁面顯示效果如下:

SpringBoot中怎么使用WebSocket創(chuàng)建一個聊天室

  • 接下來在頁面添加 WebSocket 通訊代碼:

<script type="text/javascript">
    $(document).ready(function(){
        var urlPrefix ='ws://localhost:8080/chat-room/';
        var ws = null;
        $('#user_join').click(function(){
            var username = $('#in_user_name').val();
            var url = urlPrefix + username;
            ws = new WebSocket(url);
            ws.onopen = function () {
                console.log("建立 websocket 連接...");
            };
            ws.onmessage = function(event){
                //服務(wù)端發(fā)送的消息
                $('#message_content').append(event.data+'\n');
            };
            ws.onclose = function(){
                $('#message_content').append('用戶['+username+'] 已經(jīng)離開聊天室!');
                console.log("關(guān)閉 websocket 連接...");
            }
        });
        //客戶端發(fā)送消息到服務(wù)器
        $('#user_send_all').click(function(){
            var msg = $('#in_room_msg').val();
            if(ws){
                ws.send(msg);
            }
        });
        // 退出聊天室
        $('#user_exit').click(function(){
            if(ws){
                ws.close();
            }
        });
    })
</script>
  • 這段代碼的功能主要是監(jiān)聽三個按鈕的點(diǎn)擊事件,當(dāng)用戶登錄、離開、發(fā)送消息是調(diào)用對應(yīng)的 WebSocket 事件,將信息傳送給服務(wù)端。同時打開頁面時創(chuàng)建了 WebSocket 對象,頁面會監(jiān)控 WebSocket 事件,如果后端服務(wù)和前端通訊室將對應(yīng)的信息展示在頁面。

2. 服務(wù)端開發(fā)

- 引入依賴
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

主要添加 Web 和 Websocket 組件。

- 啟動類

啟動類需要添加 @EnableWebSocket 開啟 WebSocket 功能。

@EnableWebSocket
@SpringBootApplication
public class WebSocketApplication {

    public static void main(String[] args) {
        SpringApplication.run(WebSocketApplication.class, args);
    }

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}
- 請求接收
  • 在創(chuàng)建服務(wù)端消息接收功能之前,我們先創(chuàng)建一個 WebSocketUtils 工具類,用來存儲聊天室在線的用戶信息,以及發(fā)送消息的功能。首先定義一個全局變量 ONLINE_USER_SESSIONS 用來存儲在線用戶,使用 ConcurrentHashMap 提升高并發(fā)時效率。

public static final Map<String, Session> ONLINE_USER_SESSIONS = new ConcurrentHashMap<>();
  • 封裝消息發(fā)送方法,在發(fā)送之前首先判單用戶是否存在再進(jìn)行發(fā)送:

public static void sendMessage(Session session, String message) {
    if (session == null) {
        return;
    }
    final RemoteEndpoint.Basic basic = session.getBasicRemote();
    if (basic == null) {
        return;
    }
    try {
        basic.sendText(message);
    } catch (IOException e) {
        logger.error("sendMessage IOException ",e);
    }
}

聊天室的消息是所有在線用戶可見,因此每次消息的觸發(fā)實際上是遍歷所有在線用戶,給每個在線用戶發(fā)送消息。

public static void sendMessageAll(String message) {
    ONLINE_USER_SESSIONS.forEach((sessionId, session) -> sendMessage(session, message));
}
  • 其中,ONLINE_USER_SESSIONS.forEach((sessionId, session) -> sendMessage(session, message)) 是 JDK 1.8 forEach 的簡潔寫法。

  • 這樣我們在創(chuàng)建 ChatRoomServerEndpoint 類的時候就可以直接將工具類的方法和全局變量導(dǎo)入:

import static com.neo.utils.WebSocketUtils.ONLINE_USER_SESSIONS;
import static com.neo.utils.WebSocketUtils.sendMessageAll;
  • 接收類上需要添加 @ServerEndpoint("url") 代表監(jiān)聽此地址的 WebSocket 信息。

@RestController
@ServerEndpoint("/chat-room/{username}")
public class ChatRoomServerEndpoint {
}
  • 用戶登錄聊天室時,將用戶信息添加到 ONLINE_USER_SESSIONS 中,同時通知聊天室中的人。其中,@OnOpen 注解和前端的 onopen 事件一致,表示用戶建立連接時觸發(fā)。

@OnOpen
public void openSession(@PathParam("username") String username, Session session) {
    ONLINE_USER_SESSIONS.put(username, session);
    String message = "歡迎用戶[" + username + "] 來到聊天室!";
    logger.info("用戶登錄:"+message);
    sendMessageAll(message);
}
  • 當(dāng)聊天室某個用戶發(fā)送消息時,將此消息同步給聊天室所有人。其中,@OnMessage 監(jiān)聽發(fā)送消息的事件。

@OnMessage
public void onMessage(@PathParam("username") String username, String message) {
    logger.info("發(fā)送消息:"+message);
    sendMessageAll("用戶[" + username + "] : " + message);
}
  • 當(dāng)用戶離開聊天室后,需要將用戶信息從 ONLINE_USER_SESSIONS 移除,并且通知到在線的其他用戶,其中,@OnClose 監(jiān)聽用戶斷開連接事件。

@OnClose
public void onClose(@PathParam("username") String username, Session session) {
    //當(dāng)前的Session 移除
    ONLINE_USER_SESSIONS.remove(username);
    //并且通知其他人當(dāng)前用戶已經(jīng)離開聊天室了
    sendMessageAll("用戶[" + username + "] 已經(jīng)離開聊天室了!");
    try {
        session.close();
    } catch (IOException e) {
        logger.error("onClose error",e);
    }
}
  • 當(dāng) WebSocket 連接出現(xiàn)異常時,出觸發(fā) @OnError 事件,可以在此方法內(nèi)記錄下錯誤的異常信息,并關(guān)閉用戶連接。

@OnError
public void onError(Session session, Throwable throwable) {
    try {
        session.close();
    } catch (IOException e) {
        logger.error("onError excepiton",e);
    }
    logger.info("Throwable msg "+throwable.getMessage());
}
  • 到此我們服務(wù)端內(nèi)容就開發(fā)完畢了。

- 測試
  • 啟動 spring-boot-websocket 項目,在瀏覽器中輸入地址 http://localhost:8080/ 打開兩個頁面進(jìn)行測試。

  • 在第一個頁面中以用戶“小王”登錄聊天室,第二個頁面以“小張”登錄聊天室。

小王:你是誰?
小張:你猜
小王:我猜好了!
小張:你猜的什么
小王:你猜?
小張:…

小張離開聊天室…
  • 大家在兩個頁面模式小王和小張對話,可以看到兩個頁面的展示效果,頁面都可實時無刷新展示最新聊天內(nèi)容,頁面最終展示效果如下:

SpringBoot中怎么使用WebSocket創(chuàng)建一個聊天室

看完上述內(nèi)容,你們對SpringBoot中怎么使用WebSocket創(chuàng)建一個聊天室有進(jìn)一步的了解嗎?如果還想了解更多知識或者相關(guān)內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI