IM 聊天系統設計
1. 前言
經過前面章節的學習,Netty 的知識點基本上講解完了,從本節開始,主要通過一個 IM 聊天系統來把之前的知識點串聯一遍,加深同學們對于 Netty 的理解。
2. 需求分析
業務場景: 模擬微信聊天,每個客戶端和服務端建立連接,并且可以實現點對點通信(單聊),點對多點通信(群聊)。
設計思路: 我們要實現的是點(客戶端)對點(客戶端)的通訊,但是我們大部分情況下接觸的業務都是客戶端和服務端之間的通訊,客戶端只需要知道服務端的 IP 地址和端口號即可發起通訊了,那么客戶端和客戶端應該怎么去設計呢?
思考:難道是手機和手機之間建立通訊連接,互相發送消息嗎?
這種方案顯然不是很好的方案,第一: 客戶端和客戶端之間通訊,首先需要確定對方的 IP 地址和端口號,顯然不是很現實。第二: 即使有辦法拿到對方的 IP 地址和端口號,那么每個點(客戶端)既作為服務端還得作為客戶端,無形之中增加了客戶端的壓力。
其實,我們可以使用服務端作為中轉站,由服務端主動往指定客戶端推送消息,如果是這種模式的話,那么 Http 協議是無法支持的,Http 是無狀態的,只能一請求一響應的模式,只能使用 TCP 協議去實現了。
3. 單聊思路設計
3.1 架構圖
流程解析
- 實現客戶端和客戶端之間通訊,那么需要使用服務端作為通訊的中轉站,每個客戶端都必須和服務端建立連接;
- 每個客戶端和服務端建立連接之后,服務端保存用戶 ID 和通道的映射關系,其中用戶 ID 作為客戶端的唯一標識;
- 客戶端 A 往客戶端 B 發送消息時,先把消息發送到服務端,再有服務端往客戶端 B 進行推送。但是,服務端如何找到客戶端 B 呢?
3.1 客戶端 A 往服務端發送消息時,消息攜帶的信息有:客戶端 A 用戶 ID、客戶端 B 用戶 ID、消息內容。這樣服務端就能順利找到服務端 B 的通道并且進行推送消息了。
3.2 消息推送流程
每個客戶端和服務端建立連接的時候,必須把個人用戶信息上傳到服務端,由服務端統一保存映射關系,如果某個客戶端下線了,則服務端監聽到連接斷開,刪除對應的映射關系。其次,發起群聊的時候,需要傳遞 touser 字段,服務端根據該字段在映射表里面查找到對應的連接通道并發起消息推送。
4. 群聊思路設計
群聊指的是一個組內多個用戶之間的聊天,一個用戶發到群組的消息會被組內任何一個成員接收 。具體架構思路如下所示:
群聊流程解析:
- 群聊其實和單聊整體上思路都是一致的,都是需要保存每個用戶和通道的對應關系,方便后期通過用戶 ID 去查找到對應的通道,再跟進通道推送消息;
- 那么如何把消息發送給多個組內的成員呢?其實很簡單,服務端再保存另外一份映射關系,那就是聊天室和成員的映射關系。發送消息時,首先根據聊天室 ID 找到對應的所有成員,然后再跟進各個成員的 ID 去查找到對應的通道,最后由每個通道進行消息的發送;
- 成員加入某個群聊組的時候,往映射表新增一條記錄,如果成員退群的時候則刪除對應的映射記錄;
- 通過上面的架構圖發現,群聊和單聊相比,其實就是多了一份映射關系而已。
5. 小結
本節主要掌握單聊和群聊的核心設計思路
單聊: 主要是服務器保存了一份用戶和通道之間的映射關系,發送消息的時候,根據接收人 ID 找到其對應的通道 Channel,Channel 的 write () 可以給客戶端發送消息;
群聊: 保存兩份關系,分別是用戶 ID 和 Channel 之間的關系、群組 ID 和用戶 ID 的關系。推送消息的時候,首先根據聊天組 ID 找到其對應的成員,遍歷每個成員再進行找出其對應的通道即可。
整體來說,思路還是很簡單的,掌握了該設計思路以后,你會發現設計一款 IM 聊天軟件其實也不是很復雜。
特殊說明:我們的實戰案例,主要是帶大家理解 IM 聊天的設計思路和大體的實現思路,因此后面代碼部分主要是基于控制臺去實現,而不是真正的手機端去實現。