天天看點

太頂了,使用 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知音 ,作者啊傑

繼續閱讀