SpringBoot 使用WebSocket打造在線聊天室(基于注解)

推薦WebSocket的三大理由:

  • 1邪乍、采用全雙工通信降狠,擺脫傳統(tǒng)HTTP輪詢的窘境。
  • 2庇楞、采用W3C國際標準榜配,完美支持HTML5。
  • 3吕晌、簡單高效蛋褥,容易上手。

學習目標

快速學會通過WebSocket編寫簡單聊天功能睛驳。

快速查閱

專題閱讀:《SpringBoot 布道系列》

源碼下載:SpringBoot-WebSocket-Chat

溫馨提示:
1烙心、WebSocket是HTML5開始提供的一種在單個 TCP 連接上進行全雙工通訊的協(xié)議。在WebSocket API中乏沸,瀏覽器和服務(wù)器只需要做一個握手的動作淫茵,然后,瀏覽器和服務(wù)器之間就形成了一條快速通道蹬跃。兩者之間就直接可以數(shù)據(jù)互相傳送匙瘪。
2、瀏覽器通過 JavaScript 向服務(wù)器發(fā)出建立 WebSocket 連接的請求,連接建立以后丹喻,客戶端和服務(wù)器端就可以通過 TCP 連接直接交換數(shù)據(jù)薄货。
3、當你獲取 Web Socket 連接后驻啤,你可以通過 send() 方法來向服務(wù)器發(fā)送數(shù)據(jù)菲驴,并通過 onmessage 事件來接收服務(wù)器返回的數(shù)據(jù)。

使用教程

一骑冗、打造 WebSocket 聊天客戶端

溫馨提示:得益于W3C國際標準的實現(xiàn)赊瞬,我們在瀏覽器JS就能直接創(chuàng)建WebSocket對象,再通過簡單的回調(diào)函數(shù)就能完成WebSocket客戶端的編寫贼涩,非常簡單愚争!接下來讓我們一探究竟焦匈。

使用說明:

使用步驟:1禁灼、獲取WebSocket客戶端對象碉考。

例如: var webSocket = new WebSocket(url);

使用步驟:2、獲取WebSocket回調(diào)函數(shù)袒哥。

