Asp.net-Core-Mvc

.Net Core SignalR - 連接超時 - 心跳定時器 - 連接狀態更改處理

  • March 6, 2019

只是要預先明確,這個問題是關於 .Net Core SignalR,而不是以前的版本。

新的 SignalR 與 IIS 後面的 WebSockets 存在問題(我無法讓它們在 Chrome/Win7/IIS express 上工作)。因此,我使用的是伺服器發送事件 (SSE)。但是,問題是大約 2 分鐘後超時,連接狀態從 2 變為 3。自動重新連接已被刪除(顯然在以前的版本中它並不能很好地工作)。

我現在想實現一個心跳計時器來阻止客戶端超時,每 30 秒一次的滴答聲就可以完成這項工作。

11 月 10 日更新

我現在已經設法實現伺服器端 Heartbeat,基本上取自 Ricardo Peres 的https://weblogs.asp.net/ricardoperes/signalr-in-asp-net-core

  • 在 startup.cs 中,添加到public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider)
   app.UseSignalR(routes =>  
   {  
       routes.MapHub<TheHubClass>("signalr");  
   });

   TimerCallback SignalRHeartBeat = async (x) => {   
   await serviceProvider.GetService<IHubContext<TheHubClass>>().Clients.All.InvokeAsync("Heartbeat", DateTime.Now); };
   var timer = new Timer(SignalRHeartBeat).Change(TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(30));            
  • 集線器類

對於 HubClass,我添加了public async Task HeartBeat(DateTime now) => await Clients.All.InvokeAsync("Heartbeat", now);

顯然,計時器、正在發送的數據(我只是發送一個 DateTime)和客戶端方法名稱都可以不同。

更新.Net Core 2.1+

請參閱下面的評論;不應再使用計時器回調。我現在已經實現了一個 IHostedService(或者更確切地說是抽象的 BackgroundService)來做到這一點:

public class HeartBeat : BackgroundService
{
   private readonly IHubContext<SignalRHub> _hubContext;
   public HeartBeat(IHubContext<SignalRHub> hubContext)
   {
       _hubContext = hubContext;
   }
   protected override async Task ExecuteAsync(CancellationToken stoppingToken)
   {
       while (!stoppingToken.IsCancellationRequested)
       {
           await _hubContext.Clients.All.SendAsync("Heartbeat", DateTime.Now, stoppingToken);
           await Task.Delay(30000, stoppingToken);
       }            
   }
}

在您的啟動課程中,將其連接到之後services.AddSignalR();

services.AddHostedService<HeartBeat>();
  • 客戶
   var connection = new signalR.HubConnection("/signalr", { transport: signalR.TransportType.ServerSentEvents });
   connection.on("Heartbeat", serverTime => { console.log(serverTime); });

初始問題的剩餘部分

剩下的是如何正確地重新連接客戶端,例如在 IO 暫停之後(瀏覽器的電腦進入睡眠狀態、失去連接、更改 Wifi 或其他)

我已經實現了一個正常工作的客戶端心跳,至少在連接中斷之前:

  • 集線器類:public async Task HeartBeatTock() => await Task.CompletedTask;
  • 客戶:

var heartBeatTockTimer; 函式 sendHeartBeatTock() { connection.invoke(“HeartBeatTock”); } connection.start().then(args => { heartBeatTockTimer = setInterval(sendHeartBeatTock, 10000); });

例如,在瀏覽器掛起 IO 之後,invoke 方法會拋出一個異常——它不能被簡單的 try/catch 擷取,因為它是非同步的。我試圖為我的 HeartBeatTock 做的事情類似於(虛擬碼):

