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
繁體地址,請注明出處:http://hk.pswp.cn/web/83211.shtml
英文地址,請注明出處:http://en.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; 為盡快 讓近期新招的新同…