本文主要目的在於實現一個後臺心跳廣播包,所有連接到 SignalR 的客戶端,通過訂閱心跳包廣播頻道,能夠自動收到伺服器發送的心跳廣播 ...
什麼是後臺主機
在之前的 Asp.NETCore 輕鬆學系列中,曾經介紹過一個輕量級服務主機 IHostedService ,利用 IHostedService 可以輕鬆的實現一個系統級別的後臺服務,該服務跟隨系統啟動和停止;同時,其使用非同步載入和相容註入的特性,可以很好的實現業務的擴展和隔離。
IHostedService 有一個預設的實現基類 Microsoft.Extensions.Hosting.BackgroundService,我們僅需要繼承 BackgroundService 即可實現後臺主機。
本文主要目的在於實現一個後臺心跳廣播包,所有連接到 SignalR 的客戶端,通過訂閱心跳包廣播頻道,能夠自動收到伺服器發送的心跳廣播
廣播協議定義
public interface IHeartbeat
{
Task HeartbeatAsync(int data);
}
上面定義 了一個介面 IHeartbeat,該介面有一個非同步的方法 HeartbeatAsync,主要就是心跳的定義,先不管它怎麼使用,我們繼續往下
定義一個泛型的 Hub
public class WeChatHub : Hub<IHeartbeat>
{
public void Send(ChatMessage body)
{
Clients.All.RecvAsync(body);
}
public override Task OnConnectedAsync()
{
Console.WriteLine("游客[{0}]進入了聊天室", this.Context.ConnectionId);
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
Console.WriteLine("游客[{0}]離開了聊天室", this.Context.ConnectionId);
return base.OnDisconnectedAsync(exception);
}
}
上面定義了一個SignalR通信管理對象 WeChatHub ,其繼承字泛型的 Hub
上面的這段話比較繞口,其實我也覺得不太好理解,簡單來說就是客戶端要偵聽一個和 SignalR 服務端定義的相同的頻道,才可以收到廣播。
定義後臺服務主機
在定義好 SignalR 的通信協議後,接下來要做的就是實現一個後臺服務主機,也就是 IHostedService 的實現。根據 Asp.Net Core 輕鬆學-基於微服務的後臺任務調度管理器 中的提示,我們不需要實現這個介面,只需要繼承 Microsoft.Extensions.Hosting.BackgroundService 即可
實現代碼
public class WeChatHubWorker : BackgroundService
{
private readonly IHubContext<WeChatHub, IHeartbeat> heartbeat;
public WeChatHubWorker(IHubContext<WeChatHub, IHeartbeat> heartbeat)
{
this.heartbeat = heartbeat;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await this.heartbeat.Clients.All.HeartbeatAsync(0);
await Task.Delay(3000);
Console.WriteLine("heartbeat");
}
}
}
上面的代碼比較簡單,首先定義了 IHubContext<WeChatHub, IHeartbeat> 對象 heartbeat,然後在構造函數中通過註入的方式將其實例化,緊接著在 ExecuteAsync(CancellationToken stoppingToken) 方法中,執行了一個無限迴圈的操作,每 3000 毫秒給所有 SignalR 客戶端發送一個內容為 0 的心跳包。
服務註入
實現了後臺主機後,需要將其註入到 Asp.NETCore 的管道中
// 添加服務主機隨主機啟動
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR();
services.AddHostedService<WeChatHubWorker>();
...
}
// 配置 SignalR 偵聽的 Uri 地址
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseSignalR(routes =>
{
routes.MapHub<WeChatHub>("/wechatHub");
});
...
}
通過服務註入,我們就完成了服務端的實現,下麵看看 JavaScript 的實現。
JavaScript 客戶端的實現
var connection = new signalR.HubConnectionBuilder()
.withUrl("/wechatHub")
.build();
connection.on("RecvAsync", function (data) {
var li = document.createElement("li");
li = $(li).text(data.userName + ":" + data.content);
$("#msgList").append(li);
});
connection.on("HeartbeatAsync", (data) => {
console.log(data);
});
connection.start()
.then(function () {
console.log("客戶端已連接");
}).catch(function (err) {
console.log(err);
});
上面的代碼,如果有看過前兩章的同學,應該是非常熟悉的,這裡就不多解釋了;但是,由於本次 SignalR 服務端的 Hub 實現不太一樣,所以,這裡還是要解釋一下。
上面的代碼分別偵聽了兩個通道 connection.on("RecvAsync",...) 和 connection.on("HeartbeatAsync",...) ,這兩個通道對應服務端的 IHeartbeat 的定義的成員名稱,然後在 Callback 中的參數,也需要和 IHeartbeat 定義的一樣,保證可以完整接收服務端推送的消息。
- RecvAsync 通道用來接收和發送客戶端的聊天消息(主動/被動)
- HeartbeatAsync 通道用來接收來自服務端推送的心跳廣播(被動)
運行演示
運行服務端,分別打開兩個客戶端,同時觀察服務端和客戶端的心跳輸出
紅圈處就是心跳廣播的內容:0。
使用兩個客戶端分別發送聊天消息
紅圈處就是聊天消息。
擴展
如果需要開通多個通道的話怎麼辦呢,聰明的你一定想到了,就是增加 IHeartbeat 的定義,然後在客戶端訂閱該頻道即可。
示例代碼下載
https://github.com/lianggx/Examples/tree/master/SignalR/Ron.SignalRLesson3