加入群组之后则可以发送和接收群组内的消息了,给群组发送消息的格式如下
{"Method":"Group", "Group":"g1", "MsgBody":"Hi All"}
Method为Group代表着用户加入群组的业务类型 , Group则代表你要发送到具体的群组的唯一标识,MsgBody则是发送到群组内的消息 。最后再来看下订阅群组内消息的情况,也就是处理群组消息的逻辑
private async Task SubGroupMsg(string channel){var sub = _redisClient.Subscribe(channel, async (channel, data) =>{//接收群组订阅消息ChannelMsgBody msgBody = JsonConvert.DeserializeObject<ChannelMsgBody>(data.ToString());byte[] sendByte = Encoding.UTF8.GetBytes($"group 【{msgBody.ToId}】 user 【{msgBody.FromId}】 send:{msgBody.Msg}");//获取当前服务器实例中当前群组的所有用户连接GroupUser.TryGetValue(msgBody.ToId, out var currentGroup);foreach (var user in currentGroup){if (user == msgBody.FromId){continue;}//通过群组内的用户标识去用户集合获取用户集合里的用户唯一连接发送消息if (UserConnection.TryGetValue(user, out var targetSocket) && targetSocket.State == WebSocketState.Open){await targetSocket.SendAsync(new ArraySegment<byte>(sendByte, 0, sendByte.Length), WebSocketMessageType.Text, true, CancellationToken.None);}else{currentGroup.Remove(user);}}});_disposables.TryAdd(channel, sub);}
全员消息处理全员消息处理相对来说思路比较简单,因为当服务启动的时候就会监听redis的全员消息频道,这样的话具体的实现也就只包含发送和接收全员消息了,首先看一下全员消息发送的逻辑
private async Task HandleAll(string id, object msgBody){_logger.LogInformation($"user {id} send:{msgBody}");//直接给redis的全员频道发送消息ChannelMsgBody channelMsgBody = new ChannelMsgBody { FromId = id, Msg = msgBody.ToString() };_redisClient.Publish(all, JsonConvert.SerializeObject(channelMsgBody));}
全员消息的发送数据格式如下所示
{"Method":"All", "MsgBody":"Hello All"}
Method为All代表着全员消息类型,MsgBody则代表着具体消息 。接收消息出里同样很简单,订阅redis全员消息频道,然后遍历当前WebSocket服务器实例内的所有用户获取连接发送消息,具体逻辑如下
private async Task SubAllMsg(string channel){var sub = _redisClient.Subscribe(channel, async (channel, data) =>{ChannelMsgBody msgBody = JsonConvert.DeserializeObject<ChannelMsgBody>(data.ToString());byte[] sendByte = Encoding.UTF8.GetBytes($"user 【{msgBody.FromId}】 send all:{msgBody.Msg}");//获取当前服务器实例内所有用户的连接foreach (var user in UserConnection){//不给自己发送消息,因为发送的时候可以通过具体的业务代码处理if (user.Key == msgBody.FromId){continue;}//给每个用户发送消息if (user.Value.State == WebSocketState.Open){await user.Value.SendAsync(new ArraySegment<byte>(sendByte, 0, sendByte.Length), WebSocketMessageType.Text, true, CancellationToken.None);}else{_ = UserConnection.TryRemove(user.Key, out _);}}});_disposables.TryAdd(channel, sub);}
示例源码由于篇幅有限,没办法设计到全部的相关源码 , 因此在这里贴出来github
相关的地址,方便大家查看和运行源码 。相关的源码我这里实现了两个版本,一个是基于asp.net core的版本,一个是基于golang的版本 。两份源码的实现思路是一致的,所以这两份代码可以运行在一套集群示例里 , 配置在一套nginx里,并且连接到同一个redis实例里即可
asp.net core
源码示例 https://github.com/softlgl/WebsocketClustergolang
源码示例 https://github.com/softlgl/websocket-cluster
总结本文基于
ASP.NET Core
框架提供了一个基于WebSocket
做集群的示例,由于思想是通用的,所以基于这个思路楼主也实现了golang
版本 。其实在之前就想自己动手搞一搞关于WebSocket集群方面的设计 , 本篇文章算是对之前想法的一个落地操作 。其核心思路文章已经做了相关介绍,由于这些只是博主关于构思的实现,可能有很多细节尚未体现到,还希望大家多多理解 。其核心思路总结一下- 首先是,利用可以构建WebSocket服务的框架,在当前服务实例中保存当前客户端用户和WebSocket的连接关系
- 如果消息的目标客户端不在当前服务器,可以利用redis频道、消息队列相关、甚至是数据库类的共享回话发送的消息,由目标服务器获取目标是否属于自己的ws会话
- 本文设计的思路使用的是无状态的方式,即WebSocket服务实例之间不存在直接的消息通信和相互的服务地址存储,当然也可以利用redis等存储在线用户信息等,这个可以参考具体业务自行设计
推荐阅读
- 关于入门深度学习mnist数据集前向计算的记录
- 《ASP.NET Core技术内幕与项目实战》精简集-目录
- 华为开发者大会HDC2022:HMS Core 持续创新,与开发者共创美好数智生活
- 上 学习ASP.NET Core Blazor编程系列十——路由
- 手记系列之二 ----- 关于IDEA的一些使用方法经验
- 重新整理 .net core 实践篇 ———— linux上性能排查 [外篇]
- 重新整理 .net core 实践篇 ———— linux上排查问题实用工具 [外篇]
- 华为开发者大会2022:HMS Core 3D建模服务再升级,万物皆可驱动
- 两种 .Net Core 3.0 对 MongoDB 的多条件查询操作
- 【JVM】关于JVM,你需要掌握这些 | 一文彻底吃透JVM系列