Best practice per ricollegare il client .NET SignalR 2.0 all'hub del server


86

Sto usando SignalR 2.0 con il client .NET in un'applicazione mobile che deve gestire vari tipi di disconnessioni. A volte il client SignalR si riconnette automaticamente e talvolta deve essere ricollegato direttamente chiamando di HubConnection.Start()nuovo.

Dal momento che SignalR si ricollega automaticamente alcune volte, mi chiedo se mi manca una funzione o un'impostazione di configurazione?

Qual è il modo migliore per configurare un client che si ricolleghi automaticamente?


Ho visto esempi di javascript che gestiscono l' Closed()evento e quindi si connettono dopo n secondi. C'è qualche approccio consigliato?

Ho letto la documentazione e diversi articoli sulla durata delle connessioni SignalR, ma non sono ancora chiaro come gestire la riconnessione del client.

Risposte:


71

Finalmente l'ho capito. Ecco cosa ho imparato dall'inizio di questa domanda:

Background: stiamo creando un'app iOS usando Xamarin / Monotouch e il client .NET SignalR 2.0.3. Stiamo usando i protocolli SignalR predefiniti e sembra che stia usando SSE invece dei socket web. Non sono ancora sicuro se sia possibile utilizzare i socket Web con Xamarin / Monotouch. Tutto è ospitato utilizzando i siti Web di Azure.

Avevamo bisogno che l'app si riconnettesse rapidamente al nostro server SignalR, ma continuavamo ad avere problemi in cui la connessione non si ricollegava da sola o la riconnessione richiedeva esattamente 30 secondi (a causa di un timeout del protocollo sottostante).

C'erano tre scenari per i quali abbiamo finito per testare:

Scenario A: connessione la prima volta che l'app è stata caricata. Questo ha funzionato perfettamente dal primo giorno. La connessione viene completata in meno di 0,25 secondi anche su connessioni mobili 3G. (supponendo che la radio sia già accesa)

Scenario B: riconnessione al server SignalR dopo che l'app è rimasta inattiva / chiusa per 30 secondi. In questo scenario, il client SignalR alla fine si riconnetterà al server da solo senza alcun lavoro speciale, ma sembra attendere esattamente 30 secondi prima di tentare di riconnettersi. (troppo lento per la nostra app)

Durante questo periodo di attesa di 30 secondi, abbiamo provato a chiamare HubConnection.Start () che non ha avuto alcun effetto. E anche la chiamata a HubConnection.Stop () richiede 30 secondi. Ho trovato un bug correlato sul sito SignalR che sembra essere stato risolto , ma abbiamo ancora lo stesso problema nella v2.0.3.

Scenario C: riconnessione al server SignalR dopo che l'app è rimasta inattiva / chiusa per 120 secondi o più. In questo scenario, il protocollo di trasporto SignalR è già scaduto, quindi il client non si riconnette mai automaticamente. Questo spiega perché il client a volte, ma non sempre, si ricollegava da solo. La buona notizia è che la chiamata a HubConnection.Start () funziona quasi istantaneamente come lo scenario A.