function sendHeartBeatTock
   try connection.invoke("HeartbeatTock)
   catch exception
        try connection.stop()
        catch exception (and ignore it)
        finally
             connection = new HubConnection().start()
   repeat try connection.invoke("HeartbeatTock")
   catch exception
       log("restart did not work")
       clearInterval(heartBeatTockTimer)
       informUserToRefreshBrowser()

現在,由於幾個原因,這不起作用。由於非同步執行,invoke 在程式碼塊執行後拋出異常。它看起來好像公開了一個 .catch() 方法,但我不確定如何在那裡正確地實現我的想法。另一個原因是,開始一個新的連接需要我重新實現所有的伺服器呼叫,比如“connection.on(”send"…)——這看起來很傻。

任何有關如何正確實現重新連接客戶端的提示將不勝感激。

我現在有一個可行的解決方案(到目前為止在 Chrome 和 FF 中測試過)。為了激勵您想出更好的東西,或者在自己想出類似的東西時為您節省一點時間,我在這裡發布我的解決方案:

上述問題中描述了 Heartbeat-“Tick”消息(伺服器定期 ping 客戶端)。客戶端(“Tock”部分)現在有:

  • 註冊連接的函式,以便可以重複回調方法(connection.on());否則在重新啟動“新 HubConnection”後它們會失去
  • 註冊 TockTimer 的函式
  • 和一個實際發送 Tock ping 的函式

tock 方法在發送時擷取錯誤,並嘗試啟動新連接。由於計時器一直在執行,我正在註冊一個新連接,然後只需坐下來等待下一次呼叫。將客戶端放在一起:

// keeps the connection object
var connection = null;
// stores the ID from SetInterval
var heartBeatTockTimer = 0;
// how often should I "tock" the server
var heartBeatTockTimerSeconds = 10;
// how often should I retry after connection loss?
var maxRetryAttempt = 5;
// the retry should wait less long then the TockTimer, or calls may overlap
var retryWaitSeconds = heartBeatTockTimerSeconds / 2;
// how many retry attempts did we have?
var currentRetryAttempt = 0;
// helper function to wait a few seconds
$.wait = function(miliseconds) {
   var defer = $.Deferred();
   setTimeout(function() { defer.resolve(); }, miliseconds);
   return defer;
};
// first routine start of the connection
registerSignalRConnection();
function registerSignalRConnection() {
   ++currentRetryAttempt;
   if (currentRetryAttempt > maxRetryAttempt) {
       console.log("Clearing registerHeartBeatTockTimer");
       clearInterval(heartBeatTockTimer);
       heartBeatTockTimer = 0;
       throw "Retry attempts exceeded.";
   }
   if (connection !== null) {
       console.log("registerSignalRConnection was not null", connection);
       connection.stop().catch(err => console.log(err));
   }
   console.log("Creating new connection");
   connection = new signalR.HubConnection("/signalr", { transport: signalR.TransportType.ServerSentEvents });
   connection.on("Heartbeat", serverTime => { console.log(serverTime); });
   connection.start().then(() => {
       console.log("Connection started, starting timer.");
       registerHeartBeatTockTimer();
   }).catch(exception => {
       console.log("Error connecting", exception, connection);
   });
}
function registerHeartBeatTockTimer() {
   // make sure we're registered only once
   if (heartBeatTockTimer !== 0) return;
   console.log("Registering registerHeartBeatTockTimer");
   if (connection !== null)
       heartBeatTockTimer = setInterval(sendHeartBeatTock, heartBeatTockTimerSeconds * 1000);
   else
       console.log("Connection didn't allow registry");
}

function sendHeartBeatTock() {
   console.log("Standard attempt HeartBeatTock");
   connection.invoke("HeartBeatTock").then(() => { 
        console.log("HeartbeatTock worked.") })
        .catch(err => {
            console.log("HeartbeatTock Standard Error", err);
            $.wait(retryWaitSeconds * 1000).then(function() {
                console.log("executing attempt #" + currentRetryAttempt.toString());
            registerSignalRConnection();
        });
        console.log("Current retry attempt: ", currentRetryAttempt);
    });
}

這是在 IIS 後面執行 SignalR Core 時出現的問題。IIS 將在 2 分鐘後關閉空閒連接。長期計劃是添加保持活動消息,作為副作用,這將阻止 IIS 關閉連接。要暫時解決此問題,您可以:

  • 定期向客戶端發送消息
  • 按照此處所述更改 IIS 中的空閒超時設置
  • 如果連接關閉,則在客戶端重新啟動連接
  • 使用不同的傳輸方式(例如長輪詢,因為您不能在 IIS 後面的 Win7/Win2008 R2 上使用 webSockets)

引用自:https://stackoverflow.com/questions/47208844