在上一篇 SignalR 文章中, 演示了如何通过 SignalR 实现了简单的聊天室功能; 本着简洁就是美的原则, 这一篇我们也来聊聊在 SignalR 中的用户和组的概念, 理解这些基础知识有助于更好的开发基于 SignalR 的应用, 通过对用户和分组的理解, 进一步扩展出对用户和分组的管理, 以及消息推送的各种方式, 为全面接入 SignalR 做准备.
1. 用户
在 SignalR 中, 用户表示连接, 一个用户代表一个连接, 一个 "系统用户" 可以创建多个连接身份, 通过函数集线器, 可以给一个用户的所有连接发送消息; 比如一个 "系统用户" 拥有多个连接, 这些连接分别是 web 连接, Android 手机客户端连接, iOS 手机客户端连接, 或者其它客户端连接,"系统用户" 分别登录了这些客户端, 同时创建了多个连接; 默认情况下这些连接都通过 ClaimTypes.NameIdentifier 在 ClaimsPrincipal 于用户标识进行关联.
** 注意: 用户标识符是区分大小写的, 为了实现一个客户多个连接, 本例还简单实现了一个基于 ClaimsIdentity 登录接口, 算是意外惊喜.
1.1 用户连接管理
为了直观的观察到用户是可以拥有多连接的, 需要建立一个本地静态对象, 用于存储用户连接
- public class WeChatHub : Hub
- {
- public Dictionary<string, List<string>> UserList { get; set; } = new Dictionary<string, List<string>>();
- public void Send(ChatMessage body)
- {
- Clients.All.SendAsync("Recv", body);
- }
- public override Task OnConnectedAsync()
- {
- var userName = this.Context.User.Identity.Name;
- var connectionId = this.Context.ConnectionId;
- if (!UserList.ContainsKey(userName))
- {
- UserList[userName] = new List<string>();
- UserList[userName].Add(connectionId);
- }
- else if (!UserList[userName].Contains(connectionId))
- {
- UserList[userName].Add(connectionId);
- }
- Console.WriteLine("哇, 有人进来了:{0},{1},{2}", this.Context.UserIdentifier, this.Context.User.Identity.Name, this.Context.ConnectionId);
- return base.OnConnectedAsync();
- }
- public override Task OnDisconnectedAsync(Exception exception)
- {
- var userName = this.Context.User.Identity.Name;
- var connectionId = this.Context.ConnectionId;
- if (UserList.ContainsKey(userName))
- {
- if (UserList[userName].Contains(connectionId))
- {
- UserList[userName].Remove(connectionId);
- }
- }
- Console.WriteLine("靠, 有人跑路了:{0}", this.Context.ConnectionId);
- return base.OnDisconnectedAsync(exception);
- }
- }
上面的代码包含了一个内部成员 UserList, 用于存储用户的每个连接, 在用户进行 SignalR 连接时, 将当前连接存储到 UserList 中, 当连接断开的时候, 将当前连接从 UserList 中删除. 这样就实现了一个简单的用户连接管理.
在上面的代码中, 当前用户昵称是根据 var userName = this.Context.User.Identity.Name; 这行代码获取的, 为了取得这个用户昵称, 我们实现了一个简单的 UserIdentity 登录, 然后将 User 信息写入到 Cookie 中, 最后才可以通过 var userName = this.Context.User.Identity.Name; 获得当前登录用户昵称 (熟悉 ID 登录流程的同学应该不会感到陌生, 实际上我也很少使用 ID 验证)
1.2 给单个用户发送消息
- [Authorize(Roles = "User")]
- [HttpPost("SendToUser")]
- public async Task<IActionResult> SendToUser([FromBody] UserInfoViewModel model)
- {
- ChatMessage message = new ChatMessage()
- {
- Type = 1,
- Content = model.Content,
- UserName = model.UserName
- };
- if (this.chatHub.UserList.ContainsKey(model.UserName))
- {
- var connections = this.chatHub.UserList[model.UserName].First();
- await this.chatHub.Clients.Client(connections).SendAsync("Recv", new object[] { message });
- }
- return JSON(new { Code = 0 });
- }
在 UserController 中, 定义了上面的接口 SendToUser , 客户端传入用户昵称和消息, 然后服务端就会去根据 ChatHub.UserList 成员查找目标用户的连接信息, 最后, 通过 SendAsync 将消息推送到目标客户端连接中.
2. 分组
分组的概念类似于聊天室, 每个房间就是一个独立的分组, 用户可以选择加入 A 房间, 也可以选择加入 B 房间, 如果业务允许, 一个用户还可以加入多个分组 (房间), 通过使用分组对用户进行管理, 可以实现一个或者多个聊天房间, 用户可以加入分组, 也可以将用户从分组中删除 (类似离开房间), 这里的用户并发真正意义上的 "系统用户", 而是指系统用户创建的那些 SignalR 连接.
** 注意: 当连接断开后重新发起连接的时候, SignalR 不会保留组成员身份, 必须重新加入分组.
下面的代码演示了如何对分组进行操作, 要对分组进行操作, 主要包含三个方面:
2.1 加入分组
- public async Task AddToGroupAsync(string groupName)
- {
- await Groups.AddToGroupAsync(this.Context.ConnectionId, groupName);
- }
2.2 离开分组
- public async Task RemoveFromGroupAsync(string groupName)
- {
- await Groups.RemoveFromGroupAsync(this.Context.ConnectionId, groupName);
- }
2.3 发送消息到指定分组
- public async Task SendToGroupAsync(string groupName, ChatMessage message)
- {
- await Clients.Group(groupName).SendAsync(groupName, new object[] { message });
- }
对分组的操作非常的简单, 几乎都是一行代码的事情, 不得不说, 微软的封装实在是太好了.
3. SignalR 的推送消息的其它方式
通过上面对用户和分组的学习, 再去扩展学习其它推送消息的方式, 就非常的好理解和上手, 在 SignalR 内部还有多种推送消息的方式, 他们分别是
- 3.1 All(全站推送)
- 3.2 Others(全站推送排除自己)
- 3.3 OthersInGroup(指定分组推送, 排除自己)
- 3.4 AllExcept(除指定列表外的所有人)
3.5 演示代码
- List<string> blackList = new List<string>();
- public async Task OtherSendAsync(ChatMessage body)
- {
- // 给当前连接到 Hub 上的所有连接发送消息, 相当于广播
- await Clients.All.SendAsync("Recv", body);
- // 给当前连接对象发送消息
- await Clients.Caller.SendAsync("Recv", body);
- // 给其它所有连接的客户端发送消息, 除了当前正在连接的客户端
- await Clients.Others.SendAsync("Recv", body);
- // 查找当前所有连接的客户端 (排除自己), 如果是已加入此分组, 则给他们推送消息
- await Clients.OthersInGroup("groupName").SendAsync("Recv", body);
- // 给除了 blackList(黑名单) 之外的所有人发送消息
- await Clients.AllExcept(blackList).SendAsync("Recv", body);
- }
4. 一个简单的示例
本示例代码包含两个简单的界面
4.1 登录
4.2 各种方式发送消息
结束语
最近在做一个开源项目, 还处于试用阶段, 准备写个使用的 WIKI 出来, 看看大家是否感兴趣, 此 SingalR 系列只能不定期更新了, 抱歉.
演示代码下载
已托管到 GitHub 仓库
来源: https://www.cnblogs.com/viter/p/10638331.html