SignalR の接続有効期間イベントについて理解し、処理する

警告

このドキュメントは、最新版の SignalR は対象としていません。 ASP.NET Core SignalR を参照してください。

この記事では、処理できる SignalR の接続、再接続、切断の各イベントと、構成できるタイムアウトとキープアライブの各設定の概要について説明します。

この記事では、SignalR と接続の有効期間イベントについてある程度の知識があることを前提としています。 SignalR の概要については、「SignalR 入門」を参照してください。 接続の有効期間イベントの一覧については、次のリソースを参照してください。

このトピックで使用されるソフトウェアのバージョン

このトピックの以前のバージョン

SignalR の以前のバージョンについては、「SignalR の以前のバージョン」を参照してください。

質問とコメント

このチュートリアルの感想、改善できる内容に関するフィードバックをページの下部にあるコメントでお寄せください。 チュートリアルに直接関連しない質問がある場合は、ASP.NET SignalR フォーラムまたは StackOverflow.com に投稿できます。

概要

この記事は、次のセクションで構成されています。

API リファレンス トピックへのリンクは、API の .NET 4.5 バージョンへのリンクです。 .NET 4 を使用している場合は、.NET 4 バージョンの API トピックを参照してください。

接続の有効期間の用語とシナリオ

SignalR ハブの OnReconnected イベント ハンドラーは、特定のクライアントに対して OnConnected の直後に実行できますが、OnDisconnected の後には実行できません。 切断なしで再接続できる理由は、SignalR で "接続" という単語がいくつかの意味で使用されているためです。

SignalR 接続、トランスポート接続、物理的な接続

この記事では、"SignalR 接続"、"トランスポート接続"、"物理的な接続" を区別します。

  • SignalR 接続とは、SignalR API によって維持され、接続 ID によって一意に識別される、クライアントとサーバー URL の間の論理リレーションシップを指します。 このリレーションシップに関するデータは SignalR によって維持され、トランスポート接続を確立するために使用されます。 クライアントが Stop メソッドを呼び出すか、SignalR が失われたトランスポート接続の再確立を試みている間にタイムアウト制限に達すると、リレーションシップが終了し、SignalR によってデータが破棄されます。
  • トランスポート接続とは、WebSocket、サーバー送信イベント、永続的フレーム、ロング ポーリングの 4 つのトランスポート API のいずれかによって維持される、クライアントとサーバー間の論理関係を指します。 SignalR はトランスポート API を使用してトランスポート接続を作成し、トランスポート API はトランスポート接続を作成するために物理ネットワーク接続の存在に依存します。 トランスポート接続は、SignalR によって終了されるか、物理的な接続が切断されたことがトランスポート API によって検出されると、終了します。
  • 物理的な接続とは、クライアント コンピューターとサーバー コンピューター間の通信を容易にする物理ネットワーク リンク (ワイヤ、ワイヤレス信号、ルーターなど) を指します。 トランスポート接続を確立するには物理的な接続が存在する必要があり、SignalR 接続を確立するにはトランスポート接続を確立する必要があります。 ただし、物理的な接続を切断しても、トランスポート接続または SignalR 接続がすぐに終了するとは限りません。これについては、このトピックで後述します。

次の図では、SignalR 接続は Hubs API および PersistentConnection API SignalR レイヤーで表され、トランスポート接続はトランスポート レイヤーで表され、物理的な接続はサーバーとクライアント間の線で表されています。

SignalR architecture diagram

SignalR クライアントでメソッドを Start 呼び出すと、サーバーへの物理的な接続を確立するために必要なすべての情報を SignalR クライアント コードに提供することになります。 SignalR クライアント コードは、この情報を使用して HTTP 要求を行い、4 つのトランスポート メソッドのいずれかを使用する物理的な接続を確立します。 トランスポート接続が失敗した場合、またはサーバーに障害が発生した場合でも、クライアントには同じ SignalR URL への新しいトランスポート接続を自動的に再確立するために必要な情報が残っているため、SignalR 接続はすぐには切断されません。 このシナリオでは、ユーザー アプリケーションからの介入は行われず、SignalR クライアント コードによって新しいトランスポート接続が確立されても、新しい SignalR 接続は開始されません。 SignalR 接続の継続性は、Start メソッドを呼び出したときに作成される接続 ID が変更されないという事実に反映されています。

