Best Practice für die erneute Verbindung des SignalR 2.0 .NET-Clients mit dem Server-Hub


86

Ich verwende SignalR 2.0 mit dem .NET-Client in einer mobilen Anwendung, die verschiedene Arten von Verbindungsabbrüchen verarbeiten muss. Manchmal stellt der SignalR-Client die Verbindung automatisch wieder her - und manchmal muss er durch erneutes Aufrufen direkt HubConnection.Start()wieder verbunden werden.

Da sich SignalR manchmal auf magische Weise automatisch wieder verbindet, frage ich mich, ob mir eine Funktion oder Konfigurationseinstellung fehlt.

Was ist der beste Weg, um einen Client einzurichten, der sich automatisch wieder verbindet?


Ich habe Javascript-Beispiele gesehen, die das Closed()Ereignis behandeln und dann nach n Sekunden eine Verbindung herstellen. Gibt es einen empfohlenen Ansatz?

Ich habe die Dokumentation und mehrere Artikel über die Lebensdauer von SignalR-Verbindungen gelesen, bin mir aber immer noch nicht sicher, wie ich mit der erneuten Verbindung des Clients umgehen soll.


Können Sie Code teilen?
Pxaml

Antworten:


71

Ich habe das endlich herausgefunden. Folgendes habe ich gelernt, seit ich mit dieser Frage angefangen habe:

Hintergrund: Wir erstellen eine iOS-App mit Xamarin / Monotouch und dem .NET SignalR 2.0.3-Client. Wir verwenden die Standard-SignalR-Protokolle - und es scheint, dass SSE anstelle von Web-Sockets verwendet wird. Ich bin mir noch nicht sicher, ob es möglich ist, Web-Sockets mit Xamarin / Monotouch zu verwenden. Alles wird über Azure-Websites gehostet.

Wir brauchten die App, um schnell wieder eine Verbindung zu unserem SignalR-Server herzustellen, aber wir hatten immer wieder Probleme, bei denen die Verbindung nicht von selbst wiederhergestellt wurde - oder die erneute Verbindung dauerte genau 30 Sekunden (aufgrund eines zugrunde liegenden Protokoll-Timeouts).

Es gab drei Szenarien, auf die wir getestet haben:

Szenario A - Verbindung beim ersten Laden der App herstellen. Dies funktionierte vom ersten Tag an einwandfrei. Die Verbindung wird auch über mobile 3G-Verbindungen in weniger als 0,25 Sekunden hergestellt. (vorausgesetzt, das Radio ist bereits eingeschaltet)

Szenario B - Wiederverbindung mit dem SignalR-Server, nachdem die App 30 Sekunden lang inaktiv / geschlossen war. In diesem Szenario stellt der SignalR-Client möglicherweise ohne besondere Arbeit selbstständig wieder eine Verbindung zum Server her. Es scheint jedoch genau 30 Sekunden zu warten, bevor versucht wird, die Verbindung wiederherzustellen. (viel zu langsam für unsere App)

Während dieser Wartezeit von 30 Sekunden haben wir versucht, HubConnection.Start () aufzurufen, was keine Auswirkungen hatte. Das Aufrufen von HubConnection.Stop () dauert ebenfalls 30 Sekunden. Ich habe einen verwandten Fehler auf der SignalR-Site gefunden, der behoben zu sein scheint , aber wir haben immer noch das gleiche Problem in Version 2.0.3.

Szenario C - Wiederverbindung mit dem SignalR-Server, nachdem die App 120 Sekunden oder länger inaktiv / geschlossen war. In diesem Szenario ist das SignalR-Transportprotokoll bereits abgelaufen, sodass der Client die Verbindung nie automatisch wiederherstellt. Dies erklärt, warum der Client manchmal, aber nicht immer, die Verbindung von selbst wieder herstellte. Die gute Nachricht ist, dass das Aufrufen von HubConnection.Start () fast sofort wie in Szenario A funktioniert.

Es dauerte eine Weile, bis mir klar wurde, dass die Bedingungen für die erneute Verbindung unterschiedlich waren, je nachdem, ob die App 30 Sekunden lang oder mehr als 120 Sekunden lang geschlossen war. Und obwohl die SignalR-Ablaufverfolgungsprotokolle beleuchten, was mit dem zugrunde liegenden Protokoll vor sich geht, glaube ich nicht, dass es eine Möglichkeit gibt, die Ereignisse auf Transportebene im Code zu behandeln. (Das Closed () -Ereignis wird in Szenario B nach 30 Sekunden sofort in Szenario C ausgelöst. Die State-Eigenschaft sagt während dieser Wartezeiten für die erneute Verbindung "Verbunden". Keine anderen relevanten Ereignisse oder Methoden.)

Lösung: Die Lösung liegt auf der Hand. Wir warten nicht darauf, dass SignalR seine Wiederverbindungsmagie ausführt. Wenn die App aktiviert ist oder die Netzwerkverbindung des Telefons wiederhergestellt ist, bereinigen wir einfach die Ereignisse und referenzieren die HubConnection (kann nicht entsorgt werden, da dies 30 Sekunden dauert, hoffentlich wird die Speicherbereinigung dies beheben ) und Erstellen einer neuen Instanz. Jetzt funktioniert alles super. Aus irgendeinem Grund dachte ich, wir sollten eine dauerhafte Verbindung wiederverwenden und die Verbindung wiederherstellen, anstatt nur eine neue Instanz zu erstellen.