Quindi mi ci è voluto un po 'per rendermi conto che le condizioni di riconnessione erano diverse a seconda che l'app fosse chiusa per 30 secondi rispetto a 120+ secondi. E sebbene i log di traccia di SignalR illuminino cosa sta succedendo con il protocollo sottostante, non credo che ci sia un modo per gestire gli eventi a livello di trasporto nel codice. (l'evento Closed () si attiva dopo 30 secondi nello scenario B, istantaneamente nello scenario C; la proprietà State dice "Connesso" durante questi periodi di attesa di riconnessione; nessun altro evento o metodo rilevante)

Soluzione: la soluzione è ovvia. Non stiamo aspettando che SignalR esegua la sua magia di riconnessione. Invece, quando l'app viene attivata o quando viene ripristinata la connessione di rete del telefono, stiamo semplicemente ripulendo gli eventi e de-referenziando HubConnection (non possiamo eliminarlo perché ci vogliono 30 secondi, si spera che la garbage collection se ne occuperà ) e creando una nuova istanza. Ora tutto funziona alla grande. Per qualche ragione, ho pensato che dovremmo riutilizzare una connessione persistente e riconnetterci invece di creare solo una nuova istanza.


5
Saresti disposto a pubblicare del codice? Sono solo curioso di sapere come l'hai strutturato. Sto usando Signalr in un'app di chat da un PCL anche in un'app Xamarin. Funziona davvero alla grande tranne che non riesco a far funzionare la magia di riconnessione dopo che il telefono è stato spento e riacceso. Giuro che l'IT Crowd ha detto che era tutto quello che dovevo fare.
Timothy Lee Russell

1
Ciao Ender2050, ho notato che una volta che il dispositivo Android si è disconnesso dal server, non riconnettersi mai più.Quindi avevo implementato un allarme che viene eseguito ogni 5 minuti e controlla la connessione del segnaleR con l'hub del server.Su evento tick di allarme devo controllare se l'oggetto di connessione è nullo o connectionId è vuoto, quindi ha stabilito di nuovo la connessione, ma non funziona bene. L'utente deve terminare l'app e riaprirla di nuovo. Ho usato java-client per Android e C # .Net per servire hub. Cerchi il tuo aiuto per risolvere questo problema.
jignesh

1
Cordiali saluti, Mono non ha socket web. Questo è il motivo per cui le tue app Xamarin usano sempre SSE. Puoi scrivere un client della console. Se lo esegui su Mono, utilizzerà SSE. Se lo esegui su Windows (almeno Windows 8 perché anche 7 non supporta i web socket) utilizzerà i web socket.
daramasala

@ Ender2050 Puoi espandere la tua soluzione con alcuni esempi di codice?
mbx-mbx

Stiamo riscontrando problemi di riconnessione con SignalR Hub (libreria SignalR versione 2.2.2) dall'app Android che utilizza la "libreria client Java SignalR" e l'app iOS che utilizza la "libreria SignalR Object C". Le librerie client su entrambe le piattaforme non sono state aggiornate da tempo. Penso che il problema sia dovuto all'incompatibilità del protocollo SignalR tra client e server.
Nadim Hossain Sonet

44

L'impostazione di un timer sull'evento disconnesso per tentare automaticamente la riconnessione è l'unico metodo di cui sono a conoscenza.

In javascript è fatto così:

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

Questo è l'approccio consigliato nella documentazione:

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


1
un suggerimento: assicurati di eseguire tutte le funzionalità di avvio complete sin dall'inizio, altrimenti ad esempio ti riconnetteresti agli hub.
MikeBaz - MSFT

1
Ho scoperto che con il client .NET, se ti iscrivi all'evento Closed prima di chiamare hub.Start (), se si verifica un errore di connessione inizialmente, viene chiamato il gestore dell'evento Closed e prova a chiamare di nuovo hub.Start () , facendo sì che l'hub.Start () originale non venga mai completato. La mia soluzione è stata quella di iscriversi a Closed solo dopo che Start () ha avuto esito positivo e di annullare l'iscrizione a Closed immediatamente nella richiamata.
Oran Dennison

3
@MikeBaz Penso che intendi ricollegarti ai gruppi
Simon_Weaver

1
@KingOfHypocrites Mi sono iscritto reconnectingall'evento, che viene attivato quando l'hub perde la connessione e imposta quella variabile (ad esempio shouldReconnect) su true. Quindi ho adattato il tuo esempio per controllare quella variabile. Sembra carino.
Alisson

2
Ho fatto un numero casuale compreso tra 10 e 60 secondi. Abbiamo troppi client per mettere solo 5 secondi, faremmo DDoS noi stessi. $ .connection.hub.disconnected (function () {setTimeout (function () {$ .connection.hub.start ();}, (Math.floor (Math.random () * 50) + 10) * 1000); });
Brain2000

17

Poiché l'OP richiede un client .NET (un'implementazione winform di seguito),

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

Ho trovato in SignalR 2.3.0 che se aspettavo la connessione nell'evento Closed () a volte non si connetteva. Tuttavia, se chiamassi un Wait () manuale sull'evento con un timeout, ad esempio 10 secondi, chiamerebbe automaticamente Closed () ancora e ancora ogni 10 secondi, quindi la riconnessione funzionerebbe.
Brain2000

0

Aggiungo qualche aggiornamento per la risposta ibubi . Forse qualcuno ne ha bisogno. Ho scoperto che in alcuni casi il segnale non genera un evento "chiuso" dopo la riconnessione interrotta. L'ho risolto utilizzando l'evento "StateChanged". Metodo di connessione al server 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;
        }

Metodo per la riconnessione:

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

Metodo di infiniti tentativi di connessione al server (anch'io utilizzo questo metodo per creare la prima connessione):

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

-3

Potresti provare a richiamare il metodo del server dal tuo Android prima dell'inizio dello stato di riconnessione per evitare problemi di riconnessione magica.

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);
    });
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.