トランスポート接続が失われた後に自動的に再確立されると、ハブ上の OnReconnected イベント ハンドラーが実行されます。 OnDisconnected イベント ハンドラーは、SignalR 接続の終了時に実行されます。 SignalR 接続は、次のいずれかの方法で終了できます。

  • クライアントが Stop メソッドを呼び出すと、停止メッセージがサーバーに送信され、クライアントとサーバーの両方が SignalR 接続を直ちに終了します。
  • クライアントとサーバー間の接続が失われると、クライアントは再接続を試み、サーバーはクライアントの再接続を待機します。 再接続の試行が失敗し、切断タイムアウト期間が終了すると、クライアントとサーバーの両方が SignalR 接続を終了します。 クライアントは再接続の試行を停止し、サーバーは SignalR 接続の表現を破棄します。
  • クライアントが Stop メソッドを呼び出さずに実行を停止した場合、サーバーはクライアントの再接続を待機してから、切断タイムアウト期間の経過後に SignalR 接続を終了します。
  • サーバーの実行が停止すると、クライアントは再接続 (トランスポート接続の再作成) を試行し、切断タイムアウト期間の経過後に SignalR 接続を終了します。

接続に問題がなく、ユーザー アプリケーションが Stop メソッドを呼び出して SignalR 接続を終了すると、SignalR 接続とトランスポート接続はほぼ同時に開始および終了します。 以降のセクションでは、他のシナリオについて詳しく説明します。

トランスポート切断のシナリオ

物理的な接続が遅いか、接続が中断される可能性があります。 中断の長さなどの要因によっては、トランスポート接続が切断される可能性があります。 その後、SignalR はトランスポート接続の再確立を試みます。 トランスポート接続 API によって中断が検出され、トランスポート接続が切断されることがあり、SignalR はその接続が失われたことをすぐに認識します。 他のシナリオでは、トランスポート接続 API も SignalR も、接続が失われたことをすぐには認識しません。 ロング ポーリングを除くすべてのトランスポートについて、SignalR クライアントは keepalive と呼ばれる関数を使用して、トランスポート API が検出できない接続の喪失をチェックします。 ロングポーリング接続の詳細については、このトピックで後述する「タイムアウトとキープアライブの設定」を参照してください。

接続が非アクティブな場合、サーバーは定期的にキープアライブ パケットをクライアントに送信します。 この記事の執筆時点では、既定の頻度は 10 秒ごとです。 クライアントはこれらのパケットをリッスンして、接続に問題があるかどうかを確認できます。 キープアライブ パケットを期待通りに受信できなかった場合、クライアントはしばらくして、速度低下や中断などの接続の問題があると見なします。 長い時間が経過してもキープアライブが受信されない場合、クライアントは接続が切断されたと見なし、再接続の試行を開始します。

次の図は、物理的な接続に問題があり、それがトランスポート API によってすぐに認識されない典型的なシナリオで発生するクライアントとサーバーのイベントを示しています。 この図は、次の状況に適用されます。

  • トランスポートは、WebSocket、永続的フレーム、サーバー送信イベントのいずれかです。
  • 物理ネットワーク接続には、さまざまな期間の中断が発生します。
  • トランスポート API では中断が認識されないため、SignalR はそれらを検出するためにキープアライブ機能に依存します。

Transport disconnections

