美文网首页
[开源精品] C#.NET im 聊天通讯架构设计 -- Fre

[开源精品] C#.NET im 聊天通讯架构设计 -- Fre

作者: 叶先生_34e6 | 来源:发表于2022-08-29 15:34 被阅读0次

    💻 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 发送群消息

    image.png

    🎣 分析痛点

    协议痛点:如果浏览器使用 websocket 协议,iOS 使用其他协议,协议不一致将很难维护。

    职责痛点:IM 的系统一般涉及【我的好友】、【我的群】、【历史消息】等等。。

    ImServerWebApi(业务方) 该保持何种关系呢?

    用户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 通知 ImServer,ImHelper.SendMessage(B, "A正在给传送文件...")
    • B收到消息,A正在给传送文件...
    • WebApi 文件接收完成时通知 ImServer,ImHelper.SendMessage(B, "A文件传输完毕(含文件链接)")
    • B收到消息,A文件传输完毕(含文件链接)

    FreeIM 强依赖 redis-server 组件功能:

    • 集成了 redis 轻量级的订阅发布功能,实现消息缓冲发送,后期可更换为其他技术
    • 使用了 redis 存储一些关系数据,如在线 clientId、频道信息、授权信息等

    🌳 集群分区

    单个 ImServer 实例支持多少个客户端连接,3万?如果在线用户有10万人,怎么办???

    部署 4 个 ImServer

    • ImServer1 订阅 redisChan1
    • ImServer2 订阅 redisChan2
    • ImServer3 订阅 redisChan3
    • ImServer4 订阅 redisChan4

    WebApi(业务方) 根据接收方的 clientId 后四位 16 进制与节点总数取模,定位到对应的 redisChan,进行 redis->publish 操作将消息定位到相应的 ImServer

    每个 ImServer 管理着对应的终端连接,当接收到 redis 订阅消息后,向对应的终端连接推送数据。


    📰 事件消息

    IM 系统比较常用的有上线、下线,在 ImServer 层才能准确捕捉事件,但业务代码不合适在这上面编写了。

    此时采用 redis 发布订阅,将上线、下线等事件向指定频道发布,WebApi(业务方) 通过 ImHelper.EventBus 方法进行订阅捕捉。

    image.png

    🌌 有感而发

    为什么说 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 学习用途

    需要的请拿走,这些都是最近几年的开源作品,以前更早写的就不发了。

    相关文章

      网友评论

          本文标题:[开源精品] C#.NET im 聊天通讯架构设计 -- Fre

          本文链接:https://www.haomeiwen.com/subject/wstknrtx.html