<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12
      有夢想的魚
      寫代碼我得再認真點,當我最終放下鍵盤時,我實在不想仍有太多疑惑

      基本介紹

      1.什么是signalR

      SignalR 是微軟開發的一個開源庫,它可以讓服務器端代碼能夠即時推送內容到連接的客戶端,用來簡化向客戶端應用程序添加實時功能的過程。

      • 大白話的意思就是微軟搞了一個可以用來做服務端推送的庫,并且都是幫你封裝好了的,你不用操心,用就完了

      特點:

      概念 說明
      雙工通信 服務端和客戶端可以互相發送數據,互不干擾,實現雙向實時通信。例如,Web API 的 Controller 是單向請求-響應模式,而 SignalR 的 Hub 支持服務端主動推消息給客戶端,客戶端也能調用服務端方法,形成雙向交互。
      傳輸降級 SignalR 會自動選擇最佳的通信方式。優先嘗試 WebSocket,若不支持則依次降級為 Server-Sent Events 或長輪詢等。整個過程對開發者透明,確保在各種瀏覽器和網絡環境下都能建立連接。
      簡化開發 SignalR 內置了心跳檢測、連接存活檢查、斷線自動重連等機制,開發者無需手動實現這些復雜邏輯,極大降低了實時通信功能的開發難度。
      Hub 可以理解為一個“實時通信中轉站”。就像你和朋友之間有一個快遞站(Hub),你想發消息(寄快遞),就把包裹交給 Hub;朋友也能通過同一個 Hub 寄快遞給你。Hub 負責把消息準確送達對方,并通知接收方有新消息到來。它是 SignalR 的核心通信中心。

      主要應用場景:

      應用場景 說明 是否推薦
      實時消息傳遞 搭建聊天室、下象棋等需要低延遲雙向通信的場景,非常適合 SignalR。 ? 推薦
      實時消息通知 向客戶端推送通知(如系統提醒、訂單狀態更新等),是 SignalR 的典型用例。 ? 推薦
      數據統計看板 實時將動態數據推送給前端看板(如銷售數據、監控指標),體驗流暢。 ? 推薦
      服務之間通信 理論上可行,但不建議使用 SignalR 進行服務間通信。應使用 gRPC、REST、消息隊列等更合適的方案。 ? 不建議

      當然根據他的特性還能能延伸出更多應用場景,但目前在實際開發中,我使用SignalR的場景就是:
      1.在業務系統內部作為站內信發送通知。
      2.作為前端實時數據看板展示的業務數據,剛畢業那會,前端用js 開一堆定時器來請求后端接口刷新數據報表,每隔2天就讓客戶去F5一下瀏覽器,客戶問為什么,當時的解釋是電腦不行 ??。

      2.WebSocket 和 SignalR

      其實說到推送相關的話題,有很多實現方式,應用上使用純WebSocket也可以實現,簡單方便,最終還是得結合自己的實際需求來權衡,例如拿巴掌拍蚊子,和拿大炮打蚊子,方法上都行得通,最重要的是把握這個“度”,這里就不一一列舉了.

      為什么不直接用 WebSocket 說明
      1. 開發效率高 SignalR 封裝了底層細節,提供了開箱即用的 API,屏蔽了連接管理、序列化、異常處理等復雜邏輯,大幅提升了開發效率。雖然相比原生 WebSocket 有一定性能損耗,但換來了極高的生產力。
      2. 支持傳輸降級 SignalR 能根據客戶端環境自動選擇最佳傳輸方式(WebSocket → Server-Sent Events → 長輪詢)。在復雜網絡環境或老舊瀏覽器中仍能保持連接,而純 WebSocket 在不支持或被代理阻塞時會直接失敗。
      3. 省去基礎設施開發 若使用原生 WebSocket,需自行實現心跳檢測、斷線重連、消息確認、集群同步等機制,開發和維護成本高。SignalR 已內置這些功能,開箱即用。
      4. 已有成熟框架,何不善用? SignalR 是一個經過生產驗證的成熟框架,解決了實時通信中的常見痛點。既然有穩定可靠的輪子,就沒有必要重復造輪子,可以更專注于業務邏輯的實現。

      image

      上手實踐

      1.基本概念

      角色 說明
      服務端 為你提供消息推送服務的后端應用程序,負責處理連接、業務邏輯,并通過 SignalR 向客戶端主動發送數據。
      客戶端 接收消息的一方,可以是瀏覽器(JavaScript)、移動應用、桌面程序等,通過連接到 Hub 來接收服務端推送的實時消息。
      Hub SignalR 中客戶端與服務端進行消息交換和推送的核心抽象代理。它相當于一個“通信中心”,客戶端和服務端通過 Hub 進行雙向方法調用和消息傳遞。
      屬性 說明
      ConnectionId 獲取連接的唯一 ID(在連接時 SignalR 分配)。
      UserIdentifier 用戶標識一般是用戶id,關聯連接和用戶。
      User 當前用戶的 ClaimsPrincipal(身份信息)。
      Items 一些共同的數據可以在連接建立后加載,然后存到Items,在后續不同的方法中都可以訪問到,不過數據僅存在內存中,連接斷開后自動銷毀
      ConnectionAborted 獲取一個 CancellationToken,它會在客戶端連接中止時發出通知,比 OnDisconnectedAsync更早觸發,更及時。

      Items屬性舉例

      // 連接建立查詢數據庫獲取用戶信息
      public override async Task OnConnectedAsync()
      {
          var user = await db.GetUserAsync(Context.UserIdentifier);
          Context.Items["UserProfile"] = user; // 緩存用戶數據
      }
      
      
      // 在其他方法中獲取Items拿到
      public async Task SendMessage(string message)
      {
          var user = (UserProfile)Context.Items["UserProfile"]; // 直接讀取緩存
          // ... 使用用戶數據
      }
      
      

      2.基本使用

      后端代碼使用.net7,客戶端使用js,分為2部分,基本使用,以及更加貼合業務的實現

      先鋪墊一下,這一部分有條件自己試一下向個人,所有人,以及組發送消息的api,后續會分享它內部實現,也很巧妙。

      1.首先注入使用SignalR需要的相關服務和配置

      services.AddSignalR(options =>
      {
          options.ClientTimeoutInterval = TimeSpan.FromMinutes(1);
          options.KeepAliveInterval = TimeSpan.FromSeconds(10);
          options.EnableDetailedErrors = true;
      })
      

      2.定義一個Hub

      // Hubs/PushMsgHub.cs
      using Microsoft.AspNetCore.SignalR;
      
      public class PushMsgHub : Hub
      {
          // 連接事件
          public override async Task OnConnectedAsync()
          {
              await base.OnConnectedAsync();
          }
      
          // 斷開連接事件
          public override async Task OnDisconnectedAsync(Exception? exception)
          {
              UserManager.RemoveUser(Context.ConnectionId);
              await base.OnDisconnectedAsync(exception);
          }
      
          // 客戶端調用此方法登錄并注冊用戶信息
          public async Task Login(string userId, string name, string companyId, string orgId)
          {
              var user = new User{ConnectionId = Context.ConnectionId,UserId = userId,Name = name,CompanyId = companyId,OrgId = orgId};
              UserManager.AddUser(user);
      
              await Clients.Caller.SendAsync("ReceiveMessage", "系統", $"{name}登錄成功!");
          }
      
          // 發送消息給指定用戶
          public async Task SendMessageToUser(string toUserId, string message)
          {
              var fromUser = UserManager.GetUserByConnectionId(Context.ConnectionId);
              var toUser = UserManager.GetUserById(toUserId);
      
              if (toUser != null)
              {
                  await Clients.Client(toUser.ConnectionId).SendAsync("ReceiveMessage", $"{fromUser.Name} (私信)", message);
              }
              else
              {
                  await Clients.Caller.SendAsync("ReceiveMessage", "系統", "用戶不在線或不存在。");
              }
          }
      
          // 發送給組織內所有用戶
          public async Task SendMessageToOrg(string message)
          {
              var fromUser = UserManager.GetUserByConnectionId(Context.ConnectionId);
              var users = UserManager.GetUsersByOrg(fromUser.OrgId);
      
              foreach (var user in users)
              {
                  await Clients.Client(user.ConnectionId).SendAsync("ReceiveMessage", $"【組織】{fromUser.Name}", message);
              }
          }
      
          // 發送給公司內所有用戶
          public async Task SendMessageToCompany(string message)
          {
              var fromUser = UserManager.GetUserByConnectionId(Context.ConnectionId);
              var users = UserManager.GetUsersByCompany(fromUser.CompanyId);
      
              foreach (var user in users)
              {
                  await Clients.Client(user.ConnectionId).SendAsync("ReceiveMessage", $"【公司】{fromUser.Name}", message);
              }
          }
      }
      

      3.然后將Hub映射到中間件管道路由中

      app.MapHub<ChatHub>("/PushMsgHub");
      

      4.htmljs測試代碼

      <!-- index.html -->
      <!DOCTYPE html>
      <html>
      <head>
          <title>SignalR Demo</title>
      </head>
      <body>
          <h2>SignalR 消息系統</h2>
      
          <div>
              <label>用戶ID: <input id="userId" value="u001" /></label>
              <label>姓名: <input id="name" value="張三" /></label>
              <label>公司ID: <input id="companyId" value="comp001" /></label>
              <label>組織ID: <input id="orgId" value="org001" /></label>
              <button onclick="login()">登錄</button>
          </div>
      
          <hr />
      
          <div>
              <h3>發送私信</h3>
              <input id="toUserId" placeholder="目標用戶ID" value="u002" />
              <input id="privateMsg" placeholder="輸入私信內容" />
              <button onclick="sendPrivate()">發送</button>
          </div>
      
          <div>
              <h3>發送組織消息</h3>
              <input id="orgMsg" placeholder="組織內廣播消息" />
              <button onclick="sendToOrg()">發送</button>
          </div>
      
          <div>
              <h3>發送公司消息</h3>
              <input id="companyMsg" placeholder="公司內廣播消息" />
              <button onclick="sendToCompany()">發送</button>
          </div>
      
          <hr />
          <h3>消息記錄</h3>
          <ul id="messages"></ul>
      
          <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/8.0.0/signalr.min.js"></script>
          <script>
              const connection = new signalR.HubConnectionBuilder()
                  .withUrl("http://localhost:5200/PushMsgHub")
                  .build();
      
              function log(message) {
                  const li = document.createElement("li");
                  li.textContent = message;
                  document.getElementById("messages").appendChild(li);
              }
      
              connection.on("ReceiveMessage", (user, message) => {
                  log(`${user}: ${message}`);
              });
      
              connection.start().then(() => log("連接到 SignalR 服務器")).catch(err => console.error(err));
      
              async function login() {
                  const userId = document.getElementById("userId").value;
                  const name = document.getElementById("name").value;
                  const companyId = document.getElementById("companyId").value;
                  const orgId = document.getElementById("orgId").value;
      
                  await connection.invoke("Login", userId, name, companyId, orgId);
              }
      
              async function sendPrivate() {
                  const toUserId = document.getElementById("toUserId").value;
                  const msg = document.getElementById("privateMsg").value;
                  await connection.invoke("SendMessageToUser", toUserId, msg);
              }
      
              async function sendToOrg() {
                  const msg = document.getElementById("orgMsg").value;
                  await connection.invoke("SendMessageToOrg", msg);
              }
      
              async function sendToCompany() {
                  const msg = document.getElementById("companyMsg").value;
                  await connection.invoke("SendMessageToCompany", msg);
              }
          </script>
      </body>
      </html>
      

      3.強類型Hub用法

      與傳統的直接繼承 Hub 相比,有設計上的優勢,先看下面不使用強類型的截圖。

      image

      1.先定義一個接口

      public interface IPushMessageHubAsync
      {
          Task ReceiveMessage(string message);
      }
      

      2.然后優化這個Hub像這樣寫

      public class MsgPushHub : Hub<IPushMessageHubAsync>
      {
          public async Task SendMessage(string message)
          {
              await Clients.All.ReceiveMessage(message);
          }
      }
      

      IPushMessageHubAsync: 用于約定具體推送的業務類型,這里的接口名就是實際推送到客戶端的名字,同理如果有報表展示的需要可以定義一個為 Hub,在業務層面不需要實現它們,只是為了規范業務和參數標準化, 但是框架層面運行時會為他們創建代理.

      • 羅列的對比
      對比項 Hub<IClientContract>(強類型) 直接繼承 Hub(弱類型)
      類型安全 ? 編譯時檢查方法名、參數 ? 運行時才報錯(字符串魔法值)
      修改 ? 改方法名時 IDE 自動提示,接口與實現同步更新 ? 手動修改所有字符串調用,易遺漏出錯
      代碼可讀性 ? 接口明確定義服務端可調用的客戶端方法,通信契約清晰 ? 方法名散落在 SendAsync("MethodName") 中,不易維護
      單元測試 ? 可輕松 Mock 客戶端接口,便于測試 Hub 邏輯 ? 需模擬字符串發送邏輯,測試復雜且脆弱

      4.鑒權

      1.安裝 NuGet 包(如果還沒加)

      dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
      

      2.標準的.netCore集成鑒權

      builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
          .AddJwtBearer(options =>
          {
              options.TokenValidationParameters = new TokenValidationParameters
              {
                  //.....
              };
      
              // SignalR 要求在 WebSocket 模式下從查詢字符串傳遞 token
              options.Events = new JwtBearerEvents
              {
                  OnMessageReceived = context =>
                  {
                      var accessToken = context.Request.Query["access_token"];
      
                      // 如果是 SignalR 長連接,且路徑是 /chatHub,則從查詢字符串讀取 token
                      var path = context.HttpContext.Request.Path;
                      if (!string.IsNullOrEmpty(accessToken) &&
                          path.StartsWithSegments("/PushMsgHub")) // 你的 Hub 路徑
                      {
                          context.Token = accessToken;
                      }
                      return Task.CompletedTask;
                  }
              };
          });
      

      3.然后再hub上使用 Authorize 標記

      [Authorize]
      public class MsgPushHub : Hub<IPushMessageHubAsync>
      {
          public async Task SendMessage(string message)
          {
              await Clients.All.ReceiveMessage(message);
          }
      }
      

      3.遇到的問題

      1.上面的方式在單體系統是不會有問題的,但是如果服務實例負載均衡后開啟了多個實例,會存在回話丟失的問題

      image
      A)

      產生的原因其實根據上面的圖就能理解,因為SignalR 默認使用內存狀態存儲連接信息,每個實例獨立維護自己的客戶端連接表。

      解決方案:

      1.啟用粘性會話網關層根據ip和實例綁定,確保同一個客戶端的所有請求都路由到同一個后端實例。不需要額外組件(如 Redis),配置簡單,適合小規模部署。但是也存在一些缺點如下:

      • 單節點故障:如果該實例宕機,所有連接丟失。
      • 負載不均:某些實例可能連接過多。
      • 無法彈性伸縮:新增/刪除實例時,部分用戶會斷連。
      • 違反微服務無狀態原則。

      2.啟用底板機制橫向擴展,因為啟用橫向擴展后,所有會話狀態(連接、組、用戶映射)均存儲在外部,例如Redis中,而不是單機內存,但是需要引入外部依賴,增加復雜度,但是也有顯著的優點如下:

      • 真正的高可用和彈性伸縮。
      • 實例宕機不影響整體服務(其他實例可接管)。
      • 支持動態擴縮容。
      • 符合云原生架構。

      1.集成SignalR.StackExchangeRedis

      1.先安裝擴展庫

      dotnet add package Microsoft.AspNetCore.SignalR.StackExchangeRedis
      

      2.在注冊服務時加入擴展的代碼

      builder.Services.AddSignalR()
      .AddStackExchangeRedis("redis-connection-string", options =>
      {
          options.Configuration.ChannelPrefix = "SignalR"; // 可選:命名空間前綴
      });
      
      對比項 粘性會話 背板機制
      架構模式 有狀態 無狀態
      擴展性 差(受限于單實例容量) 好(可水平擴展)
      可用性 差(實例宕機即斷連) 好(故障轉移)
      部署復雜度 中(需 Redis)
      消息一致性 依賴路由 通過中間件保證
      推薦 ? 不推薦用于生產 ? 推薦
      適用場景 小型項目、測試環境 生產環境、微服務、云部署
      posted on 2025-09-20 08:29  yuyuyui  閱讀(73)  評論(0)    收藏  舉報

      主站蜘蛛池模板: 亚洲 卡通 欧美 制服 中文 | 精品中文人妻中文字幕| 欧美v国产v亚洲v日韩九九| 亚洲第一福利网站在线观看| 人妻少妇| 老师扒下内裤让我爽了一夜| 国产v亚洲v天堂a无码| 国产欧美久久一区二区| 久久一夜天堂av一区二区| 国产精品av免费观看| 不卡国产一区二区三区| 国产成本人片无码免费| 伊人久久大香线蕉AV网禁呦 | 岛国岛国免费v片在线观看| 自拍视频在线观看成人| 日本一区二区三区在线播放| 日韩一本不卡一区二区三区| 国产愉拍91九色国产愉拍| 午夜免费福利小电影| 97精品人妻系列无码人妻| 野外做受三级视频| 国产精品久久久久久妇女| 白嫩人妻精品一二三四区| 国产三级精品三级在线区| 国产亚洲999精品AA片在线爽| 少妇无套内谢免费视频| 成人欧美日韩一区二区三区| 国产精品一区二区国产主播| 封丘县| 一级女性全黄久久生活片| 国产资源精品中文字幕| 乱码精品一区二区三区| 国产男女黄视频在线观看| 国内不卡的一区二区三区| 国产一区二区日韩在线| 亚洲老熟女一区二区三区 | 久久亚洲精品中文字幕波多野结衣| 久久精品无码鲁网中文电影| 和田市| 国产精品女生自拍第一区| 成人看的污污超级黄网站免费|