クライアントが再接続モードに入っても、切断タイムアウト制限内にトランスポート接続を確立できない場合、サーバーは SignalR 接続を終了します。 これが発生すると、サーバーはハブの OnDisconnected メソッドを実行し、クライアントが後で接続できた場合に備えて、クライアントに送信する切断メッセージをキューに入れます。 その後、クライアントが再接続すると、切断コマンドを受け取り、Stop メソッドを呼び出します。 このシナリオでは、クライアントが再接続するときに OnReconnected は実行されず、クライアントが Stop を呼び出すときに OnDisconnected は実行されません。 次の図にこのシナリオを示します。

Transport disruptions - server timeout

クライアントで発生する可能性がある SignalR 接続の有効期間イベントは次のとおりです。

  • ConnectionSlow クライアント イベント。

    最後のメッセージまたはキープアライブ ping を受信してから、あらかじめ設定された割合のキープアライブ タイムアウト期間が経過したときに発生します。 既定のキープアライブ タイムアウト警告期間は、キープアライブ タイムアウトの 2/3 です。 キープアライブ タイムアウトは 20 秒であるため、警告は約 13 秒で発生します。

    既定では、サーバーは 10 秒ごとにキープアライブ ping を送信し、クライアントは約 2 秒ごとにキープアライブ ping をチェックします (キープアライブ タイムアウト値とキープアライブ タイムアウト警告値の差の 3 分の 1)。

    トランスポート API が切断を認識すると、キープアライブ タイムアウト警告期間が経過する前に、SignalR に切断が通知される可能性があります。 その場合、ConnectionSlow イベントは発生せず、SignalR は直接 Reconnecting イベントに進みます。

  • Reconnecting クライアント イベント。

    (a) トランスポート API が接続の喪失を検出した場合、または (b) 最後のメッセージまたはキープアライブ ping を受信してからキープアライブ タイムアウト期間が経過した場合に発生します。 SignalR クライアント コードが再接続の試行を開始します。 トランスポート接続が失われたときにアプリケーションで何らかのアクションを実行する場合は、このイベントを処理できます。 既定のキープアライブ タイムアウト期間は、現在 20 秒です。

    SignalR が再接続モードにあるときにクライアント コードで Hub メソッドを呼び出そうとすると、SignalR はコマンドの送信を試みます。 ほとんどの場合、このような試行は失敗しますが、状況によっては成功する可能性があります。 サーバー送信イベント、永続的フレーム、ロング ポーリングの各トランスポートに対して、SignalR は 2 つの通信チャネルを使用します。1 つはクライアントがメッセージを送信するために使用し、1 つはクライアントがメッセージを受信するために使用します。 受信に使用されるチャネルは永続的に開いているチャネルであり、物理的な接続が中断されると閉じられます。 送信に使用されるチャネルは使用可能なままであるため、物理的な接続が回復すると、受信チャネルが再確立される前にクライアントからサーバーへのメソッド呼び出しが成功する可能性があります。 SignalR が受信に使用するチャネルを再度開くまで、戻り値は受信されません。

  • Reconnected クライアント イベント。

    トランスポート接続が再確立されたときに発生します。 ハブの OnReconnected イベント ハンドラーが実行されます。

  • Closed クライアント イベント (JavaScript では disconnected イベント)。

    SignalR クライアント コードがトランスポート接続を失った後に再接続しようとしている間に、切断タイムアウト期間が経過すると発生します。 既定の切断タイムアウトは 30 秒です。 (このイベントは、Stop メソッドが呼び出されたために接続が終了したときにも発生します)。

トランスポート API によって検出されず、サーバーからのキープアライブ ping の受信がキープアライブ タイムアウト警告期間よりも長く遅延しないトランスポート接続の中断では、接続の有効期間イベントが発生しない可能性があります。