例如:webSocket.onmessage = function (event) {console.log('WebSocket收到消息:' + event.data);

事件類型 WebSocket回調(diào)函數(shù) 事件描述
open webSocket.onopen 當打開連接后觸發(fā)
message webSocket.onmessage 當客戶端接收服務(wù)端數(shù)據(jù)時觸發(fā)
error webSocket.onerror 當通信異常時觸發(fā)
close webSocket.onclose 當連接關(guān)閉時觸發(fā)

使用步驟:3缩筛、發(fā)送消息給服務(wù)端

例如:webSokcet.send(jsonStr) 結(jié)合實際場景 本案例采用JSON字符串進行消息通信。

具體實現(xiàn):

下面是本案例在線聊天的客戶端實現(xiàn)的JS代碼堡称,附帶詳細注釋瞎抛。

<script>

    /**
     * WebSocket客戶端
     *
     * 使用說明:
     * 1、WebSocket客戶端通過回調(diào)函數(shù)來接收服務(wù)端消息却紧。例如:webSocket.onmessage
     * 2桐臊、WebSocket客戶端通過send方法來發(fā)送消息給服務(wù)端。例如:webSocket.send();
     */
    function getWebSocket() {
        /**
         * WebSocket客戶端 PS:URL開頭表示W(wǎng)ebSocket協(xié)議 中間是域名端口 結(jié)尾是服務(wù)端映射地址
         */
        var webSocket = new WebSocket('ws://localhost:8080/chat');
        /**
         * 當服務(wù)端打開連接
         */
        webSocket.onopen = function (event) {
            console.log('WebSocket打開連接');
        };

        /**
         * 當服務(wù)端發(fā)來消息:1.廣播消息 2.更新在線人數(shù)
         */
        webSocket.onmessage = function (event) {
            console.log('WebSocket收到消息:%c' + event.data, 'color:green');
            //獲取服務(wù)端消息
            var message = JSON.parse(event.data) || {};
            var $messageContainer = $('.message-container');
            //喉嚨發(fā)炎
            if (message.type === 'SPEAK') {
                $messageContainer.append(
                    '<div class="mdui-card" style="margin: 10px 0;">' +
                    '<div class="mdui-card-primary">' +
                    '<div class="mdui-card-content message-content">' + message.username + ":" + message.msg + '</div>' +
                    '</div></div>');
            }
            $('.chat-num').text(message.onlineCount);
            //防止刷屏
            var $cards = $messageContainer.children('.mdui-card:visible').toArray();
            if ($cards.length > 5) {
                $cards.forEach(function (item, index) {
                    index < $cards.length - 5 && $(item).slideUp('fast');
                });
            }
        };

        /**
         * 關(guān)閉連接
         */
        webSocket.onclose = function (event) {
            console.log('WebSocket關(guān)閉連接');
        };

        /**
         * 通信失敗
         */
        webSocket.onerror = function (event) {
            console.log('WebSocket發(fā)生異常');

        };
        return webSocket;
    }

    var webSocket = getWebSocket();


    /**
     * 通過WebSocket對象發(fā)送消息給服務(wù)端
     */
    function sendMsgToServer() {
        var $message = $('#msg');
        if ($message.val()) {
            webSocket.send(JSON.stringify({username: $('#username').text(), msg: $message.val()}));
            $message.val(null);
        }

    }
    /**
     * 清屏
     */
    function clearMsg(){
      $(".message-container").empty();
    }

    /**
     * 使用ENTER發(fā)送消息
     */
    document.onkeydown = function (event) {
        var e = event || window.event || arguments.callee.caller.arguments[0];
        e.keyCode === 13 && sendMsgToServer();
    };
</script>

========================================================================

二晓殊、打造 WebSocket 聊天服務(wù)端

溫馨提示:得益于SpringBoot提供的自動配置断凶,我們只需要通過簡單注解@ServerEndpoint就就能創(chuàng)建WebSocket服務(wù)端,再通過簡單的回調(diào)函數(shù)就能完成WebSocket服務(wù)端的編寫巫俺,比起客戶端的使用同樣非常簡單认烁!

使用說明:

首先在POM文件引入spring-boot-starter-websocket 、thymeleaf 识藤、FastJson等依賴砚著。

使用步驟:1、開啟WebSocket服務(wù)端的自動注冊痴昧。

這里需要特別提醒:ServerEndpointExporter 是由Spring官方提供的標準實現(xiàn),用于掃描ServerEndpointConfig配置類和@ServerEndpoint注解實例冠王。使用規(guī)則也很簡單:1.如果使用默認的嵌入式容器 比如Tomcat 則必須手工在上下文提供ServerEndpointExporter赶撰。2. 如果使用外部容器部署war包,則不要提供提供ServerEndpointExporter,因為此時SpringBoot默認將掃描服務(wù)端的行為交給外部容器處理豪娜。

@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {

        return new ServerEndpointExporter();
    }
}

使用步驟:2餐胀、創(chuàng)建WebSocket服務(wù)端。

核心思路:

  • ① 通過注解@ServerEndpoint來聲明實例化WebSocket服務(wù)端瘤载。
  • ② 通過注解@OnOpen否灾、@OnMessage、@OnClose鸣奔、@OnError 來聲明回調(diào)函數(shù)墨技。
事件類型 WebSocket服務(wù)端注解 事件描述
open @OnOpen 當打開連接后觸發(fā)
message @OnMessage 當接收客戶端信息時觸發(fā)
error @OnError 當通信異常時觸發(fā)
close @OnClose 當連接關(guān)閉時觸發(fā)
  • ③ 通過ConcurrentHashMap保存全部在線會話對象。
@Component
@ServerEndpoint("/chat")//標記此類為服務(wù)端
public class WebSocketChatServer {

    /**
     * 全部在線會話  PS: 基于場景考慮 這里使用線程安全的Map存儲會話對象挎狸。
     */
    private static Map<String, Session> onlineSessions = new ConcurrentHashMap<>();


    /**
     * 當客戶端打開連接:1.添加會話對象 2.更新在線人數(shù)
     */
    @OnOpen
    public void onOpen(Session session) {
        onlineSessions.put(session.getId(), session);
        sendMessageToAll(Message.jsonStr(Message.ENTER, "", "", onlineSessions.size()));
    }

    /**
     * 當客戶端發(fā)送消息:1.獲取它的用戶名和消息 2.發(fā)送消息給所有人
     * <p>
     * PS: 這里約定傳遞的消息為JSON字符串 方便傳遞更多參數(shù)扣汪!
     */
    @OnMessage
    public void onMessage(Session session, String jsonStr) {
        Message message = JSON.parseObject(jsonStr, Message.class);
        sendMessageToAll(Message.jsonStr(Message.SPEAK, message.getUsername(), message.getMsg(), onlineSessions.size()));
    }

