SignalR 2.0.NETクライアントをサーバーハブに再接続するためのベストプラクティス


86

さまざまな種類の切断を処理する必要があるモバイルアプリケーションの.NETクライアントでSignalR2.0を使用しています。SignalRクライアントが自動的に再接続する場合もあれば、再度呼び出すことによって直接再接続する必要がある場合もありHubConnection.Start()ます。

SignalRは時々魔法のように自動再接続するので、機能や構成設定が不足していないかどうか疑問に思っていますか?

自動的に再接続するクライアントを設定するための最良の方法は何ですか?


Closed()イベントを処理し、n秒後に接続するJavaScriptの例を見てきました。推奨されるアプローチはありますか?

SignalR接続の存続期間に関するドキュメントといくつかの記事を読みましたが、クライアントの再接続を処理する方法がまだわかりません。


コードを共有できますか?
Pxaml

回答:


71

私はついにこれを理解しました。この質問を始めてから私が学んだことは次のとおりです。

背景: Xamarin / Monotouchと.NETSignalR2.0.3クライアントを使用してiOSアプリを構築しています。デフォルトのSignalRプロトコルを使用していますが、Webソケットの代わりにSSEを使用しているようです。Xamarin / MonotouchでWebソケットを使用できるかどうかはまだわかりません。すべてがAzureWebサイトを使用してホストされます。

SignalRサーバーにすばやく再接続するにはアプリが必要でしたが、接続がそれ自体で再接続されない、または再接続に正確に30秒かかった(基になるプロトコルタイムアウトのため)という問題が引き続き発生しました。

最終的にテストしたシナリオは3つありました。

シナリオA-アプリが最初に読み込まれたときに接続します。これは初日から完璧に機能しました。3Gモバイル接続でも、接続は0.25秒未満で完了します。(ラジオがすでにオンになっていると仮定)

シナリオB-アプリが30秒間アイドル状態/クローズされた後、SignalRサーバーに再接続します。このシナリオでは、SignalRクライアントは、特別な作業を行わなくても、最終的にはサーバーに自動的に再接続しますが、再接続を試みる前に正確に30秒待機しているようです。(私たちのアプリには遅すぎます)

この30秒間の待機期間中に、HubConnection.Start()を呼び出してみましたが、効果はありませんでした。また、HubConnection.Stop()の呼び出しにも30秒かかります。SignalRサイトで解決されたように見える関連するバグを見つけましが、v2.0.3でも同じ問題が発生しています。

シナリオC-アプリが120秒以上アイドル状態/クローズされた後、SignalRサーバーに再接続します。このシナリオでは、SignalRトランスポートプロトコルがすでにタイムアウトしているため、クライアントが自動的に再接続することはありません。これは、クライアントが時々、しかし常にではないが、それ自体で再接続していた理由を説明しています。幸いなことに、HubConnection.Start()の呼び出しは、シナリオAのようにほぼ瞬時に機能します。

そのため、アプリを30秒以上閉じたかどうかによって再接続条件が異なることに気付くのに少し時間がかかりました。また、SignalRトレースログは、基盤となるプロトコルで何が起こっているかを明らかにしますが、コードでトランスポートレベルのイベントを処理する方法はないと思います。(Closed()イベントは、シナリオBでは30秒後に発生し、シナリオCでは即座に発生します。Stateプロパティは、これらの再接続待機期間中に「接続済み」と表示します。他の関連するイベントやメソッドはありません)

解決策: 解決策は明らかです。SignalRが再接続の魔法を実行するのを待っていません。代わりに、アプリがアクティブ化されたとき、または電話のネットワーク接続が復元されたときに、イベントをクリーンアップしてHubConnectionの参照を解除するだけです(30秒かかるため、破棄できません。ガベージコレクションで処理されるといいのですが) )そして新しいインスタンスを作成します。今、すべてがうまく機能しています。何らかの理由で、新しいインスタンスを作成するだけでなく、永続的な接続を再利用して再接続する必要があると思いました。


5
コードを投稿していただけませんか?あなたがそれをどのように構成したかについてちょうど興味があります。XamarinアプリのPCL内からチャットアプリでもSignalrを使用しています。電話をオフにしてから再びオンにした後、再接続の魔法が機能しないように見えることを除いて、それは本当に素晴らしい働きをします。ITクラウドが私がしなければならなかったのはそれだけだと言ったことを誓います。
ティモシーリーラッセル

1
こんにちはEnder2050、私はアンドロイドデバイスがサーバーから切断された後、二度と再接続しないことに気づきました.5分ごとに実行され、サーバーハブとのsignalR接続をチェックするアラームを実装しました。アラームティックイベントで、接続オブジェクトがヌルかどうかをチェックしますconnectionIdが空の場合、接続を再度確立しますが、これはうまく機能しません。ユーザーはアプリを強制終了して、再度開く必要があります。Androidにはjava-clientを使用し、サーブハブにはC#.Netを使用しました。この問題を解決するためにあなたの助けを探しています。
ジグネッシュ2015年

1
参考までに、MonoにはWebソケットがありません。これが、Xamarinアプリが常にSSEを使用する理由です。コンソールクライアントを作成できます。Monoで実行すると、SSEが使用されます。Windowsで実行する場合(7もWebソケットをサポートしていないため、少なくともWindows 8)、Webソケットを使用します。
daramasala 2016年

@ Ender2050いくつかのコード例を使用して、ソリューションを拡張できますか?
Magrangs 2016

「SignalRJavaクライアントライブラリ」を使用しているAndroidアプリと「SignalRObjectCライブラリ」を使用しているiOSアプリからSignalRHub(SignalRライブラリバージョン2.2.2)との再接続の問題が発生しています。両方のプラットフォームのクライアントライブラリは、しばらくの間更新されていません。この問題は、クライアントとサーバー間のSignalRプロトコルの非互換性が原因だと思います。
Nadim Hossain Sonet 2017年