一部のネットワーク環境では、アイドル状態の接続が意図的に閉じられることがあります。キープアライブ パケットのもう 1 つの機能は、SignalR 接続が使用中であることをこれらのネットワークに知らせて、これを防ぐことです。 極端な場合には、キープアライブ ping の既定の頻度では、接続が閉じられるのを防ぐには不十分な場合があります。 その場合は、キープアライブ ping をより頻繁に送信するように構成できます。 詳細については、このトピックで後述する「タイムアウトとキープアライブの設定」を参照してください。

Note

重要: ここで説明するイベントの順序は保証されていません。 SignalR は、このスキームに従って予測可能な方法で接続の有効期間イベントを発生させるあらゆる試行を行いますが、ネットワーク イベントにはさまざまなバリエーションがあり、トランスポート API などの基になる通信フレームワークがそれらを処理する方法も数多くあります。 たとえば、クライアントが再接続するときに Reconnected イベントが発生しない場合や、接続の確立が失敗したときにサーバー上の OnConnected ハンドラーが実行される場合があります。 このトピックでは、特定の一般的な状況下で通常生じるであろう効果についてのみ説明します。

クライアント切断のシナリオ

ブラウザー クライアントでは、SignalR 接続を維持する SignalR クライアント コードが Web ページの JavaScript コンテキストで実行されます。 そのため、あるページから別のページに移動するときに SignalR 接続を終了する必要があり、複数のブラウザー ウィンドウまたはタブから接続する場合、複数の接続 ID を持つ複数の接続が存在することになります。 ユーザーがブラウザー ウィンドウまたはタブを閉じたり、新しいページに移動したり、ページを更新したりすると、SignalR クライアント コードによってそのブラウザー イベントが自動的に処理され、Stop メソッドが呼び出されるため、SignalR 接続は直ちに終了します。 これらのシナリオ、またはアプリケーションが Stop メソッドを呼び出す場合の任意のクライアント プラットフォームでは、OnDisconnected イベント ハンドラーがサーバー上で直ちに実行され、クライアントによって Closed イベント (JavaScript ではこのイベントは disconnected という名前が付けられます) が発生します。

クライアント アプリケーションまたはそれを実行しているコンピューターがクラッシュしたりスリープ状態になったりした場合 (ユーザーがノート PC を閉じた場合など)、サーバーには何が起こったのかが通知されません。 サーバーの認識では、クライアントの喪失は接続の中断が原因である可能性があり、クライアントは再接続を試みている可能性があります。 したがって、これらのシナリオでは、サーバーはクライアントに再接続の機会を与えるために待機し、切断タイムアウト期間 (既定では約 30 秒) が経過するまで OnDisconnected は実行されません。 次の図にこのシナリオを示します。

Client computer failure

サーバー切断のシナリオ

サーバーがオフラインになると (再起動、障害、アプリ ドメインのリサイクルなど)、その結果は接続の切断と同様になるか、トランスポート API と SignalR がサーバーの喪失を即座に認識して、SignalR が ConnectionSlow イベントを発生させずに再接続を開始する可能性があります。 クライアントが再接続モードになり、切断タイムアウト期間が経過する前にサーバーが回復または再起動するか、新しいサーバーがオンラインになると、クライアントは復元されたサーバーまたは新しいサーバーに再接続します。 その場合、SignalR 接続はクライアントで続行され、Reconnected イベントが発生します。 最初のサーバーでは OnDisconnected は実行されず、新しいサーバーでは OnReconnected が実行されますが、以前にそのサーバー上のそのクライアントに対して OnConnected が実行されたことはありません (再起動またはアプリ ドメインのリサイクル後にクライアントが同じサーバーに再接続しても、サーバーには再起動時に以前の接続アクティビティの記憶がないため、効果は同じです)。次の図は、トランスポート API が失われた接続をすぐに認識すると想定しているため、ConnectionSlow イベントは発生しません。

Server failure and reconnection

切断タイムアウト期間内にサーバーが使用できなくなった場合、SignalR 接続は終了します。 このシナリオでは、Closed イベント (JavaScript クライアントでは disconnected) がクライアントで発生しますが、OnDisconnected はサーバーで呼び出されません。 次の図は、トランスポート API が失われた接続を認識しないことを想定しています。そのため、SignalR キープアライブ機能によってそれが検出され、ConnectionSlow イベントが発生します。

