SignalR:针对部分客户端的消息推送
上节我们使用Clients.All.SendAsync向连接到当前集线器的所有客户端推送消息,而在很多业务场景中,我们一般都只是向部分客户端推送消息。
在我们进行客户端筛选的时候,有3个筛选参数:ConnectionId、组和用户ID。ConnectionId是SignalR为每个连接分配的唯一标识,我们可以通过集线器的Context属性中的ConnectionId属性获取当前连接的ConnectionId;每个组有唯一的名字,对于连接到同一个集线器中的客户端,我们可以把它们分组;用户ID是登录用户的ID,它对应的是类型为ClaimTypes.NameIdentifier的Claim的值,如果使用用户ID进行筛选,我们需要在客户端登录的时候设定类型为ClaimTypes.NameIdentifier的Claim。
Hub类
Hub类的Groups属性为IGroupManager
类型,它可以用于对组成员进行管理,IGroupManager类包含如下所示的方法。
//将connectionId的连接添加到名字为groupName的组中 Task AddToGroupAsync(string connectionId, string groupName, CancellationToken cancellationToken = default); //将connectionId的连接从名字为groupName的组中删除 Task RemoveFromGroupAsync(string connectionId, string groupName, CancellationToken cancellationToken = default);
我们在把连接加入组中的时候,如果指定名字的组不存在,SignalR会自动创建组。因为连接和组的关系是通过ConnectionId建立的,所以客户端重连之后,我们就需要把连接重新加入组。
Hub类的Clients属性为IHubCallerClients
类型,它可以用来对连接到当前集线器的客户端进行筛选。IHubCallerClients类包含如下所示的成员。
//获取当前连接的客户端 T Caller { get; } //获取除了当前连接外的所有客户端 T Others { get; } //获取名字为groupName组中除了当前连接外的其他客户端 T OthersInGroup(string groupName); //获取所有连接的客户端 T All { get; } //获取除了excludedConnectionIds的客户端 T AllExcept(IReadOnlyList<string> excludedConnectionIds); //获取connectionId客户端 T Client(string connectionId); //获取包含在connectionIds中的客户端 T Clients(IReadOnlyList<string> connectionIds); //获取组名groupName中的客户端 T Group(string groupName); //获取组名groupName中的客户端,除了在excludedConnectionIds中 T GroupExcept(string groupName, IReadOnlyList<string> excludedConnectionIds); //获取组名包含在groupNames中的客户端 T Groups(IReadOnlyList<string> groupNames); //获取用户id的客户端 T User(string userId); //获取用户id包含在userIds中的客户端 T Users(IReadOnlyList<string> userIds);
这些成员的属性值、返回值都是IClientProxy类型的,我们可以通过IClientProxy向筛选到的客户端发送消息。IClientProxy类型中只定义了一个用来向客户端发送消息的SendCoreAsync
方法,我们调用的SendAsync方法是用来简化SendCoreAsync调用的扩展方法。基于性能、准确度等的考虑,我们并不能获得筛选到的每一个客户端的信息,只能向筛选到的客户端推送消息。
发送私聊消息
下面我们来为之前编写的Web聊天室增加“发送私聊消息”的功能。
第1步:
在ChatRoomHub中增加一个发送私聊消息的SendPrivateMessage方法。
public async Task<string> SendPrivateMessage(string destUserName, string message) { User? user = UserManager.FindByName(destUserName); if (user == null) { return "目标用户不存在"; } string userId = user.Id.ToString(); string srcUserName = this.Context.User!.FindFirst(ClaimTypes.Name)!.Value; string time = DateTime.Now.ToShortTimeString(); await this.Clients.User(userId) .SendAsync("ReceivePrivateMessage", srcUserName, time, message); return "ok"; }
需要注意的是,SignalR不会对消息进行持久化,因此即使目标用户当前不在线,SendAsync方法的调用也不会出错,而且用户上线后也不会收到离线期间的消息。同样的道理也适用于分组发送消息,用户在上线后才能加入一个分组,因此用户也无法收到离线期间该组内的消息。
如果我们的系统需要实现接收离线期间的消息的功能,就需要再自行额外开发消息的持久化功能,比如服务器端在向客户端发送消息的同时,也要把消息保存到数据库中;在用户上线时,程序要先到数据库中查询历史消息。
第2步:
在前端页面增加私聊功能的界面和代码。
公屏:<input type="text" v-model="state.userMessage" v-on:keypress="txtMsgOnkeypress" /> <div> 私聊给<input type="text" v-model="state.privateMsg.destUserName"/> 说<input type="text" v-model="state.privateMsg.message" v-on:keypress="txtPrivateMsgOnkeypress"/> </div>
const txtPrivateMsgOnkeypress = async function (e) { if (e.keyCode != 13) return; const destUserName = state.privateMsg.destUserName; const msg = state.privateMsg.message; try { const ret = await connection.invoke("SendPrivateMessage", destUserName, msg); if (ret != "ok") { alert(ret);}; } catch (err) { alert(err); return; } state.privateMsg.message = ""; };
第3步:
网页端监听服务器端发送的ReceivePrivateMessage消息,把收到的私聊消息添加到聊天消息界面中。
connection.on('ReceivePrivateMessage', (srcUser,time,msg) => { state.messages.push(srcUser+"在"+time+"发来私信:"+msg); });
最后:
运行结果如下。
聊天室功能可以私聊,可以把所有的用户都放到一个聊天室中,如果我们想实现多个聊天室的效果,就可以把用户放入不同的分组中,这样每个分组就是一个聊天室了。
本文来自博客园,作者:一纸年华,转载请注明原文链接:https://www.cnblogs.com/nullcodeworld/p/16743110.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
2021-09-29 C# 特性
2021-09-29 C# 阻止类被继承