    /**
     * 當關(guān)閉連接:1.移除會話對象 2.更新在線人數(shù)
     */
    @OnClose
    public void onClose(Session session) {
        onlineSessions.remove(session.getId());
        sendMessageToAll(Message.jsonStr(Message.QUIT, "", "下線了!", onlineSessions.size()));
    }

    /**
     * 當通信發(fā)生異常:打印錯誤日志
     */
    @OnError
    public void onError(Session session, Throwable error) {
        error.printStackTrace();
    }

    /**
     * 公共方法:發(fā)送信息給所有人
     */
    private static void sendMessageToAll(String msg) {
        onlineSessions.forEach((id, session) -> {
            try {
                session.getBasicRemote().sendText(msg);
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }

}
  • ④ 通過會話對象 javax.websocket.Session 來發(fā)消息給客戶端锨匆。
/**
 * WebSocket 聊天消息類
 */
package com.hehe.chat;

import com.alibaba.fastjson.JSON;

/**
 * WebSocket 聊天消息類
 */
public class Message {

    public static final String ENTER = "ENTER";
    public static final String SPEAK = "SPEAK";
    public static final String QUIT = "QUIT";

    private String type;//消息類型

    private String username; //發(fā)送人

    private String msg; //發(fā)送消息

    private int onlineCount; //在線用戶數(shù)

    public static String jsonStr(String type, String username, String msg, int onlineTotal) {
        return JSON.toJSONString(new Message(type, username, msg, onlineTotal));
    }

    public Message(String type, String username, String msg, int onlineCount) {
        this.type = type;
        this.username = username;
        this.msg = msg;
        this.onlineCount = onlineCount;
    }

    //這里省略get/set方法 請自行補充
}

三崭别、WebSocket在線聊天案例的視頻演示

1、源碼下載

至此恐锣,我們完成了客戶端和服務(wù)端的編碼茅主,由于篇幅有限,本教程的頁面代碼并未完整貼上土榴,想要完整的體驗效果請在Github下載源碼诀姚。傳送門:springboot-websocket-chat

2、視頻演示

上面一頓操作猛如虎鞭衩,實際到底是啥樣子呢学搜,接下來由哈士奇童鞋為我們演示最終版的在線聊天案例:


ws-gif.gif

四、全文總結(jié)

1论衍、使用WebSocket用于實時雙向通訊的場景瑞佩,常見的如聊天室、跨系統(tǒng)消息推送等坯台。

2炬丸、創(chuàng)建WebSocket客戶端使用JS內(nèi)置對象+回調(diào)函數(shù)+send方法發(fā)送消息。

3蜒蕾、創(chuàng)建WebSocket服務(wù)端使用注解聲明實例+使用注解聲明回調(diào)方法+使用Session發(fā)送消息稠炬。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市咪啡,隨后出現(xiàn)的幾起案子首启,更是在濱河造成了極大的恐慌,老刑警劉巖撤摸,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件毅桃,死亡現(xiàn)場離奇詭異褒纲,居然都是意外死亡,警方通過查閱死者的電腦和手機钥飞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門莺掠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人读宙,你說我怎么就攤上這事彻秆。” “怎么了结闸?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵唇兑,是天一觀的道長。 經(jīng)常有香客問我膀估,道長幔亥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任察纯,我火速辦了婚禮帕棉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘饼记。我一直安慰自己香伴,他們只是感情好,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布具则。 她就那樣靜靜地躺著即纲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪博肋。 梳的紋絲不亂的頭發(fā)上低斋,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天,我揣著相機與錄音匪凡,去河邊找鬼膊畴。 笑死,一個胖子當著我的面吹牛病游,可吹牛的內(nèi)容都是我干的唇跨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼衬衬,長吁一口氣:“原來是場噩夢啊……” “哼买猖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起滋尉,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤玉控,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后狮惜,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體奸远,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡既棺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年讽挟,在試婚紗的時候發(fā)現(xiàn)自己被綠了懒叛。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡耽梅,死狀恐怖薛窥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情眼姐,我是刑警寧澤诅迷,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站众旗,受9級特大地震影響罢杉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜贡歧,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一滩租、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧利朵,春花似錦律想、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至樟遣,卻和暖如春而叼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背豹悬。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工葵陵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人屿衅。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓埃难,卻偏偏與公主長得像,于是被迫代替她去往敵國和親涤久。 傳聞我的和親對象是個殘疾皇子涡尘,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容