5
Würdest du bereit sein, einen Code zu posten? Ich bin nur neugierig, wie du es strukturiert hast. Ich verwende Signalr in einer Chat-App innerhalb einer PCL auch in einer Xamarin-App. Es funktioniert wirklich gut, außer dass ich die Magie der Wiederverbindung nicht zum Laufen bringen kann, nachdem das Telefon aus- und wieder eingeschaltet wurde. Ich schwöre, die IT-Crowd sagte, das sei alles, was ich tun musste.
Timothy Lee Russell

1
Hallo Ender2050, ich habe festgestellt, dass nach dem Trennen des Android-Geräts vom Server die Verbindung nie wieder hergestellt wurde. Daher hatte ich einen Alarm implementiert, der alle 5 Minuten ausgeführt wird und auf signalR-Verbindung mit dem Server-Hub überprüft wird. Bei einem Alarm-Tick-Ereignis habe ich überprüft, ob das Verbindungsobjekt null oder ist connectionId ist leer und stellt die Verbindung wieder her. Dies funktioniert jedoch nicht. Der Benutzer muss die App beenden und erneut öffnen. Ich habe Java-Client für Android und C # .Net für Serve Hub verwendet. Suchen Sie nach Ihrer Hilfe, um dieses Problem zu lösen.
Jignesh

1
Zu Ihrer Information, Mono hat keine Web-Sockets. Aus diesem Grund verwenden Ihre Xamarin-Apps immer SSE. Sie können einen Konsolenclient schreiben. Wenn Sie es auf Mono ausführen, wird SSE verwendet. Wenn Sie es unter Windows ausführen (mindestens Windows 8, da 7 auch keine Web-Sockets unterstützt), werden Web-Sockets verwendet.
Daramasala

@ Ender2050 Können Sie Ihre Lösung mit einigen Codebeispielen erweitern?
Magrangs

Wir haben ein Problem mit der erneuten Verbindung mit SignalR Hub (SignalR-Bibliothek Version 2.2.2) von der Android-App, die die "SignalR Java Client-Bibliothek" verwendet, und der iOS-App, die die "SignalR Object C-Bibliothek" verwendet. Die Client-Bibliotheken auf beiden Plattformen wurden seit einiger Zeit nicht mehr aktualisiert. Ich denke, das Problem liegt an der Inkompatibilität des SignalR-Protokolls zwischen Client und Server.
Nadim Hossain Sonet

43

Das Einstellen eines Timers für das nicht verbundene Ereignis, um automatisch zu versuchen, die Verbindung wiederherzustellen, ist die einzige mir bekannte Methode.

In Javascript wird es so gemacht:

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

Dies ist der empfohlene Ansatz in der Dokumentation:

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


1
Ein Hinweis: Stellen Sie sicher, dass Sie beim Start alle Funktionen für den vollständigen Start ausführen, da Sie sonst z. B. die Verbindung zu den Hubs wiederherstellen würden.
MikeBaz - MSFT

1
Ich habe festgestellt, dass mit dem .NET-Client, wenn Sie das Closed-Ereignis abonnieren, bevor Sie hub.Start () aufrufen, wenn zunächst keine Verbindung hergestellt werden kann, Ihr Closed-Ereignishandler aufgerufen wird und versucht, hub.Start () erneut aufzurufen Dies führt dazu, dass der ursprüngliche Hub.Start () niemals abgeschlossen wird. Meine Lösung bestand darin, Closed erst zu abonnieren, nachdem Start () erfolgreich war, und Closed sofort im Rückruf abzubestellen.
Oran Dennison

3
@ MikeBaz Ich denke, Sie meinen, sich wieder mit Gruppen zu verbinden
Simon_Weaver

1
@KingOfHypocrites Ich habe das reconnectingEreignis abonniert , das ausgelöst wird, sobald der Hub die Verbindung verliert, und diese Variable (z shouldReconnect. B. ) auf true gesetzt hat. Also habe ich Ihr Beispiel angepasst, um diese Variable zu überprüfen. Es sieht gut aus.
Alisson

2
Ich habe eine Zufallszahl zwischen 10 und 60 Sekunden gemacht. Wir haben zu viele Kunden, um nur 5 Sekunden zu setzen, wir würden DDoS selbst. $ .connection.hub.disconnected (function () {setTimeout (function () {$ .connection.hub.start ();}, (Math.floor (Math.random () * 50) + 10) * 1000); });
Brain2000

17

Da das OP nach einem fragt .NET-Client (eine Winform-Implementierung unten),

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")
    }
}

Ich habe in SignalR 2.3.0 festgestellt, dass wenn ich im Closed () -Ereignis auf die Verbindung gewartet habe, diese manchmal nicht verbunden werden kann. Wenn ich jedoch ein manuelles Wait () für das Ereignis mit einer Zeitüberschreitung von z. B. 10 Sekunden aufrufe, wird automatisch alle 10 Sekunden Closed () aufgerufen, und dann funktioniert die erneute Verbindung.
Brain2000

0

Ich füge ein Update für die Antwort von ibubi hinzu . Vielleicht braucht es jemand. Ich habe festgestellt, dass in einigen Fällen signalr kein "geschlossenes" Ereignis auslöst, nachdem die Wiederverbindung gestoppt wurde. Ich habe es mit dem Ereignis "StateChanged" gelöst. Methode, die eine Verbindung zum SignalR-Server herstellt:

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;
        }

Methode zum erneuten Verbinden:

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

Methode für endlose Versuche, eine Verbindung zum Server herzustellen (auch ich verwende diese Methode zum Herstellen der ersten Verbindung):

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

-3

Sie könnten versuchen, die Servermethode von Ihrem Android aus aufzurufen, bevor Sie den Status der erneuten Verbindung starten, um ein Problem mit der magischen erneuten Verbindung zu vermeiden.

SignalR Hub C #

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

In 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);
    });
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.