ASP.NET Core SignalR - 部分客户端消息发送

文章目录

  • 前言
  • 一、消息发送的核心概念
    • 1.客户端标识
    • 2.消息接收范围
  • 二、向特定用户发送消息
    • 管理员向指定用户发送私信,或用户之间一对一聊天。
  • 三、向组发送消息
    • 聊天室、工作群组、通知订阅等。
  • 四、广播消息
    • 系统公告、实时统计数据更新等。
  • 五、向角色发送消息
    • 向管理员组发送系统警报,或向特定权限用户推送通知。
  • 六、客户端接收消息
    • JavaScript 客户端
  • 总结


前言

SignalR 提供了强大的消息发送机制,支持向特定用户、组或所有客户端广播消息。

一、消息发送的核心概念

1.客户端标识

  • 连接 ID (Context.ConnectionId):每次客户端连接时生成的唯一标识符,用于精确识别某个连接。
  • 用户标识 (Context.UserIdentifier):与身份验证关联的用户唯一标识(如数据库 ID),可关联多个连接(同一用户多设备登录)。

2.消息接收范围

  1. 所有客户端:Clients.All
  2. 特定用户:Clients.User(userId)
  3. 特定连接:Clients.Client(connectionId)
  4. 特定组:Clients.Group(groupName)
  5. 除发送者外的客户端:Clients.Others

二、向特定用户发送消息

管理员向指定用户发送私信,或用户之间一对一聊天。

  1. 代码如下(示例):
    /// <summary>
    /// 向特定用户发送消息 
    /// </summary>
    /// <param name="toUserName">接收者</param>
    /// <param name="content">发送的消息</param>
    /// <returns></returns>
    public async Task SendPrivateMsgAsync(string toUserName, string content)
    {try{var senderUserID = Context.UserIdentifier;var senderUser= await userManager.FindByIdAsync(senderUserID);var toUser = await userManager.FindByNameAsync(toUserName);await Clients.User(toUser.Id.ToString()).SendAsync("ReceivePrivateMsg", senderUser.UserName, content);}catch (Exception ex){throw;}}
    
    1. 关键点
    • 用户标识:需通过身份验证系统获取(如 JWT 的 sub 声明)。
    • 多设备支持:同一用户的多个连接都会收到消息。

三、向组发送消息

