Prática recomendada para reconectar o cliente SignalR 2.0 .NET ao hub do servidor

86

Estou usando o SignalR 2.0 com o cliente .NET em um aplicativo móvel que precisa lidar com vários tipos de desconexões. Às vezes, o cliente SignalR se reconecta automaticamente - e às vezes ele precisa ser reconectado diretamente chamando HubConnection.Start()novamente.

Como o SignalR se reconecta automaticamente de forma mágica algumas vezes, estou me perguntando se estou perdendo um recurso ou definição de configuração.

Qual é a melhor maneira de configurar um cliente que se reconecta automaticamente?


Eu vi exemplos de javascript que manipulam o Closed()evento e, em seguida, Connect após n-segundos. Existe alguma abordagem recomendada?

Eu li a documentação e vários artigos sobre o tempo de vida das conexões SignalR, mas ainda não estou certo sobre como lidar com a reconexão do cliente.

Ender2050
fonte

Respostas:

71

Eu finalmente descobri isso. Aqui está o que aprendi desde o início desta pergunta:

Histórico: Estamos construindo um aplicativo iOS usando Xamarin / Monotouch e o cliente .NET SignalR 2.0.3. Estamos usando os protocolos SignalR padrão - e parece estar usando SSE em vez de web sockets. Ainda não tenho certeza se é possível usar sockets web com Xamarin / Monotouch. Tudo é hospedado usando sites do Azure.

Precisávamos que o aplicativo se reconectasse ao nosso servidor SignalR rapidamente, mas continuamos tendo problemas onde a conexão não se reconectava por conta própria - ou a reconexão demorava exatamente 30 segundos (devido a um tempo limite de protocolo subjacente).

Acabamos testando três cenários:

Cenário A - conectando na primeira vez que o aplicativo foi carregado. Isso funcionou perfeitamente desde o primeiro dia. A conexão é concluída em menos de 0,25 segundos, mesmo em conexões móveis 3G. (presumindo que o rádio já esteja ligado)

Cenário B - reconectar ao servidor SignalR após o aplicativo ficar inativo / fechado por 30 segundos. Nesse cenário, o cliente SignalR eventualmente se reconectará ao servidor por conta própria sem nenhum trabalho especial - mas parece esperar exatamente 30 segundos antes de tentar se reconectar. (muito lento para nosso aplicativo)

Durante esse período de espera de 30 segundos, tentamos chamar HubConnection.Start () que não teve efeito. E chamar HubConnection.Stop () também leva 30 segundos. Encontrei um bug relacionado no site SignalR que parece estar resolvido , mas ainda estamos tendo o mesmo problema na v2.0.3.

Cenário C - reconectando-se ao servidor SignalR após o aplicativo ficar inativo / fechado por 120 segundos ou mais. Nesse cenário, o protocolo de transporte SignalR já atingiu o tempo limite, portanto o cliente nunca se reconecta automaticamente. Isso explica por que o cliente às vezes, mas nem sempre se reconectava sozinho. A boa notícia é que chamar HubConnection.Start () funciona quase que instantaneamente como o cenário A.

