Práctica recomendada para volver a conectar el cliente .NET de SignalR 2.0 al concentrador del servidor

86

Estoy usando SignalR 2.0 con el cliente .NET en una aplicación móvil que necesita manejar varios tipos de desconexiones. A veces, el cliente de SignalR se vuelve a conectar automáticamente y, a veces, debe volver a conectarse directamente llamando de HubConnection.Start()nuevo.

Dado que SignalR se reconecta automáticamente por arte de magia algunas veces, me pregunto si me falta una función o configuración de configuración.

¿Cuál es la mejor manera de configurar un cliente que se vuelva a conectar automáticamente?


He visto ejemplos de javascript que manejan el Closed()evento y luego se conectan después de n segundos. ¿Existe algún enfoque recomendado?

He leído la documentación y varios artículos sobre la vida útil de las conexiones de SignalR, pero todavía no tengo claro cómo manejar la reconexión del cliente.

Ender2050
fuente

Respuestas:

71

Finalmente me di cuenta de esto. Esto es lo que he aprendido desde que comencé esta pregunta:

Antecedentes: estamos creando una aplicación para iOS con Xamarin / Monotouch y el cliente .NET SignalR 2.0.3. Estamos usando los protocolos SignalR predeterminados, y parece estar usando SSE en lugar de sockets web. Todavía no estoy seguro de si es posible usar sockets web con Xamarin / Monotouch. Todo se aloja mediante sitios web de Azure.

Necesitábamos que la aplicación se reconectara rápidamente a nuestro servidor SignalR, pero seguíamos teniendo problemas en los que la conexión no se reconectaba por sí sola, o la reconexión tardaba exactamente 30 segundos (debido a un tiempo de espera del protocolo subyacente).

Hubo tres escenarios que terminamos probando:

Escenario A: conexión la primera vez que se cargó la aplicación. Esto funcionó a la perfección desde el primer día. La conexión se completa en menos de 0,25 segundos incluso a través de conexiones móviles 3G. (asumiendo que la radio ya está encendida)

Escenario B: volver a conectarse al servidor de SignalR después de que la aplicación estuvo inactiva / cerrada durante 30 segundos. En este escenario, el cliente de SignalR eventualmente se volverá a conectar al servidor por sí solo sin ningún trabajo especial, pero parece esperar exactamente 30 segundos antes de intentar volver a conectarse. (demasiado lento para nuestra aplicación)

Durante este período de espera de 30 segundos, intentamos llamar a HubConnection.Start () que no tuvo ningún efecto. Y llamar a HubConnection.Stop () también lleva 30 segundos. Encontré un error relacionado en el sitio de SignalR que parece estar resuelto , pero seguimos teniendo el mismo problema en la v2.0.3.

Escenario C: volver a conectarse al servidor de SignalR después de que la aplicación estuvo inactiva / cerrada durante 120 segundos o más. En este escenario, el protocolo de transporte de SignalR ya ha agotado el tiempo de espera, por lo que el cliente nunca se vuelve a conectar automáticamente. Esto explica por qué el cliente a veces, pero no siempre, se reconectaba por sí solo. La buena noticia es que llamar a HubConnection.Start () funciona casi instantáneamente como el escenario A.

Así que me tomó un tiempo darme cuenta de que las condiciones de reconexión eran diferentes en función de si la aplicación estaba cerrada durante 30 segundos frente a más de 120 segundos. Y aunque los registros de seguimiento de SignalR iluminan lo que sucede con el protocolo subyacente, no creo que haya una forma de manejar los eventos de nivel de transporte en el código. (el evento Cerrado () se activa después de 30 segundos en el escenario B, instantáneamente en el escenario C; la propiedad del estado dice "Conectado" durante estos períodos de espera de reconexión; ningún otro evento o método relevante)

Solución: la solución es obvia. No estamos esperando que SignalR haga su magia de reconexión. En cambio, cuando se activa la aplicación o cuando se restablece la conexión de red del teléfono, simplemente estamos limpiando los eventos y eliminando la referencia a HubConnection (no podemos desecharla porque toma 30 segundos, con suerte la recolección de basura se encargará de ello ) y creando una nueva instancia. Ahora todo funciona muy bien. Por alguna razón, pensé que deberíamos reutilizar una conexión persistente y reconectarnos en lugar de simplemente crear una nueva instancia.

