前もって明確にするために、この質問は.Net Core SignalRに関するものであり、以前のバージョンではありません。
新しいSignalRには、IISの背後にあるWebSocketに問題があります(Chrome/Win7/IIS Expressで動作させることができません)。代わりに、Server Sent Events(SSE)を使用しています。ただし、問題は、約2分後にタイムアウトすると、接続状態が2から3になることです。自動再接続が削除されました(以前のバージョンでは、とにかくうまく機能していなかったようです)。
クライアントがタイムアウトするのを防ぐために、ハートビートタイマーを今すぐ実装したいと思います。30秒ごとのティックでうまくいく可能性があります。
11月10日更新
これで、サーバー側のHeartbeatを実装できました。これは、基本的にRicardo Peresの https://weblogs.asp.net/ricardoperes/signalr-in-asp-net-core から取得したものです。
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を送信するだけです)、およびクライアントメソッド名の両方が異なる可能性があります。
以下のコメントを参照してください。タイマーコールバックは使用しないでください。これを行うために、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が一時停止された後(ブラウザのコンピュータがスリープ状態になった、接続が失われた、Wi-Fiが変更されたなど)
少なくとも接続が切断されるまで、正しく機能するクライアント側のハートビートを実装しました。
public async Task HeartBeatTock() => await Task.CompletedTask;
クライアント:
var heartBeatTockTimer; function sendHeartBeatTock(){connection.invoke( "HeartBeatTock"); } connection.start()。then(args => {heartBeatTockTimer = setInterval(sendHeartBeatTock、10000);});
ブラウザが一時停止した後IOたとえば、invokeメソッドは例外をスローします-非同期であるため、単純なtry/catchではキャッチできません。HeartBeatTockに対して実行しようとしたのは何かでしたlike(擬似コード):
_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()メソッドを公開しているように見えますが、そこで自分の考えを適切に実装する方法がわかりません。もう1つの理由は、新しい接続を開始するには、「connection.on( "send" ...)」のようなすべてのサーバー呼び出しを再実装する必要があるためです。これはばかげているように見えます。
再接続するクライアントを適切に実装する方法に関するヒントをいただければ幸いです。
私は今、実用的な解決策を持っています(これまでのところChromeとFFでテスト済み))。このようなもので、私はここに私の解決策を投稿しています:
ハートビート-「ティック」メッセージ(サーバーがクライアントに定期的にpingを実行する)は、上記の質問で説明されています。クライアント(「Tock」部分)には、次のものがあります。
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);
});
}
ExternalUseの answer ..に基づくクライアントバージョン.
import * as signalR from '@aspnet/signalr'
import _ from 'lodash'
var connection = null;
var sendHandlers = [];
var addListener = f => sendHandlers.Push(f);
function registerSignalRConnection() {
if (connection !== null) {
connection.stop().catch(err => console.log(err));
}
connection = new signalR.HubConnectionBuilder()
.withUrl('myHub')
.build();
connection.on("Heartbeat", serverTime =>
console.log("Server heartbeat: " + serverTime));
connection.on("Send", data =>
_.each(sendHandlers, value => value(data)));
connection.start()
.catch(exception =>
console.log("Error connecting", exception, connection));
}
registerSignalRConnection();
setInterval(() =>
connection.invoke("HeartBeatTock")
.then(() => console.log("Client heatbeat."))
.catch(err => {
registerSignalRConnection();
}), 10 * 1000);
export { addListener };