聊天室、工作群组、通知订阅等。

  1. 代码如下(示例):

    // 在内存中缓存组信息以提高性能
    private static readonly ConcurrentDictionary<string, GroupInfo> _groups = new ConcurrentDictionary<string, GroupInfo>();
    /// <summary>
    /// 创建自定义组
    /// </summary>
    /// <param name="groupName"></param>
    /// <returns></returns>
    public async Task CreateGroup(string groupName)
    {long userId = Convert.ToInt64(Context.UserIdentifier);if (_groups.ContainsKey(groupName)){await Clients.Caller.SendAsync("GroupCreationFailed", "组已存在");return;}// 创建新组并保存到数据库var group = new Group{GroupName = groupName,CreatedAt = DateTime.UtcNow,CreatorId = userId};myDbContext.Groups.Add(group);await myDbContext.SaveChangesAsync();// 添加到内存缓存var groupInfo = new GroupInfo{GroupId = group.GroupId,GroupName = groupName,MemberIds = new HashSet<long> { userId }};_groups.TryAdd(groupName, groupInfo);// 创建者自动加入组            await AddUserToGroup(groupName, userId);await Clients.All.SendAsync("GroupCreated", groupName);}
    private async Task AddUserToGroup(string groupName, long userId)
    {try{var groupInfo = _groups[groupName];// 添加到数据库var groupMember = new GroupMember{GroupId = groupInfo.GroupId,UserId = userId,JoinedAt = DateTime.UtcNow};myDbContext.GroupMembers.Add(groupMember);await myDbContext.SaveChangesAsync();}catch (Exception){throw;}}
    /// <summary>
    /// 加入自定义组
    /// </summary>
    /// <param name="groupName"></param>
    /// <returns></returns>
    public async Task JoinGroup(string groupName)
    {var userId = Convert.ToInt64(Context.UserIdentifier);if (!_groups.TryGetValue(groupName, out var groupInfo)){await Clients.Caller.SendAsync("JoinGroupFailed", "组不存在");return;}if (groupInfo.MemberIds.Contains(userId)){await Clients.Caller.SendAsync("JoinGroupFailed", "您已在该组中");return;}// 添加用户到组await AddUserToGroup(groupName, userId);// 更新内存缓存groupInfo.MemberIds.Add(userId);// 将用户加入 SignalR 组await Groups.AddToGroupAsync(Context.ConnectionId, groupName);await Clients.Group(groupName).SendAsync("UserJoinedGroup", Context.User.Identity.Name, groupName);//try//{//    if (_groups.ContainsKey(groupName))//    {//        await Groups.AddToGroupAsync(Context.ConnectionId, groupName);//        _groups[groupName].Add(Context.ConnectionId);//        await Clients.Group(groupName).SendAsync("UserJoinGroup", Context.UserIdentifier, groupName); ;//    }//}//catch (Exception ex)//{//    throw;//}}
    /// <summary>
    /// 用户离开自定义组
    /// </summary>
    /// <param name="groupName"></param>
    /// <returns></returns>
    public async Task LeaveGroup(string groupName)
    {var userId = Convert.ToInt64(Context.UserIdentifier);                if (!_groups.TryGetValue(groupName, out var groupInfo) ||!groupInfo.MemberIds.Contains(userId)){await Clients.Caller.SendAsync("LeaveGroupFailed", "您不在该组中");return;}// 从组中移除用户await RemoveUserFromGroup(groupName, userId);// 更新内存缓存groupInfo.MemberIds.Remove(userId);// 如果组为空,删除组if (groupInfo.MemberIds.Count == 0){await DeleteGroup(groupName);}else{// 将用户移出 SignalR 组await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);await Clients.Group(groupName).SendAsync("UserLeftGroup", Context.User.Identity.Name, groupName);}//if (_groups.ContainsKey(groupName) && _groups[groupName].Contains(Context.ConnectionId))//{//    await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);//    _groups[groupName].Remove(Context.ConnectionId);//    await Clients.Group(groupName).SendAsync("UserLeaveGroup",Context.UserIdentifier, groupName); //}
    }
    private async Task RemoveUserFromGroup(string groupName, long userId)
    {var groupInfo = _groups[groupName];// 从数据库移除var groupMember = await myDbContext.GroupMembers.FirstOrDefaultAsync(gm => gm.GroupId == groupInfo.GroupId && gm.UserId == userId);if (groupMember != null){myDbContext.GroupMembers.Remove(groupMember);await myDbContext.SaveChangesAsync();}
    }private async Task DeleteGroup(string groupName)
    {if (_groups.TryRemove(groupName, out var groupInfo)){// 从数据库删除组var group = await myDbContext.Groups.FindAsync(groupInfo.GroupId);if (group != null){myDbContext.Groups.Remove(group);await myDbContext.SaveChangesAsync();}await Clients.All.SendAsync("GroupDeleted", groupName);}
    }
    
  2. 关键点

  • 组管理:需手动维护用户与组的关系(如 JoinGroup 和 LeaveGroup)。
  • 持久化:组信息不持久化,服务器重启后需重新加入。

四、广播消息

系统公告、实时统计数据更新等。

  1. 代码示例:
    /// <summary>
    /// 向所有用户发送消息
    /// </summary>
    /// <param name="user"></param>
    /// <param name="content"></param>
    /// <returns></returns>
    [Authorize(Roles = "admin")]
    public async Task SendMessageAsync(string user, string content)
    {//var connectionId = this.Context.ConnectionId;//string msg = $"{connectionId},{DateTime.Now.ToString()}:{user}";await Clients.All.SendAsync("ReceiveMsg", user, content);
    }/// <summary>/// 向除发送者外的所有客户端发送消息/// </summary>/// <param name="sender"></param>/// <param name="content"></param>/// <returns></returns>public async Task SendOthersMsg(string sender, string content){await Clients.Others.SendAsync("ReceiveMsg",sender, content);}
    

五、向角色发送消息

向管理员组发送系统警报,或向特定权限用户推送通知。

  1. 代码示例:
     /// <summary>/// 向管理员组AdminUsers发送消息/// </summary>/// <param name="sender"></param>/// <param name="content"></param>/// <returns></returns>public async Task SendAdminMsgAsync(string sender, string content){await Clients.Group("AdminUsers").SendAsync("ReceiveAdminMsg", sender, content);}
    

六、客户端接收消息

JavaScript 客户端

  1. 代码示例:

    // 创建新连接
    state.connection = new signalR.HubConnectionBuilder().withUrl(state.serverUrl, {accessTokenFactory: () => token,skipNegotiation: true,transport: signalR.HttpTransportType.WebSockets}).withAutomaticReconnect().configureLogging(signalR.LogLevel.Information).build();// 注册消息处理程序
    state.connection.on("ReceiveMsg", (user, message) => {state.messages.push({type: 'broadcast',sender: `${user}(广播消息)`,content: message,timestamp: new Date()});});state.connection.on("ReceivePrivateMsg", (sender, message) => {              if (!sender || !message) return;state.messages.push({type: 'private',sender: `${sender} (私信)`,content: message,timestamp: new Date()});});state.connection.on("ReceiveGroupMsg", (sender, group, message) => {state.messages.push({type: 'group',sender: `${sender} (${group})`,content: message,group: group,timestamp: new Date()});});......
    // 启动连接
    await state.connection.start();
    

总结

通过以上方法,你可以灵活实现 SignalR 的部分消息发送功能,满足不同场景下的实时通信需求。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/web/83211.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前后端交互过程中—各类文件/图片的上传、下载、显示转换

前后端交互过程中—各类文件/图片的上传、下载、显示转换 图片上传下载常用函数&#xff1a;new Blob()**blobParts&#xff1a;&#xff08;必传&#xff09;****options&#xff1a;&#xff08;可选&#xff09;**blob的常见的MIME类型&#xff1a; URL.createObjectURL()替…

校园二手交易平台(微信小程序版)

文章目录 1. 项目概述2. 项目功能思维导图3. 技术架构1. 前端技术栈2. 后端技术栈 4. 核心模块实现5. 总结6. 项目实现效果截图7. 关于作者其它项目视频教程介绍 1. 项目概述 校园二手交易平台微信小程序旨在为在校学生提供一个便捷的二手物品交易渠道&#xff0c;包含用户模块…

Linux简单的操作

ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …

【芯片设计- RTL 数字逻辑设计入门 4.2 -- 组合逻辑赋值 + 时序逻辑状态保持】

文章目录 Overview原语句分析变量含义假设(根据命名推测)状态更新逻辑详解状态转移逻辑举个实际例子小结Overview 本文将详细介绍 verilog rtl 中 assign reg_halt_mode_nx = halt_taken | (reg_halt_mode & ~halt_return);的作用,以及这里为何要使用 reg_halt_mode,…

【单片机期末】汇编试卷

一、选择题 DPTR是16位的&#xff0c;所以寻址范围是64KB R1是8位的&#xff0c;只能寻址256 访问内部ROM只能用MOVC指令 一个指令周期是时钟周期的1/12 12个时钟周期是一个机器周期 单指令周期是指一个机器周期 T 1 / f 12MHz ~ 1us 13位计数16位计数8位自动重装载双8位计数器…

校验枚举类类型的入参合法性的统一方案

文章目录 背景解决实践定义枚举类 InEnum注解定义验证逻辑 InEnumValidator 实际使用 背景 业务要做电商平台做入参, 在电商平台被抽离成枚举类的情况下 &#xff0c;要怎么验证输入的参数是正确的呢? 解决 Constraint 实现自定义验证逻辑 Constraint 注解用于标注其他注解&am…

Unity-NavMesh详解-其一

今天我们来详细地探究一下Unity的NavMesh这一性能强大的组件&#xff1a; NavMesh基本使用 NavMesh简单地说本质上是一个自动寻路的AI组件&#xff0c;我们首先来学习基本的使用。 画面中我已经添加好了地面&#xff0c;目标&#xff0c;障碍物以及玩家四个要素。 注意我们要…

vue的created和mounted区别

在Vue.js中&#xff0c;created和mounted的核心区别在于调用时机和DOM可访问性‌&#xff1a;created钩子在组件实例创建后、DOM挂载前调用&#xff0c;适用于数据初始化&#xff1b;mounted钩子在DOM挂载后调用&#xff0c;支持DOM操作。‌‌ ‌调用时机与核心能力对比‌ ‌…

MySQL 8.0 OCP 英文题库解析(十四)

Oracle 为庆祝 MySQL 30 周年&#xff0c;截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始&#xff0c;将英文题库免费公布出来&#xff0c;并进行解析&#xff0c;帮助大家在一个月之内轻松通过OCP认证。 本期公布试题121~130 试题1…

【HarmonyOS 5】拍摄美化开发实践介绍以及详细案例

以下是 HarmonyOS 5 拍摄美化功能的简洁介绍&#xff0c;整合核心能力与技术亮点&#xff1a; 一、AI 影像创新 ‌AI 魔法移图‌ 系统级图像分层技术实现人物/物体自由拖拽、缩放与复制&#xff0c;突破传统构图限制。自动分离主体与背景&#xff0c;一键生成错位创意照&…

【Java多线程从青铜到王者】懒汉模式的优化(九)

懒汉模式的问题 我们看上述的代码&#xff0c;当第一次调用getIntance的时候&#xff0c;intance为null&#xff0c;就会进入if里面&#xff0c;创建出实例&#xff0c;当不是第一次调用的时候&#xff0c;此时的intandce不是null&#xff0c;不进入循环&#xff0c;直接return…

SCI期刊查重参考文献会被查重吗?

查重的时候&#xff0c;参考文献不会被查重。 不管中文还是英文查重系统里一般都有排除参考文献的设置。 比如英文查重系统iThenticate 的排除文献的设置如下&#xff1a; 在iThenticate在线报告界面的右下角点击“漏斗”图标&#xff08;Filter&#xff09;&#xff0c; ✔…

OpenLayers 获取地图状态

注&#xff1a;当前使用的是 ol 5.3.0 版本&#xff0c;天地图使用的key请到天地图官网申请&#xff0c;并替换为自己的key 地图状态信息包括中心点、当前缩放级别、比例尺以及当前鼠标移动位置信息等&#xff0c;在WebGIS开发中&#xff0c;地图状态可以方便快捷的向用户展示基…

JxBrowser 8.8.0 版本发布啦!

一次调用即可下载文件精准清除浏览数据右键点击位置检测获取元素在视口中的位置 &#x1f517; 点击此处了解更多详情。 &#x1f193; 获取 30 天免费试用。

React 中的TypeScript开发范式

在 TypeScript 中使用 React 可以提高代码的可维护性、可读性和可靠性。TypeScript 提供了静态类型检查和丰富的类型系统&#xff0c;这些功能在 React 开发中非常有用。下面详细介绍如何在 React 项目中使用 TypeScript&#xff0c;并结合泛型和 infer 来定义类型。 1. 项目初…

72道Nginx高频题整理(附答案背诵版)

1. 简述什么是Nginx &#xff1f; Nginx 是一个开源的高性能HTTP和反向代理服务器&#xff0c;也能够用作IMAP/POP3/SMTP代理服务器。它最初由Igor Sysoev为俄罗斯的一个大型网站Rambler开发&#xff0c;并在2004年首次公开发布。Nginx被设计用来解决C10k问题&#xff0c;即同…

AI时代,数据分析师如何成为不可替代的个体

在数据爆炸的 AI 时代&#xff0c;AI工具正以惊人的速度重塑数据分析行业&#xff0c;数据分析师的工作方式正在经历一场前所未有的变革。数据分析师又该如何破局&#xff0c;让自己不被AI取代呢&#xff1f; 一、AI工具对重复性工作的彻底解构 如以往我们需要花几天写一份数…

DockerHub与私有镜像仓库在容器化中的应用与管理

哈喽&#xff0c;大家好&#xff0c;我是左手python&#xff01; Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库&#xff0c;用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…

Kafka入门-Broker以及文件存储机制

Kafka Broker Broker实际上就是kafka实例&#xff0c;每一个节点都是独立的Kafka服务器。 Zookeeper中存储的Kafka信息 节点的服役以及退役 服役 首先要重新建立一台全新的服务器105&#xff0c;并且在服务器中安装JDK、Zookeeper、以及Kafka。配置好基础的信息之后&#x…

dexcap升级版之DexWild——面向户外环境的灵巧手交互策略:人类和机器人演示协同训练(人类直接带上动捕手套采集数据)

前言 截止到25年6.6日&#xff0c;在没动我司『七月在线』南京、武汉团队的机器的前提下&#xff0c;长沙这边所需的前几个开发设备都已到齐——机械臂、宇树g1 edu、VR、吊架 ​长沙团队必须尽快追上南京步伐 加速前进 如上篇文章所说的&#xff0c; 为尽快 让近期新招的新同…