Ender2050
fuente
5
¿Estarías dispuesto a publicar algún código? Solo curiosidad por saber cómo lo estructuraste. También estoy usando Signalr en una aplicación de chat desde dentro de una PCL en una aplicación de Xamarin. Funciona muy bien, excepto que parece que no puedo hacer que la magia de reconexión funcione después de que el teléfono se apaga y se vuelve a encender. Juro que la multitud de TI dijo que eso era todo lo que tenía que hacer.
Timothy Lee Russell
1
Hola Ender2050, me he dado cuenta de que una vez que el dispositivo Android se desconecta del servidor, no se vuelve a conectar nunca más. Por lo tanto, implementé una alarma que se ejecuta cada 5 minutos y verifiqué la conexión de signalR con el concentrador del servidor. connectionId está vacío y luego estableció la conexión nuevamente. Pero esto no funciona bien. El usuario debe cerrar la aplicación y volver a abrirla. He usado java-client para android y C # .Net para serve hub. Buscando su ayuda para resolver este problema.
jignesh
1
FYI, Mono no tiene enchufes web. Es por eso que sus aplicaciones de Xamarin siempre usan SSE. Puede escribir un cliente de consola. Si lo ejecuta en Mono, usará SSE. Si lo ejecuta en Windows (al menos Windows 8 porque 7 tampoco admite sockets web), utilizará sockets web.
daramasala
@ Ender2050 ¿Puede ampliar su solución con algunos ejemplos de código?
Magrangs
Tenemos un problema de reconexión con SignalR Hub (biblioteca de SignalR versión 2.2.2) de la aplicación de Android que usa la "biblioteca de cliente de SignalR Java" y la aplicación de iOS que usa la "biblioteca de SignalR Object C". Las bibliotecas de cliente en ambas plataformas no se han actualizado por un tiempo. Creo que el problema se debe a la incompatibilidad del protocolo SignalR entre el cliente y el servidor.
Nadim Hossain Sonet
44

Establecer un temporizador en el evento desconectado para intentar reconectarse automáticamente es el único método que conozco.

En javascript se hace así:

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

Este es el enfoque recomendado en la documentación:

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

KingOfHypocrites
fuente
1
una pista: asegúrese de realizar cualquier funcionalidad de inicio completo desde el principio, ya que, de lo contrario, volvería a conectarse a los concentradores.
MikeBaz - MSFT
1
Descubrí que con el cliente .NET, si se suscribe al evento Cerrado antes de llamar a hub.Start (), si hay una falla para conectarse inicialmente, se llama a su controlador de eventos Cerrado e intenta llamar al hub.Start () nuevamente , provocando que el hub.Start () original nunca se complete. Mi solución fue suscribirme a Cerrado solo después de que Start () sea exitoso y cancelar la suscripción a Cerrado inmediatamente en la devolución de llamada.
Oran Dennison
3
@MikeBaz Creo que te refieres a volver a conectarte a grupos
Simon_Weaver
1
@KingOfHypocrites Me suscribí al reconnectingevento, que se activa una vez que el concentrador pierde la conexión y establece esa variable (por ejemplo shouldReconnect) en verdadera. Así que adapté tu ejemplo para verificar esa variable. Se ve bien.
Alisson
2
Hice un número aleatorio entre 10 y 60 segundos. Tenemos demasiados clientes para poner solo 5 segundos, nosotros mismos haríamos DDoS. $ .connection.hub.disconnected (function () {setTimeout (function () {$ .connection.hub.start ();}, (Math.floor (Math.random () * 50) + 10) * 1000); });
Brain2000
17

Dado que el OP solicita un cliente .NET (una implementación de winform a continuación),

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")
    }
}
ibubi
fuente
En SignalR 2.3.0 encontré que si esperaba la conexión en el evento Closed (), a veces no se conectaba. Sin embargo, si llamo a un Wait () manual en el evento con un tiempo de espera, como 10 segundos, llamaría automáticamente a Closed () una y otra vez cada 10 segundos, y luego la reconexión funcionaría.
Brain2000
0

Agrego alguna actualización para la respuesta de ibubi . Puede ser que alguien lo necesite. Encontré que, en algunos casos, el señalizador no sube al evento "cerrado" después de que se detuvo la reconexión. Lo resolví usando el evento "StateChanged". Método que se conecta al servidor 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;
        }

Método de reconexión:

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

Método de intentos interminables de conectarse al servidor (también utilizo este método para crear la primera conexión):

public async Task RestartConnection()
        {
            while (!ApplicationClosed)
            {
                bool connected = await ConnectToSignalRServer();
                if (connected)
                    return;
            }
        }
Константин Золин
fuente
-3

Puede intentar invocar el método del servidor desde su Android antes de volver a conectar el estado para evitar problemas de reconexión mágica.

SignalR Hub C #

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

En 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);
    });
}
Jongz Puangput
fuente