[開源] .NETCore websocket 即時通訊組件—ImCore

  • 2019 年 10 月 3 日
  • 筆記

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),偽代碼:

ajax('/prev-connect-imserver', function(data) {      var url = data; //此時的值:ws://127.0.0.1:6001/ws?token=xxxxx      var sock = new WebSocket(url);      sock.onmessage = function (e) {          //...      };  })

Demo

運行環境:.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 發送群消息

image

設計思路

終端(如瀏覽器) 使用 webSocket 連接 imServer;

imServer 根據 clientId 分區管理 webSocket 連接,可群集部署;

webApi 或其他應用端,使用 ImHelper 調用相關方法(如:SendMessage、群聊相關方法),將數據推至 Redis Channel;

imServer 訂閱 Redis Channel,收到消息後向終端(如瀏覽器)推送消息;

1、可緩解並發推送消息過多的問題;

2、可解決連接數過多的問題;

3、解決業務和通訊分離,結構更加清淅;

imServer 充當消息轉發,維護連接,代碼萬年不變不需要重啟維護

webApi 負責所有業務

webSocket

如果瀏覽器使用 webSocket ,iOS 使用其他協議,協議不一致的後果很嚴重(難維護)。

建議所有端都使用 webSocket 協議,adorid/ios/h5/小程序 全部支持 webSocket 客戶端。

業務通訊

IM 系統一般涉及【我的好友】、【我的群】、【歷史消息】等等。。

那麼,imServer與業務方(webApi)該保持何種關係呢?

用戶A向好友B發送消息,分析一下:

  • 需要判斷B是否為A好友;
  • 需要判斷A是否有權限;
  • 等等。。

諸如此類業務判斷會很複雜,如果使用imServer做業務協議,它是不是會變成巨無霸難以維護?

又如獲取歷史聊天記錄,難道客戶端要先webSocket.send(‘gethistory’),再在onmessage里定位回調處理?

發送消息

業務和推送分離的設計,即 imServer 只負責推送工作,webApi 負責業務。

用戶A向B發消息:終端A ajax -> webApi -> imServer -> 終端B webSocket.onmessage;

獲取歷史消息:客戶端請求業務方(webApi)接口,返回json(歷史消息)。

背後採用 redis 輕量級的訂閱發佈功能,實現消息緩衝發送,方案必備之一,後期可更換為其他技術。比如 webApi 業務發需要通知1000個人,若不用消息緩衝,會對 webApi 應用程序整體將造成性能損耗。

還有使用 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 方法進行訂閱捕捉。

image

A向B發文件的例子

1、A向 webapi 傳文件

2、webapi 告訴 imServer,A向B正在傳文件,ImHelper.SendMessage(B, "A正在給傳送文件…")

3、B收到消息,A正在傳文件

4、webapi 文件接收完成時告訴imServer,A向B文件傳輸完畢,ImHelper.SendMessage(B, "A文件傳輸完畢(含文件鏈接)")

5、B收到消息,A文件傳輸完畢(含文件鏈接)

有感而發

為什麼說 signalr 不合適做 im?

im 的特點必定是長連接,輪訓的功能用不上。

因為他是雙工通訊的設計,用 hub.invoke 發送命令給服務端處理業務,其他就和 ajax 差不多,用來代替 ajax 減少 http 請求數量比較看好。

但是過多使用 hub,signalr 服務端會被業務入侵嚴重,業務變化頻繁後不得不重新發佈版本,每次部署所有終端都會斷開連接,遇到5分鐘發一次業務補丁的時候,類似離線和上線提示好友的功能就無法實現。

ImCore 的設計是業務和推送分離,即 imServer 永不更新重啟,業務全部在 webApi 上編寫,終端連接的是 imServer 就不會頻繁重啟的問題。