Server failure and timeout

タイムアウトとキープアライブの設定

ConnectionTimeoutDisconnectTimeoutKeepAlive の各既定値はほとんどのシナリオに適していますが、お使いの環境で特別なニーズがある場合は変更できます。 たとえば、5 秒間アイドル状態の接続が切断されるネットワーク環境の場合、キープアライブ値を減らす必要がある場合があります。

ConnectionTimeout

この設定は、トランスポート接続を開いたままにし、応答を待ってから接続を閉じて新しい接続を開くまでの時間を表します。 既定値は 110 秒です。

この設定は、キープアライブ機能が無効になっている場合にのみ適用されます。通常は、ロング ポーリング トランスポートにのみ適用されます。 次の図は、この設定がロング ポーリング トランスポート接続に与える影響を示しています。

Long polling transport connection

DisconnectTimeout

この設定は、トランスポート接続が失われた後、Disconnected イベントを発生させるまでに待機する時間を表します。 既定値は 30 秒です。 DisconnectTimeout を設定すると、KeepAlive は自動的に DisconnectTimeout 値の 1/3 に設定されます。

KeepAlive

この設定は、アイドル状態の接続を介してキープアライブ パケットが送信されるまでの待機時間を表します。 既定値は 10 秒です。 この値は、DisconnectTimeout 値の 1/3 を超えてはなりません。

DisconnectTimeoutKeepAlive の両方を設定する場合は、DisconnectTimeout の後に KeepAlive を設定します。 そうしないと、DisconnectTimeout によって KeepAlive がタイムアウト値の 1/3 に自動的に設定されるときに、KeepAlive 設定が上書きされます。

キープアライブ機能を無効にする場合は、KeepAlive を null に設定します。 ロング ポーリング トランスポートでは、キープアライブ機能が自動的に無効になります。

タイムアウトとキープアライブの設定を変更する方法

これらの設定の既定値を変更するには、次の例に示すように、Global.asax ファイルの Application_Start で設定します。 サンプル コードに示されている値は、既定値と同じです。

protected void Application_Start(object sender, EventArgs e)
{
    // Make long polling connections wait a maximum of 110 seconds for a
    // response. When that time expires, trigger a timeout command and
    // make the client reconnect.
    GlobalHost.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(110);
    
    // Wait a maximum of 30 seconds after a transport connection is lost
    // before raising the Disconnected event to terminate the SignalR connection.
    GlobalHost.Configuration.DisconnectTimeout = TimeSpan.FromSeconds(30);
    
    // For transports other than long polling, send a keepalive packet every
    // 10 seconds. 
    // This value must be no more than 1/3 of the DisconnectTimeout value.
    GlobalHost.Configuration.KeepAlive = TimeSpan.FromSeconds(10);
    
    RouteTable.Routes.MapHubs();
}

切断についてユーザーに通知する方法

一部のアプリケーションでは、接続に問題がある場合にユーザーにメッセージを表示する必要がある場合があります。 これを行う方法とタイミングには、いくつかのオプションがあります。 次のコード サンプルは、生成されたプロキシを使用する JavaScript クライアント用です。

  • connectionSlow イベントを処理して、SignalR が接続の問題を認識したらすぐに、再接続モードに入る前にメッセージを表示します。

    $.connection.hub.connectionSlow(function() {
        notifyUserOfConnectionProblem(); // Your function to notify user.
    });
    
  • reconnecting イベントを処理して、SignalR が切断を認識して再接続モードに入るときにメッセージを表示します。

    $.connection.hub.reconnecting(function() {
        notifyUserOfTryingToReconnect(); // Your function to notify user.
    });
    
  • disconnected イベントを処理して、再接続の試行がタイムアウトしたときにメッセージを表示します。このシナリオでは、サーバーとの接続を再度確立する唯一の方法は、Start メソッドを呼び出して SignalR 接続を再開することです。これにより、新しい接続 ID が作成されます。 次のコード サンプルでは、フラグを使用して、Stop メソッドを呼び出して SignalR 接続が正常に終了した後ではなく、再接続タイムアウト後にのみ通知を発行するようにします。

    var tryingToReconnect = false;
    
    $.connection.hub.reconnecting(function() {
        tryingToReconnect = true;
    });
    
    $.connection.hub.reconnected(function() {
        tryingToReconnect = false;
    });
    
    $.connection.hub.disconnected(function() {
        if(tryingToReconnect) {
            notifyUserOfDisconnect(); // Your function to notify user.
        }
    });
    