44

切断されたイベントにタイマーを設定して自動的に再接続を試みることは、私が知っている唯一の方法です。

javascriptでは、次のように実行されます。

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

これは、ドキュメントで推奨されているアプローチです。

http://www.asp.net/signalr/overview/signalr-20/hubs-api/handling-connection-lifetime-events#clientdisconnect


1
1つのヒント-ハブに再接続する場合など、開始時に完全な開始機能を実行するようにしてください。
MikeBaz-MSFT 2014年

1
.NETクライアントでは、hub.Start()を呼び出す前にClosedイベントをサブスクライブすると、最初に接続に失敗した場合、Closedイベントハンドラーが呼び出され、hub.Start()を再度呼び出そうとすることがわかりました。 、元のhub.Start()が完了しない原因になります。私の解決策は、Start()が成功した後にのみClosedにサブスクライブし、コールバックですぐにClosedのサブスクライブを解除することでした。
Oran Dennison 2014

3
@MikeBazグループに再接続することを意味すると思います
Simon_Weaver 2015年

1
@KingOfHypocritesreconnectingイベントをサブスクライブしました。このイベントは、ハブが接続を失い、その変数(たとえばshouldReconnect)をtrueに設定すると発生します。そこで、その変数をチェックするように例を調整しました。良い感じ。
アリソン2017

2
私は10秒から60秒の間で乱数を出しました。クライアントが多すぎて5秒もかからないので、自分たちでDDoS攻撃を行います。$ .connection.hub.disconnected(function(){setTimeout(function(){$ .connection.hub.start();}、(Math.floor(Math.random()* 50)+ 10)* 1000); });
Brain2000 2018

17

OPが.NETクライアント(以下のwinform実装)を要求しているので、

private async Task<bool> ConnectToSignalRServer()
{
    bool connected = false;
    try
    {
        Connection = new HubConnection("server url");
        Hub = Connection.CreateHubProxy("MyHub");
        await Connection.Start();

        //See @Oran Dennison's comment on @KingOfHypocrites's answer
        if (Connection.State == ConnectionState.Connected)
        {
            connected = true;
            Connection.Closed += Connection_Closed;
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Error: {ex.Message}");
    }
    return connected;
}

private async void Connection_Closed()
{   // A global variable being set in "Form_closing" event 
    // of Form, check if form not closed explicitly to prevent a possible deadlock.
    if(!IsFormClosed) 
    {
        // specify a retry duration
        TimeSpan retryDuration = TimeSpan.FromSeconds(30);
        DateTime retryTill = DateTime.UtcNow.Add(retryDuration);

        while (DateTime.UtcNow < retryTill)
        {
            bool connected = await ConnectToSignalRServer();
            if (connected)
                return;
        }
        Console.WriteLine("Connection closed")
    }
}

SignalR 2.3.0で、Closed()イベントで接続を待機すると、接続されない場合があることがわかりました。ただし、10秒などのタイムアウトでイベントに対して手動のWait()を呼び出すと、10秒ごとにClosed()が自動的に呼び出され、再接続が機能します。
Brain2000 2018

0

ibubianswerのアップデートを追加します。誰かがそれを必要としているかもしれません。再接続が停止した後、Signalが「クローズ」イベントを起こさない場合があることがわかりました。イベント「StateChanged」を使用して解決しました。SignalRサーバーに接続する方法:

private async Task<bool> ConnectToSignalRServer()
        {
            bool connected = false;
            try
            {
                var connection = new HubConnection(ConnectionUrl);
                var proxy = connection.CreateHubProxy("CurrentData");
                await connection.Start();

                if (connection.State == ConnectionState.Connected)
                {
                    await proxy.Invoke("ConnectStation");

                    connection.Error += (ex) =>
                    {
                        Console.WriteLine("Connection error: " + ex.ToString());
                    };
                    connection.Closed += () =>
                    {
                        Console.WriteLine("Connection closed");
                    };
                    connection.StateChanged += Connection_StateChanged;
                    Console.WriteLine("Server for Current is started.");
                    connected = true;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}");
            }
            return connected;
        }

再接続の方法:

private async void Connection_StateChanged(StateChange obj)
        {
            if (obj.NewState == ConnectionState.Disconnected)
            {
                await RestartConnection();
            }
        }

サーバーへの接続を無限に試行する方法(また、最初の接続を作成するためにこの方法を使用します):

public async Task RestartConnection()
        {
            while (!ApplicationClosed)
            {
                bool connected = await ConnectToSignalRServer();
                if (connected)
                    return;
            }
        }

-3

魔法の再接続の問題を防ぐために、再接続状態が始まる前にAndroidからサーバーメソッドを呼び出そうとする場合があります。

SignalRハブC#

 public class MyHub : Hub
    {
        public void Ping()
        {
            //ping for android long polling
        }
 }

Androidの場合

private final int PING_INTERVAL = 10 * 1000;

private boolean isConnected = false;
private HubConnection connection;
private ClientTransport transport;
private HubProxy hubProxy;

private Handler handler = new Handler();
private Runnable ping = new Runnable() {
    @Override
    public void run() {
        if (isConnected) {
            hubProxy.invoke("ping");
            handler.postDelayed(ping, PING_INTERVAL);
        }
    }
};

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    System.setProperty("http.keepAlive", "false");

    .....
    .....

    connection.connected(new Runnable() {
        @Override
        public void run() {
            System.out.println("Connected");
            handler.postDelayed(ping, PING_INTERVAL);
    });
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.