有感而發
為什麼說 SignalR 不合适做 IM?
IM 的特點必定是長連接配接,輪訓的功能用不上。
因為它是雙工通訊的設計,用hub.invoke發送指令給服務端處理業務,其他就和 ajax 差不多,用來代替 ajax 減少 http 請求數量比較看好。
但是過多使用 hub,SignalR 服務端會被業務入侵嚴重,業務變化頻繁後不得不重新釋出版本,每次部署所有終端都會斷開連接配接,遇到5分鐘發一次業務更新檔的時候,類似離線和上線提示好友的功能就無法實作。
ImCore 的設計是業務和推送分離,即 ImServer 永不更新重新開機,業務全部在 webApi 上編寫,終端連接配接的 ImServer 就不會頻繁重新開機的問題。
為什麼說又呢?
熟悉葉先森的朋友應該都知道,他之前開源了兩款比較熱門的元件,如:
CSRedisCore
: 操作Redis的簡單易用的元件。GitHub:https://github.com/2881099/csredis 目前也已經八百多星了,nuget下載下傳量也已經121k了.相關介紹及使用可以參考《.NET Core開發者的福音之玩轉Redis的又一傻瓜式神器推薦》
FreeSql
:FreeSql 是一個功能強大的對象關系映射程式(O/RM),支援 .NETCore 2.1+ 或 .NETFramework 4.5+。這個可以說是比EFCore更好用的一個ORM元件了。這個元件目前也已經有八百多星了,nuget下載下傳量也已經13.9k了。GitHub位址:https://github.com/2881099/FreeSql 。據說用了這個元件後頭發都茂密了起來。
而且上面兩個元件我也一直在用,感覺很友善。有興趣的小夥伴可以.net core兩千人群交流讨論:637326624
ImCore是什麼
ImCore是一個利用 WebSocket 實作簡易、高性能、叢集化部署的即時通訊元件,支援點對點通訊、群聊通訊、上線下線事件消息等衆多實用性功能。
Quick Start
dotnet add package ImCore
IM服務端
public void Configure(IApplicationBuilder app)
{
app.UseImServer(new ImServerOptions
{
Redis = new CSRedis.CSRedisClient("127.0.0.1:6379,poolsize=5"),
Servers = new[] { "127.0.0.1:6001" }, //叢集配置
Server = "127.0.0.1:6001"
});
}
一套永遠不需要疊代更新的IM服務端
WebApi業務端
public void Configure(IApplicationBuilder app)
{
//...
ImHelper.Initialization(new ImClientOptions
{
Redis = new CSRedis.CSRedisClient("127.0.0.1:6379,poolsize=5"),
Servers = new[] { "127.0.0.1:6001" }
});
ImHelper.EventBus(
t => Console.WriteLine(t.clientId + "上線了"),
t => Console.WriteLine(t.clientId + "下線了"));
}
ImHelper方法 | 參數 | 描述 |
---|---|---|
PrevConnectServer | (clientId, string) | 在終端準備連接配接 WebSocket 前調用 |
SendMessage | (發送者, 接收者, 消息内容, 是否回執) | 發送消息 |
GetClientListByOnline | - | 傳回所有線上clientId |
EventBus | (上線委托, 離線委托) | socket上線與下線事件 |
群聊頻道 | 參數 | 描述 |
---|---|---|
JoinChan | (clientId, 頻道名) | 加入 |
LeaveChan | (clientId, 頻道名) | 離開 |
GetChanClientList | (頻道名) | 擷取群聊頻道所有clientId |
GetChanList | - | 擷取所有群聊頻道和線上人數 |
GetChanListByClientId | (clientId) | 擷取使用者參與的所有群聊頻道 |
GetChanOnline | (頻道名) | 擷取群聊頻道的線上人數 |
SendChanMessage | (clientId, 頻道名, 消息内容) | 發送群聊消息,所有線上的使用者将收到消息 |
說明:clientId 應該與 webApi的使用者id相同,或者有關聯。
Html5終端
本方案支援叢集分區,前端連接配接 websocket 前,應該先請求 webApi 獲得位址(ImHelper.PrevConnectServer)。
運作示例
運作環境:.NETCore 2.1 + redis-server 2.8
下載下傳Redis-x64-2.8.2402.zip,點選 start.bat 運作;
cd imServer && dotnet run
cd web && dotnet run
打開多個浏覽器,通路 http://127.0.0.1:5000 發送群消息
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISM9AnYldnJwAzN9c3Pn5GcuQ0MlQ0MlcnW1JkbMhXTE1EMVpmT0MGVOhHMD1EMVpWTwkFROVTQU10dnRUT1UERNlHMD1UMJpnTz0EVNZ3ZE1UNFRUT5hzQNFTS650MNRVT2NmMiNnSywEd5ITW110MaZHetlVdO1GT0UERNl3YXJGc5kHT20ESjBjUIF2Lc12bj5SYphXa5VWen5WY35iclN3Ztl2Lc9CX6MHc0RHaiojIsJye.png)
設計思路
ImServer 是 websocket 服務中心,可部署多執行個體,按clientId分區管理socket連接配接;
webApi 或其他應用端,使用 ImHelper 調用相關方法(如:SendMessage、群聊相關方法);
消息發送利用了 redis 訂閱釋出技術。每個 imServer 訂閱相應的頻道,收到消息,指派 websocket 向終端(如浏覽器)發送消息;
1、可緩解并發推送消息過多的問題;
2、可解決連接配接數過多的問題;
用戶端連接配接流程:client -> websocket -> imserver
imserver 訂閱消息:client <- imserver <- redis channel
推送消息流程:web1 -> sendmsg方法 -> redis channel -> imserver
imserver 充當消息轉發,及維護連接配接中心,代碼萬年不變不需要重新開機維護;
WebSocket
比較笨的辦法是浏覽器端使用websocket,其他端socket,這種混亂的設計非常難維護。
強烈建議所有端都使用websocket協定,websocket協定支援幾乎所有端,adorid/ios/h5/小程式全部支援websocket用戶端。
websocket用了後,就像跨平台。。。雖然選一種語言都能連接配接通訊。
業務與通訊協定
im系統一般涉及【我的好友】、【我的群】、【曆史消息】等等。。
那麼,imServer與業務方(webApi)該保持何種關系呢?
使用者A向好友B發送消息,分析一下:
- 需要判斷B是否為A好友;
- 需要判斷A是否有權限;
- 等等。。
諸如此類業務判斷會很複雜,我們試想一下,如果使用imServer做業務協定,它是不是會變成巨無霸難以維護?
又比如擷取曆史聊天記錄,難道用戶端要先websocket.send('gethistory'),再在onmessage裡定位回調處理?
我們可以這樣設定,所有使用者的主動行為走業務方(webApi),imServer隻負責即時消息推送。什麼意思?
使用者A向好友B發送消息:用戶端請求業務方(webApi)接口,由業務方(webApi)後端向imServer發起推送請求,imServer收到指令後,向前端使用者B的websocket發送資料,使用者B收到了消息。
擷取曆史消息:用戶端請求業務方(webApi)接口,傳回json(曆史消息)
回執:使用者A如何知道消息發送狀态(成功或失敗或不線上)?imServer端向使用者B發送消息時,把狀态以消息的方式推給使用者A即可(按上面的邏輯),具體請看源碼吧。。。
發送消息
業務和推送分離的設計,即 imServer 隻負責推送工作。
使用者A向B發消息:終端A ajax -> webApi -> imServer -> 終端B WebSocket.onmessage;
采用 redis 輕量級的訂閱釋出功能,實作消息緩沖發送。
redis 緩沖消息發送,避免對業務方(webApi)造成消息發送的性能損耗,屬于必備,也可以更換為其他技術。
比如 webapi 業務發需要通知1000個人,不用消息緩沖的話。。。
還有使用redis存儲一些資料,如線上clientId,群聊結構。
叢集分區
單個imServer執行個體支援多少個用戶端連接配接,兩千個沒問題?
如果線上使用者有10萬人,怎麼辦???
比如部署4個imServer:
imServer1 訂閱 redisChanne1
imServer2 訂閱 redisChanne2
imServer3 訂閱 redisChanne3
imServer4 訂閱 redisChanne4
業務方(webApi)端根據接收方的clientId後四位16進制與節點總數取模,定位到對應的redisChannel,進行redis->publish操作将消息定位到相應的imServer。
每個 imServer 管理着對應的終端連接配接,當接收到 redis 訂閱消息後,向對應的終端連接配接推送資料。
事件消息
IM 系統比較常用的有上線、下線,在 imServer 層才能準确捕捉事件,但業務代碼就不合适在這上面編寫了。
采用 redis 釋出訂閱技術,将上線、下線等事件向指定頻道釋出,業務方(webApi) 通過 ImHelper.EventBus 方法進行訂閱捕捉。
A向B發檔案的例子
1、A向webapi傳檔案
2、webapi告訴imServer,A向B正在傳檔案
3、B收到消息,A正在傳檔案
4、webapi檔案接收完成時告訴imServer,A向B檔案傳輸完畢
5、B收到消息,A檔案傳輸完成(含檔案連結)