継続的に再接続する方法

一部のアプリケーションでは、接続が失われ、再接続の試行がタイムアウトになった後に、接続を自動的に再確立することが必要になる場合があります。これを行うには、Closed イベント ハンドラー (JavaScript クライアントの disconnected イベント ハンドラー) から Start メソッドを呼び出します。 サーバーまたは物理的な接続が利用できない場合に、Start の呼び出しを頻繁に行わないように、一定時間待ってから呼び出すことをお勧めします。 次のコード サンプルは、生成されたプロキシを使用する JavaScript クライアント用です。

$.connection.hub.disconnected(function() {
   setTimeout(function() {
       $.connection.hub.start();
   }, 5000); // Restart connection after 5 seconds.
});

モバイル クライアントで注意すべき潜在的な問題は、サーバーまたは物理的な接続が利用できないときに継続的に再接続を試行すると、不必要なバッテリの消費が発生する可能性があることです。

サーバー コードでクライアントを切断する方法

SignalR バージョン 2 には、クライアントを切断するための組み込みサーバー API はありません。 将来的にはこの機能を追加する予定です。 現在の SignalR リリースでは、クライアントをサーバーから切断する最も簡単な方法は、クライアントに disconnect メソッドを実装し、サーバーからそのメソッドを呼び出すことです。 次のコード サンプルは、生成されたプロキシを使用する JavaScript クライアントの disconnect メソッドを示しています。

var myHubProxy = $.connection.myHub
myHubProxy.client.stopClient = function() {
    $.connection.hub.stop();
};

警告

セキュリティ - クライアントを切断するこの方法も、提案された組み込み API も、悪意のあるコードを実行しているハッキングされたクライアントのシナリオには対処できません。クライアントが再接続したり、ハッキングされたコードによって stopClient メソッドが削除されたり、その動作が変更されたりする可能性があるからです。 ステートフル サービス拒否 (DOS) 保護を実装する適切な場所は、フレームワークやサーバー レイヤーではなく、フロントエンド インフラストラクチャです。

切断の理由の検出

SignalR 2.1 では、クライアントがタイムアウトではなく意図的に切断されたかどうかを示すオーバーロードがサーバー OnDisconnect イベントに追加されます。接続がクライアントによって明示的に閉じられた場合、StopCalled パラメータは true になります。 JavaScript では、サーバー エラーによりクライアントが切断された場合、エラー情報は $.connection.hub.lastError としてクライアントに渡されます。

C# サーバー コード: stopCalled パラメータ

public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled)
{
    if (stopCalled)
    {
        Console.WriteLine(String.Format("Client {0} explicitly closed the connection.", Context.ConnectionId));
    }
    else
    {
        Console.WriteLine(String.Format("Client {0} timed out .", Context.ConnectionId));
    }
            
    return base.OnDisconnected(stopCalled);
}

JavaScript クライアント コード: disconnect イベントの lastError にアクセスします。

$.connection.hub.disconnected(function () {
    if ($.connection.hub.lastError) 
        { alert("Disconnected. Reason: " +  $.connection.hub.lastError.message); }
});