在實際業務中,當後臺數據發生變化,客戶端能夠實時的收到通知,而不是由用戶主動的進行頁面刷新才能查看,這將是一個非常人性化的設計。有沒有那麼一種場景,後臺數據明明已經發生變化了,前臺卻因為沒有及時刷新,而導致頁面顯示的數據與實際存在差異,從而造成錯誤的判斷。那麼如何才能在後臺數據變更時及時通知客戶端呢... ...
在實際業務中,當後臺數據發生變化,客戶端能夠實時的收到通知,而不是由用戶主動的進行頁面刷新才能查看,這將是一個非常人性化的設計。有沒有那麼一種場景,後臺數據明明已經發生變化了,前臺卻因為沒有及時刷新,而導致頁面顯示的數據與實際存在差異,從而造成錯誤的判斷。那麼如何才能在後臺數據變更時及時通知客戶端呢?本文以一個簡單的動態折線圖示例,簡述如何通過ASP.NET SignalR實現後臺通知功能,僅供學習分享使用,如有不足之處,還請指正。
什麼是SignalR?
ASP.NET SignalR 是一個面向 ASP.NET 開發人員的庫,可簡化將實時 web 功能添加到應用程式的過程。 實時 web 功能是讓伺服器代碼將內容推送到連接的客戶端立即可用,而不是讓伺服器等待客戶端請求新數據的能力。
SignalR做了什麼?
傳統HTTP採用的是大家熟知的“拉模式”,即客戶端發出的每次請求,服務端都是被動處理。此場景下客戶端是老大,很顯然只有一方主動,操作與處理起來就沒那麼完美。
為了能讓服務端也能主動,html5的出現讓這種變得可能,大家知道html5中有兩種主動模式。第一種叫做websockect,WebSockets是Html5提供的新的API,可以在Web網頁與伺服器端間建立Socket連接,它是基於tcp模式的雙工通訊。還有一種叫做SSE,也就是客戶端來訂閱伺服器的一種事件模型。
在html5出來之前,如果要做到伺服器主動,我們只能採用變相的longpool和iframe流勉強實現。
SignalR對上面四種方案進行了高度的封裝,也就是說signalR會在這四種技術中根據瀏覽器和伺服器設置採取最優的一種模式。
封裝與集成
對於.NET開發者的福音,.NET平臺為我們提供了一種簡潔高效智能的實時信息交互技術->SignalR,它集成了上述數種技術,並能根據配置自動或手動選擇其最佳應用。
SignalR用途
SignalR 提供了一個簡單的 API,用於創建伺服器到客戶端遠程過程調用 (RPC) ,該調用客戶端瀏覽器 (和其他客戶端平臺中的 JavaScript 函數) 伺服器端 .NET 代碼。 SignalR 還包括用於連接管理的 API (,例如連接和斷開連接事件) ,以及分組連接。
雖然聊天通常被用作示例,但你可以做更多的事情。每當用戶刷新網頁以查看新數據時,或者該網頁實施 Ajax 長輪詢以檢索新數據時,它都是使用 SignalR 的候選者。SignalR 還支持需要從伺服器進行高頻更新的全新類型的應用,例如實時游戲。
官方網址和源碼
官方網址:https://dotnet.microsoft.com/zh-cn/apps/aspnet/signalr
微軟API文檔:https://learn.microsoft.com/zh-cn/aspnet/signalr/overview/getting-started/introduction-to-signalr
GitHub網址:https://github.com/SignalR
示例截圖
本示例主要實現一個動態折線圖功能,主要分為服務端和客戶端兩部分,示例如下所示:
服務端項目創建
1. 創建一個Web服務端程式(以ASP.NET WebApi為例),預設情況下SignalR已經作為項目框架的一部分而存在,所以不需要安裝,直接使用即可。通過項目--依賴性--框架--Microsoft.AspNetCore.App可以查看
2. 創建ChatHub,繼承Hub基類,作為後臺連接管理的中心
1 using Microsoft.AspNetCore.SignalR; 2 3 namespace DemoSignalR.Server.Chat 4 { 5 public class ChatHub : Hub 6 { 7 #region 連接和斷開連接 8 9 public override async Task OnConnectedAsync() 10 { 11 var connId = Context.ConnectionId; 12 Console.WriteLine($"{connId} 已連接"); 13 await base.OnConnectedAsync(); 14 } 15 16 public void StartNotify(string type) 17 { 18 if (type == "1") 19 { 20 21 } 22 else if (type == "2") 23 { 24 25 }; 26 27 } 28 29 public override async Task OnDisconnectedAsync(Exception ex) 30 { 31 //如果斷開連接 32 var connId = Context.ConnectionId; 33 Console.WriteLine($"{connId} 已斷開"); 34 await base.OnDisconnectedAsync(ex); 35 } 36 37 #endregion 38 } 39 }
SignalR服務端業務集成
在實際業務中,存在各種需要後臺通知的功能,根據不同的業務,可以採用不同的通知觸發方式:
1. 在調用介面時觸發後臺通知
1 using DemoSignalR.Server.Chat; 2 using Microsoft.AspNetCore.Mvc; 3 using Microsoft.AspNetCore.SignalR; 4 5 namespace DemoSignalR.Server.Controllers 6 { 7 [ApiController] 8 [Route("[controller]")] 9 public class TestWebApiController : ControllerBase 10 { 11 12 13 private readonly ILogger<TestWebApiController> _logger; 14 15 private IHubContext<ChatHub> _context; 16 17 public TestWebApiController(ILogger<TestWebApiController> logger, IHubContext<ChatHub> context) 18 { 19 _logger = logger; 20 _context = context; 21 } 22 23 [HttpGet] 24 public void GetTestA(string Name) 25 { 26 string info = $"當前接收到的信息為:{Name},{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}"; 27 _context.Clients.All.SendAsync("", info); 28 } 29 30 31 } 32 }
2. 定時器迴圈通知
1 using Microsoft.AspNetCore.SignalR; 2 using System.Timers; 3 4 namespace DemoSignalR.Server.Chat 5 { 6 public class TestChatInfo 7 { 8 private IHubContext<ChatHub> _context; 9 10 private System.Timers.Timer _timer; 11 12 private readonly static object locker = new object();//鎖對象 13 14 public static TestChatInfo instance;//當前實例 15 16 private readonly ILogger _logger; 17 18 private int _index = 0; 19 20 private TestChatInfo(IHubContext<ChatHub> _context, ILogger _logger) 21 { 22 this._context = _context; 23 this._logger = _logger; 24 //定義定時器 25 _timer = new System.Timers.Timer(100); 26 _timer.AutoReset = true; 27 _timer.Enabled = true; 28 _timer.Elapsed += Timer_Elapsed; 29 _timer.Start(); 30 } 31 32 private void Timer_Elapsed(object? sender, ElapsedEventArgs e) 33 { 34 //業務邏輯判斷 35 var obj = new TestValue(); 36 obj.Index = this._index; 37 obj.Value = DateTime.Now.Millisecond % 100; 38 _context.Clients.All.SendAsync("RefreshInfos", obj); 39 this._index++; 40 } 41 42 /// <summary> 43 /// 註冊,即初始化單例實例 44 /// </summary> 45 /// <param name="context"></param> 46 /// <param name="reviewTaskService"></param> 47 /// <param name="sysParamService"></param> 48 public static void Register(IHubContext<ChatHub> context, ILogger logger) 49 { 50 lock (locker) 51 { 52 if (instance == null) 53 { 54 lock (locker) 55 { 56 instance = new TestChatInfo(context, logger); 57 } 58 } 59 } 60 } 61 62 } 63 64 public class TestValue 65 { 66 private int index; 67 public int Index { get { return index; } set { index = value; } } 68 69 private float value; 70 public float Value { get { return value; } set { this.value = value; } } 71 } 72 }
SignalR服務端配置
SignalR服務端配置主要分成三步:
1. 添加SignalR服務
2. 映射SignalR路由
3. 註冊單例後臺通知服務(如果其他方式,可省略)
1 using DemoSignalR.Server.Chat; 2 using Microsoft.AspNetCore.SignalR; 3 4 var builder = WebApplication.CreateBuilder(args); 5 6 // Add services to the container. 7 8 builder.Services.AddControllers(); 9 // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle 10 builder.Services.AddEndpointsApiExplorer(); 11 builder.Services.AddSwaggerGen(); 12 //1.添加SignalR服務 13 builder.Services.AddSignalR(); 14 builder.Services.AddLogging(logging => logging.AddConsole()); 15 16 var app = builder.Build(); 17 18 // Configure the HTTP request pipeline. 19 if (app.Environment.IsDevelopment()) 20 { 21 app.UseSwagger(); 22 app.UseSwaggerUI(); 23 } 24 app.UseRouting(); 25 app.UseHttpsRedirection(); 26 27 app.UseAuthorization(); 28 29 30 //在Use中註冊單例實例 31 app.Use(async (context, next) => 32 { 33 var hubContext = context.RequestServices.GetRequiredService<IHubContext<ChatHub>>(); 34 //var logger = context.RequestServices.GetRequiredService<ILogger>(); 35 TestChatInfo.Register(hubContext, null);//調用靜態方法註冊 36 if (next != null) 37 { 38 await next.Invoke(); 39 } 40 }); 41 42 app.MapControllers(); 43 44 //2.映射路由 45 app.UseEndpoints(endpoints => { 46 endpoints.MapHub<ChatHub>("/chat"); 47 }); 48 49 app.Run();
客戶端項目創建
1. 創建WPF項目
2. 通過NuGet包管理器安裝SignalR客戶端
3. 創建SignalR狀態管理,主要管理SignalR的連接,關閉,重連等操作。
1 using Microsoft.AspNetCore.SignalR.Client; 2 using System; 3 using System.Collections.Generic; 4 using System.Configuration; 5 using System.Linq; 6 using System.Text; 7 using System.Threading.Tasks; 8 9 namespace DemoSignalR.Client 10 { 11 internal class SignalRClient 12 { 13 private readonly HubConnection hubConnection; 14 15 public HubConnection HubConnection 16 { 17 get { return hubConnection; } 18 } 19 20 public SignalRClient() 21 { 22 var server = ConfigurationManager.AppSettings["Server"].ToString(); 23 hubConnection = new HubConnectionBuilder().WithUrl($"{server}/chat").WithAutomaticReconnect().Build(); 24 hubConnection.KeepAliveInterval = TimeSpan.FromSeconds(5); 25 } 26 27 public virtual void Listen() 28 { 29 30 } 31 32 public async void Start() 33 { 34 try 35 { 36 await hubConnection.StartAsync(); 37 38 } 39 catch (Exception e) 40 { 41 42 } 43 } 44 } 45 }
客戶端業務邏輯處理
1. 根據不同的業務邏輯分別監聽不同的通知事件。
2. 示例詳見源碼
1 using Microsoft.AspNetCore.SignalR.Client; 2 using System; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Text; 6 using System.Threading.Tasks; 7 8 namespace DemoSignalR.Client 9 { 10 internal class TestSignalRClient : SignalRClient 11 { 12 public Action<object> RefreshInfos; 13 14 public Action<string> Reconnected; 15 16 public TestSignalRClient() : base() 17 { 18 } 19 20 public override void Listen() 21 { 22 HubConnection.On<object>("RefreshInfos", (obj) => 23 { 24 // 25 if (obj != null) 26 { 27 Console.WriteLine("收到數據"); 28 //發送消息 29 if (RefreshInfos != null) 30 { 31 RefreshInfos(obj); 32 } 33 } 34 }); 35 HubConnection.Reconnected += HubConnection_Reconnected; 36 } 37 38 private Task HubConnection_Reconnected(string arg) 39 { 40 return Task.Run(() => 41 { 42 if (Reconnected != null) 43 { 44 Reconnected(arg); 45 } 46 }); 47 } 48 49 public virtual void StartNotify(string condition) 50 { 51 HubConnection.SendAsync("StartNotify", condition); 52 Console.WriteLine($"開始通過知{condition}"); 53 } 54 } 55 }
SignalR需要註意事項
你不會實例化 Hub 類或從伺服器上自己的代碼調用其方法;由 SignalR Hubs 管道為你完成的所有操作。 SignalR 每次需要處理中心操作(例如客戶端連接、斷開連接或向伺服器發出方法調用時)時,SignalR 都會創建 Hub 類的新實例。
由於 Hub 類的實例是暫時性的,因此無法使用它們來維護從一個方法調用到下一個方法的狀態。 每當伺服器從客戶端收到方法調用時,中心類的新實例都會處理消息。 若要通過多個連接和方法調用來維護狀態,請使用一些其他方法(例如資料庫)或 Hub 類上的靜態變數,或者不派生自 Hub的其他類。 如果在記憶體中保留數據,請使用 Hub 類上的靜態變數等方法,則應用域回收時數據將丟失。
如果要從在 Hub 類外部運行的代碼將消息發送到客戶端,則無法通過實例化 Hub 類實例來執行此操作,但可以通過獲取對 Hub 類的 SignalR 上下文對象的引用來執行此操作。
註意:ChatHub每次調用都是一個新的實例,所以不可以有私有屬性或變數,不可以保存對像的值,所以如果需要記錄一些持久保存的值,則可以採用靜態變數,或者中心以外的對象。
關於源碼
本示例中相關源碼,已上傳至gitee(碼雲),鏈接如下:
https://gitee.com/ahsiang/demo-signal-r
備註
以上就是WPF+ASP.NET SignalR實現動態折線圖的全部內容,關於SignalR的應用,這隻是一個簡單的入門示例,希望可以拋磚引玉,一起學習,共同進步。學習編程,從關註【老碼識途】開始!!!
作者:小六公子
出處:http://www.cnblogs.com/hsiang/
本文版權歸作者和博客園共有,寫文不易,支持原創,歡迎轉載【點贊】,轉載請保留此段聲明,且在文章頁面明顯位置給出原文連接,謝謝。
關註個人公眾號,定時同步更新技術及職場文章