太頂了,使用 Netty 實現了一個 IM 即時通訊系統

一、目錄介紹

功能梳理

具體實現

二、需求梳理

透過前面兩章內容的學習,我們基本學會了如何使用 Netty 建立一個長連線,接下來我們就在這個基礎上,實現一個單機版的 im 系統。

主要功能,我梳理了一下:

登入

維持連線、心跳檢測

聊天訊息

訊息ack

使用到的相關元件:

SpringBoot-job

GuavaCache

三、具體實現

本期的內容是基於,原理篇一的 dome 程式碼基礎上進行的,沒有看過的原理一的小夥伴,建議先回顧一下原理篇一。

原理篇一的程式碼結構:

Server(主程式)

ServerHandler(業務處理程式)

實戰篇一的程式碼結構:

太頂了,使用 Netty 實現了一個 IM 即時通訊系統

程式碼的層級結構如上所示,接下來,我們將會一個個模組對邏輯進行講解。

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

太頂了,使用 Netty 實現了一個 IM 即時通訊系統

訊息型別

3、後端對 token 進行校驗,校驗成功就記錄使用者登入資訊。

示例程式碼如下圖所示,

UserLoginProcessor。login

太頂了,使用 Netty 實現了一個 IM 即時通訊系統

登入業務邏輯

2)具體效果

主要的業務程式碼我們已經講解完畢了,接下來我們來看看效果:

太頂了,使用 Netty 實現了一個 IM 即時通訊系統

使用者登入

從上圖,我們可以看到,我們登入的兩個使用者都成功了,並且返回了對應的使用者資訊。

2、維持連線、心跳檢測

這個模組的功能,其實我們

在原理篇二的時候已經講過了具體的實現方案

了,這裡也不再過多的贅述了,我們直接來看具體實現方法吧。

1)維持連線

1、前端每10秒傳送一次心跳訊息,報文如下所示:

{ “type”: “40”, “fromId”: “2”}

注:前端傳送的每個訊息,理論上都是需要帶上使用者表示的,後端都是需要進行鑑權操作的。我們這裡為了方便講解(偷懶,bushi)將這部分邏輯進行了簡化,大家在具體實現的時候,記得一定要加上

鑑權邏輯

2、後端檢測,使用者是否還線上,如果線上,則重新整理使用者的最新線上時間,並回復 PONG 訊息。

示例程式碼如下圖所示,

HeartBeatProcessor。process

太頂了,使用 Netty 實現了一個 IM 即時通訊系統

維持連線1

太頂了,使用 Netty 實現了一個 IM 即時通訊系統

維持連線2

2)心跳檢測

這裡主要是基於

IdleStateEvent

事件實現的。

TextWebSocketFrameHandler

繼承

SimpleChannelInboundHandler

類,並實現

userEventTriggered

方法,具體程式碼如下所示:

太頂了,使用 Netty 實現了一個 IM 即時通訊系統

心跳檢測

這裡詳細說一下,三種事件的區別:

readerIdleTimeSeconds:

讀超時。即當在指定的時間間隔內沒有從

Channel

讀取到資料時,會觸發一個

READER_IDLE

IdleStateEvent

事件。

writerIdleTimeSeconds:

寫超時。即當在指定的時間間隔內沒有資料寫入到

Channel

時,會觸發一個

WRITER_IDLE

IdleStateEvent

事件。

allIdleTimeSeconds:

讀/寫超時。即當在指定的時間間隔內沒有讀且沒有寫操作時,會觸發一個

ALL_IDLE

IdleStateEvent

事件。

所以,我們這裡檢測 ALL_IDLE 事件即可。

3)具體效果

維持連線效果如下所示:

太頂了,使用 Netty 實現了一個 IM 即時通訊系統

維持連線效果

心跳檢測效果如下所示:

太頂了,使用 Netty 實現了一個 IM 即時通訊系統

心跳超時效果1

太頂了,使用 Netty 實現了一個 IM 即時通訊系統

心跳超時效果2

3、聊天訊息

聊天訊息模組主要分為兩部分:

訊息接收:客戶端推送訊息到服務端

訊息推送:服務端將訊息推送到指定的客戶端

這邊主要的難點在於,服務端將訊息推送到指定的客戶端,具體場景有2種情況:

訊息的傳送者和訊息的接受者,在同一臺伺服器上建立的 ws 連線,這種情況,就很好處理,直接在伺服器上找到建立的 ws 連線,然後將訊息推送給對應的客戶端。

訊息的傳送者和訊息的接受者,在不同的伺服器上建立的 ws 連線,這種情況就比較複雜,實現方案也很多,比較簡單的實現方式就是,傳送一條廣播訊息,讓對應的伺服器,將訊息推送到指定的客戶端。

本文由於是

單機版

的 im,所以只會有第一種情況發生,第二種情況就留給大家自由發揮了。

1)訊息接收

具體步驟如下所示:

1、客戶端傳送型別為80的報文,如下所示:

{ “type”: “80”, “fromId”: “1”, “toId”: “2”, “content”: { “contentType”: 1, “body”: “測試訊息” }}

2、服務端(ChatProcessor)對訊息進行處理,具體程式碼如下所示:

太頂了,使用 Netty 實現了一個 IM 即時通訊系統

訊息接收

2)訊息推送

具體步驟如下所示:

1、獲取訊息接受者所連線的伺服器 ip 地址 2、判斷當前伺服器 ip 地址是否和上面的 ip 地址相同,如果相同則推送訊息,否則轉發給目標伺服器

具體程式碼如下所示:

太頂了,使用 Netty 實現了一個 IM 即時通訊系統

訊息推送

3)具體效果

1、我們先登入兩個使用者,分別是張三、李四,如下圖所示:

太頂了,使用 Netty 實現了一個 IM 即時通訊系統

聊天登入

2、張三傳送訊息給李四,如下圖所示:

太頂了,使用 Netty 實現了一個 IM 即時通訊系統

張三傳送訊息給李四

3、李四傳送訊息給張三,如下圖所示:

太頂了,使用 Netty 實現了一個 IM 即時通訊系統

李四傳送訊息給張三

4、訊息 ack

因為網路環境異常或者其他異常狀況的傳送,可能會出現訊息推送失敗的情況,這時候就需要 訊息 ack 機制和重試,來保證我們的訊息可以推送成功。

1)訊息 ack 機制

具體步驟如下:

1、客戶端收到 80 型別的訊息,解析併發送 ack 報文,如下所示:

{ “type”: “90”, “msgId”: “2bfea133-72a8-4315-82aa-80049fe4fb7b”}

2、服務端收到 ack 訊息,變更訊息狀態(AckProcessor),具體程式碼如下圖所示:

太頂了,使用 Netty 實現了一個 IM 即時通訊系統

訊息ack

2)訊息重試

這裡因為是單機版 im,所以直接採用 SpringBoot-Job 實現,Job 程式碼如下所示:

太頂了,使用 Netty 實現了一個 IM 即時通訊系統

訊息重試

以上文章來源於Java知音 ,作者啊傑