Então, demorei um pouco para perceber que as condições de reconexão eram diferentes com base no fato de o aplicativo ter sido fechado por 30 segundos versus mais de 120 segundos. E embora os logs de rastreamento SignalR iluminem o que está acontecendo com o protocolo subjacente, não acredito que haja uma maneira de lidar com os eventos de nível de transporte no código. (o evento Closed () dispara após 30 segundos no cenário B, instantaneamente no cenário C; a propriedade State diz "Conectado" durante esses períodos de espera de reconexão; nenhum outro evento ou método relevante

Solução: a solução é óbvia. Não estamos esperando que o SignalR faça sua mágica de reconexão. Em vez disso, quando o aplicativo é ativado ou a conexão de rede do telefone é restaurada, estamos simplesmente limpando os eventos e retirando a referência do HubConnection (não podemos descartá-lo porque leva 30 segundos, espero que a coleta de lixo cuide disso ) e criando uma nova instância. Agora tudo está funcionando muito bem. Por algum motivo, pensei que deveríamos reutilizar uma conexão persistente e reconectar em vez de apenas criar uma nova instância.

Ender2050
fonte
5
Você estaria disposto a postar algum código? Só estou curioso para saber como você estruturou. Estou usando o Signalr em um aplicativo de bate-papo de dentro de um PCL em um aplicativo Xamarin também. Funciona muito bem, exceto que não consigo fazer a mágica de reconexão funcionar depois que o telefone é desligado e ligado novamente. Juro que a equipe de TI disse que era tudo o que eu precisava fazer.
Timothy Lee Russell
1
Olá, Ender2050, Notei que uma vez que o dispositivo Android foi desconectado do servidor, nunca mais se reconectou. Portanto, implementei o alarme que é executado a cada 5 minutos e verifica a conexão do signalR com o hub do servidor. connectionId está vazio, em seguida, estabeleceu a conexão novamente. Mas isso não funciona bem. O usuário precisa encerrar o aplicativo e reabri-lo. Eu usei o cliente Java para Android e C # .Net para servir hub. Procurando sua ajuda para resolver este problema.
jignesh,
1
FYI, Mono não tem soquetes de web. É por isso que seus aplicativos Xamarin sempre usam SSE. Você pode escrever um cliente de console. Se você executá-lo no Mono, ele usará SSE. Se você executá-lo no Windows (pelo menos no Windows 8 porque 7 também não oferece suporte para web sockets), ele usará web sockets.
daramasala
@ Ender2050 Por favor, você pode expandir sua solução com alguns exemplos de código?
Magrangs
Estamos tendo problemas de reconexão com SignalR Hub (biblioteca SignalR versão 2.2.2) do aplicativo Android que está usando "SignalR Java Client library" e iOS App que está usando "SignalR Object C library". As bibliotecas cliente em ambas as plataformas não são atualizadas há algum tempo. Acho que o problema é devido à incompatibilidade do protocolo SignalR entre o cliente e o servidor.
Nadim Hossain Sonet
44

Definir um cronômetro no evento desconectado para tentar reconectar automaticamente é o único método que conheço.

Em javascript, é feito assim:

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

Esta é a abordagem recomendada na documentação:

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

KingOfHypocrites
fonte
1
uma dica - certifique-se de executar qualquer funcionalidade completa de inicialização da mesma forma que faria, por exemplo, reconectar aos hubs.
MikeBaz - MSFT
1
Descobri que com o cliente .NET, se você se inscrever no evento Closed antes de chamar hub.Start (), se houver uma falha na conexão inicial, o manipulador de eventos Closed é chamado e tenta chamar o hub.Start () novamente , fazendo com que o hub.Start () original nunca seja concluído. Minha solução foi apenas assinar Closed após Start () ser bem-sucedido, e cancelar a assinatura de Closed imediatamente no retorno de chamada.
Oran Dennison
3
@MikeBaz, acho que você quer dizer reconectar-se a grupos
Simon_Weaver
1
@KingOfHypocrites Eu me inscrevi no reconnectingevento, que é disparado quando o hub perde a conexão e define essa variável (por exemplo shouldReconnect) como true. Portanto, adaptei seu exemplo para verificar essa variável. Parece legal.
Alisson de
2
Eu fiz um número aleatório entre 10 e 60 segundos. Temos muitos clientes para colocar apenas 5 segundos, nós mesmos faríamos o DDoS. $ .connection.hub.disconnected (function () {setTimeout (function () {$ .connection.hub.start ();}, (Math.floor (Math.random () * 50) + 10) * 1000); });
Brain2000
17

Como o OP está pedindo um cliente .NET (uma implementação winform abaixo),

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
fonte
Eu descobri no SignalR 2.3.0 que se eu esperasse pela conexão no evento Closed () ele às vezes não conectava. No entanto, se eu chamasse um Wait () manual no evento com um tempo limite, como 10 segundos, ele chamaria automaticamente Closed () novamente a cada 10 segundos e a reconexão funcionaria.
Brain2000
0

Eu adiciono alguma atualização para a resposta do ibubi . Pode ser que alguém precise. Descobri que em alguns casos o signalr não sobe no evento "fechado" após a reconexão ser interrompida. Eu resolvi isso usando o evento "StateChanged". Método que se conecta ao 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 para reconectar:

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

Método de tentativas intermináveis ​​de conexão com o servidor (também uso este método para criar a primeira conexão):

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

Você pode tentar invocar o método do servidor do seu android antes de iniciar o estado de reconexão para evitar o problema de reconexão mágica.

SignalR Hub C #

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

No 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
fonte