一、目錄介紹
- 功能梳理
- 具體實現
二、需求梳理
通過前面兩章內容的學習,我們基本學會了如何使用.NETty 建立一個長連接,接下來我們就在這個基礎上,實現一個單機版的 im 系統。
主要功能,我梳理了一下:
- 登錄
- 維持連接、心跳檢測
- 聊天消息
- 消息ack
使用到的相關組件:
- SpringBoot-job
- GuavaCache
三、具體實現
本期的內容是基于,原理篇一的 dome 代碼基礎上進行的,沒有看過的原理一的小伙伴,建議先回顧一下原理篇一。
原理篇一的代碼結構:
- Server(主程序)
- ServerHandler(業務處理程序)
實戰篇一的代碼結構:
代碼的層級結構如上所示,接下來,我們將會一個個模塊對邏輯進行講解。
1、登錄
1)實現邏輯
不管是長連接還是短連接,鑒權這個動作都是要有的,我相信這個功能模塊,大家是很好理解的。我這里就不在過多的贅述了,具體實現步驟如下所示:
1、前后端建立 ws 連接
2、前端發送登錄類型的報文,如下所示:
{
"token": "2",
"type": "10"
}
token:這里的 token,就是用戶登錄標識,大家可以根據自己所依賴的業務系統,進行修改。
type:這里表示消息報文的類型,本文所有類型定義如下所示:
- USER_LOGIN(10, "用戶上線")
- USER_LOGIN_RESP(11, "用戶上線響應")
- HEARTBEAT_TIMEOUT(30, "心跳超時")
- PING(40, "心跳")
- PONG(41, "心跳響應")
- CHAT(80, "聊天"),
- CHAT_RESP(81, "聊天響應")
- ACK(90, "確認")
- ACK_RESP(91, "確認響應")
- UNKNOWN(0, "未知類型")
示例代碼如下圖所示,WsMsgDispatcher.dispatch
消息類型
3、后端對 token 進行校驗,校驗成功就記錄用戶登錄信息。
示例代碼如下圖所示,UserLoginProcessor.login
登錄業務邏輯
2)具體效果
主要的業務代碼我們已經講解完畢了,接下來我們來看看效果:
用戶登錄
從上圖,我們可以看到,我們登錄的兩個用戶都成功了,并且返回了對應的用戶信息。
2、維持連接、心跳檢測
這個模塊的功能,其實我們在原理篇二的時候已經講過了具體的實現方案了,這里也不再過多的贅述了,我們直接來看具體實現方法吧。
1)維持連接
1、前端每10秒發送一次心跳消息,報文如下所示:
{
"type": "40",
"fromId": "2"
}
注:前端發送的每個消息,理論上都是需要帶上用戶表示的,后端都是需要進行鑒權操作的。我們這里為了方便講解(偷懶,bushi)將這部分邏輯進行了簡化,大家在具體實現的時候,記得一定要加上 鑒權邏輯。
2、后端檢測,用戶是否還在線,如果在線,則刷新用戶的最新在線時間,并回復 PONG 消息。
示例代碼如下圖所示,HeartBeatProcessor.process
維持連接1
維持連接2
2)心跳檢測
這里主要是基于 IdleStateEvent 事件實現的。
TextWebSocketFrameHandler 繼承 SimpleChannelInboundHandler 類,并實現 userEventTriggered 方法,具體代碼如下所示:
心跳檢測
這里詳細說一下,三種事件的區別:
- readerIdleTimeSeconds:讀超時。即當在指定的時間間隔內沒有從 Channel 讀取到數據時,會觸發一個 READER_IDLE 的 IdleStateEvent 事件。
- writerIdleTimeSeconds: 寫超時。即當在指定的時間間隔內沒有數據寫入到 Channel 時,會觸發一個 WRITER_IDLE 的 IdleStateEvent 事件。
- allIdleTimeSeconds: 讀/寫超時。即當在指定的時間間隔內沒有讀且沒有寫操作時,會觸發一個 ALL_IDLE 的 IdleStateEvent 事件。
所以,我們這里檢測 ALL_IDLE 事件即可。
3)具體效果
維持連接效果如下所示:
維持連接效果
心跳檢測效果如下所示:
心跳超時效果1
心跳超時效果2
3、聊天消息
聊天消息模塊主要分為兩部分:
- 消息接收:客戶端推送消息到服務端
- 消息推送:服務端將消息推送到指定的客戶端
這邊主要的難點在于,服務端將消息推送到指定的客戶端,具體場景有2種情況:
- 消息的發送者和消息的接受者,在同一臺服務器上建立的 ws 連接,這種情況,就很好處理,直接在服務器上找到建立的 ws 連接,然后將消息推送給對應的客戶端。
- 消息的發送者和消息的接受者,在不同的服務器上建立的 ws 連接,這種情況就比較復雜,實現方案也很多,比較簡單的實現方式就是,發送一條廣播消息,讓對應的服務器,將消息推送到指定的客戶端。
本文由于是 單機版 的 im,所以只會有第一種情況發生,第二種情況就留給大家自由發揮了。
1)消息接收
具體步驟如下所示:
1、客戶端發送類型為80的報文,如下所示:
{
"type": "80",
"fromId": "1",
"toId": "2",
"content": {
"contentType": 1,
"body": "測試消息"
}
}
2、服務端(ChatProcessor)對消息進行處理,具體代碼如下所示:
消息接收
2)消息推送
具體步驟如下所示:
1、獲取消息接受者所連接的服務器 ip 地址 2、判斷當前服務器 ip 地址是否和上面的 ip 地址相同,如果相同則推送消息,否則轉發給目標服務器
具體代碼如下所示:
消息推送
3)具體效果
1、我們先登錄兩個用戶,分別是張三、李四,如下圖所示:
聊天登錄
2、張三發送消息給李四,如下圖所示:
張三發送消息給李四
3、李四發送消息給張三,如下圖所示:
李四發送消息給張三
4、消息 ack
因為網絡環境異常或者其他異常狀況的發送,可能會出現消息推送失敗的情況,這時候就需要 消息 ack 機制和重試,來保證我們的消息可以推送成功。
1)消息 ack 機制
具體步驟如下:
1、客戶端收到 80 類型的消息,解析并發送 ack 報文,如下所示:
{
"type": "90",
"msgId": "2bfea133-72a8-4315-82aa-80049fe4fb7b"
}
2、服務端收到 ack 消息,變更消息狀態(AckProcessor),具體代碼如下圖所示:
消息ack
2)消息重試
這里因為是單機版 im,所以直接采用 SpringBoot-Job 實現,Job 代碼如下所示:
消息重試
以上文章來源于JAVA知音 ,作者啊杰






