FreeIM 使用 websocket 協定實作簡易、高性能(單機支援5萬+連接配接)、叢集即時通訊元件,支援點對點通訊、群聊通訊、上線下線事件消息等衆多實用性功能。使用場景:好友聊天、群聊天、直播間、實時評論區、遊戲。FreeIM 解耦了通訊與業務子產品,讓項目架構變得更加簡單易維護。解決了業務與通訊的職責沖突,簡化了架構,降低了維護成本。經曆 1年半的生産環境,整理代碼于 2018 年開源。
💻 FreeIM 是什麼?
FreeIM 使用 websocket 協定實作簡易、高性能(單機支援5萬+連接配接)、叢集即時通訊元件,支援點對點通訊、群聊通訊、上線下線事件消息等衆多實用性功能。
ImCore
已正式改名為
FreeIM
。
使用場景:好友聊天、群聊天、直播間、實時評論區、遊戲。
FreeIM 解耦了通訊與業務子產品,讓項目架構變得更加簡單易維護,2017年的設計再過5年也不過時。
FreeIM 提供了一套永遠不需要疊代更新的
ImServer
服務端,支援 .NET5.0、.NETCore2.1+、NETStandard2.0。
以及一套簡單的 ImHelper API 提供給
業務端
使用,例如 ImHelper.SendMessage(a, b, 'hello world') 就可以實作 a -> b 發送消息。
開源位址:https://github.com/2881099/FreeIM
⛳ 項目由來
2017 年進朋友的公司救火,那個時候公司的人員架構、技術架構一團糟,例如通訊子產品,配備了幾人的全職團隊負責工作,痛點如下:
1、IM服務端代碼臃腫不堪;
2、邏輯混亂不堪,IM服務端代碼包含了大量業務邏輯,例如聊天記錄、訂單資料,這本來應該是業務方的資料;
3、混亂持續放大,IM服務端為了适應需求,不斷增加業務協定,越來越像業務端;
4、溝湧成本巨高,IM服務端經常和業務方開怼,比如某業務到底以誰的資料為準;
5、通訊協定失策,IM服務端使用原生Socket自定義通訊協定,後來要維護 WebSocket 及 自己定義協定兩套,經常發生消息無法傳輸的問題;
FreeIM 架構的接入之後,解散了 IM 團隊,解決了業務與通訊的職責沖突,簡化了架構,降低了維護成本。經曆 1年半的生産環境,整理代碼于 2018 年開源。
我是不是太卷了。。别急啊,他們是 java 團隊,是不是瞬間舒服了
⚡ 如何接入?
dotnet add package FreeIM
1、ImServer 服務端
一套永遠不需要疊代更新的IM服務端, ImServer
支援 .NET6.0、.NETCore2.1+、NETStandard2.0
public void Configure(IApplicationBuilder app)
{
app.UseFreeImServer(new ImServerOptions
{
Redis = new FreeRedis.RedisClient("127.0.0.1:6379,poolsize=5"),
Servers = new[] { "127.0.0.1:6001" }, //叢集配置
Server = "127.0.0.1:6001"
});
}
2、WebApi 業務端
public void Configure(IApplicationBuilder app)
{
//...
ImHelper.Initialization(new ImClientOptions
{
Redis = new FreeRedis.RedisClient("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 |
HasOnline | clientId | 判斷用戶端是否線上 |
EventBus | (上線委托, 離線委托) | socket上線與下線事件 |
頻道 | 參數 | 描述 |
---|---|---|
JoinChan | (clientId, 頻道名) | 加入 |
LeaveChan | (clientId, 頻道名) | 離開 |
GetChanClientList | (頻道名) | 擷取頻道所有clientId |
GetChanList | - | 擷取所有頻道和線上人數 |
GetChanListByClientId | (clientId) | 擷取使用者參與的所有頻道 |
GetChanOnline | (頻道名) | 擷取頻道的線上人數 |
SendChanMessage | (clientId, 頻道名, 消息内容) | 發送消息,所有線上的使用者将收到消息 |
- clientId 應該與使用者id相同,或者關聯;
- 頻道适用臨時的群聊需求,如聊天室、讨論區;
ImHelper 支援 .NetFramework 4.5+、.NetStandard 2.0
3、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) {
//...
};
})
📡 項目示範
運作環境:.NET6.0 + redis-server 2.8+
cd ImServer && dotnet run --urls=http://*:6001
cd WebApi && dotnet run
打開多個浏覽器,分别通路 http://127.0.0.1:5000 發送群消息
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5CN0gTN3QzM30CN2MTM0AjM0ETOygDMyIDMy0yN3kDN5YTMvwFOwIjMwIzLcdzN5QTO2EzLcd2bsJ2Lc12bj5ycn9Gbi52YuIjMwIzZtl2Lc9CX6MHc0RHaiojIsJye.png)
🎣 分析痛點
協定痛點:如果浏覽器使用 websocket 協定,iOS 使用其他協定,協定不一緻将很難維護。
職責痛點:IM 的系統一般涉及【我的好友】、【我的群】、【曆史消息】等等。。
ImServer
與
WebApi
(業務方) 該保持何種關系呢?
使用者A向好友B發送消息,分析一下:
- 需要判斷B是否為A好友;
- 需要判斷A是否有權限;
擷取曆史聊天記錄,多個
終端
websocket.send('gethistory'),再在 onmessage 定位回調處理,多麻煩啊?
諸如此類業務判斷會很複雜,使用
ImServer
做業務邏輯,最終
ImServer
和
終端
都将變成巨無霸難以維護。
🌈 設計思路
終端
(如浏覽器/小程式/iOS/android) 統一使用 websocket 連接配接
ImServer
;
ImServer
(支援叢集)根據 clientId 分區管理 websocket 連接配接;
WebApi
使用 ImHelper 調用方法(如:SendMessage、群聊相關方法),将資料推至 Redis chan;
ImServer
訂閱 Redis chan,收到消息後向
終端
推送消息;
- 緩解了并發推送消息過多的問題;
- 解決了連接配接數過多的問題;
- 解耦了業務和通訊,架構更加清淅;
-
充當消息轉發,連接配接維護,代碼萬年不變、且不需要重新開機維護ImServer
-
負責所有業務WebApi
-
舉例1、使用者A向B發送消息:
終端
A ajax ->
WebApi
->
ImServer
->
終端
B websocket.onmessage;
舉例2、擷取曆史聊天記錄:
終端
請求
WebApi
(業務方) 接口,傳回json(曆史消息)。
舉例3、A向B發檔案的例子:
- A向
傳檔案WebApi
-
通知WebApi
,ImHelper.SendMessage(B, "A正在給傳送檔案...")ImServer
- B收到消息,A正在給傳送檔案...
-
檔案接收完成時通知WebApi
,ImHelper.SendMessage(B, "A檔案傳輸完畢(含檔案連結)")ImServer
- B收到消息,A檔案傳輸完畢(含檔案連結)
FreeIM 強依賴 redis-server 元件功能:
- 內建了 redis 輕量級的訂閱釋出功能,實作消息緩沖發送,後期可更換為其他技術
- 使用了 redis 存儲一些關系資料,如線上 clientId、頻道資訊、授權資訊等
🌳 叢集分區
單個
ImServer
執行個體支援多少個用戶端連接配接,3萬?如果線上使用者有10萬人,怎麼辦???
部署 4 個
ImServer
:
-
1 訂閱 redisChan1ImServer
-
2 訂閱 redisChan2ImServer
-
3 訂閱 redisChan3ImServer
-
4 訂閱 redisChan4ImServer
WebApi
(業務方) 根據接收方的 clientId 後四位 16 進制與節點總數取模,定位到對應的 redisChan,進行 redis->publish 操作将消息定位到相應的
ImServer
。
每個
ImServer
管理着對應的終端連接配接,當接收到 redis 訂閱消息後,向對應的終端連接配接推送資料。
📰 事件消息
IM 系統比較常用的有上線、下線,在
ImServer
層才能準确捕捉事件,但業務代碼不合适在這上面編寫了。
此時采用 redis 釋出訂閱,将上線、下線等事件向指定頻道釋出,
WebApi
(業務方) 通過 ImHelper.EventBus 方法進行訂閱捕捉。
🌌 有感而發
為什麼說 SignalR 不合适做 IM?
1、IM 的特點必定是長連接配接,輪訓的功能用不上;
2、因為 SignalR 是雙工通訊的設計,
終端
使用 hub.invoke 發送指令給 SignalR 服務端處理業務,适合用來代替 ajax 減少 http 請求數量;
3、過多使用 hub,SignalR 服務端會被業務入侵,業務變化頻繁後不得不重新釋出版本,每次部署所有終端都會斷開連接配接,遇到5分鐘發一次業務更新檔的時候,類似離線和上線提示好友的功能就無法實作;
FreeIM 業務和推送分離設計,
終端
連接配接永不更新重新開機
ImServer
,業務代碼全部在
WebApi
編寫,是以重新開機
WebApi
不會造成連接配接斷開。
作者是什麼人?
作者是一個入行 18年的老批,他目前寫的.net 開源項目有:
開源項目 | 描述 | 開源位址 | 開源協定 |
---|---|---|---|
FreeIM | 聊天系統架構 | https://github.com/2881099/FreeIM | MIT |
FreeRedis | Redis SDK | https://github.com/2881099/FreeRedis | MIT |
csredis | https://github.com/2881099/csredis | MIT | |
FightLandlord | 鬥DI主網絡版 | https://github.com/2881099/FightLandlord | 學習用途 |
FreeScheduler | 定時任務 | https://github.com/2881099/FreeScheduler | MIT |
IdleBus | 空閑容器 | https://github.com/2881099/IdleBus | MIT |
FreeSql | ORM | https://github.com/dotnetcore/FreeSql | MIT |
FreeSql.Cloud | 分布式tcc/saga | https://github.com/2881099/FreeSql.Cloud | MIT |
FreeSql.AdminLTE | 低代碼背景生成 | https://github.com/2881099/FreeSql.AdminLTE | MIT |
FreeSql.DynamicProxy | 動态代理 | https://github.com/2881099/FreeSql.DynamicProxy | 學習用途 |
需要的請拿走,這些都是最近幾年的開源作品,以前更早寫的就不發了。