在后台主機中托管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 ,其中,泛型類型指定為 IHeartbeat 接口,這里的泛型即表示 SignalR 的客戶端,其代表一種通信協議包,SignalR 的客戶端(各種類型的)如果希望收到 IHeartbeat 定義的消息(方法實現),必須偵聽 IHeartbeat 的定義(方法名稱和參數)

上面的這段話比較繞口,其實我也覺得不太好理解,簡單來說就是客戶端要偵聽一個和 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM