Noções básicas e tratamento de eventos de tempo de vida da conexão no SignalR 1.x

por Patrick Fletcher, Tom Dykstra

Aviso

Esta documentação não é para a versão mais recente do SignalR. Dê uma olhada em ASP.NET Core SignalR.

Este artigo fornece uma visão geral dos eventos de conexão, reconexão e desconexão do SignalR que você pode manipular, além de tempo limite e configurações keepalive que você pode definir.

O artigo pressupõe que você já tenha algum conhecimento do SignalR e dos eventos de tempo de vida da conexão. Para obter uma introdução ao SignalR, consulte SignalR – Visão geral – Introdução. Para obter listas de eventos de tempo de vida da conexão, consulte os seguintes recursos:

Visão geral

Este artigo inclui as seções a seguir:

Os links para tópicos de Referência de API são para a versão .NET 4.5 da API. Se você estiver usando o .NET 4, consulte a versão do .NET 4 dos tópicos da API.

Terminologia e cenários de tempo de vida da conexão

O OnReconnected manipulador de eventos em um Hub do SignalR pode ser executado diretamente depois OnConnected , mas não depois OnDisconnected de um determinado cliente. O motivo pelo qual você pode ter uma reconexão sem uma desconexão é que há várias maneiras pelas quais a palavra "conexão" é usada no SignalR.

Conexões do SignalR, conexões de transporte e conexões físicas

Este artigo diferenciará as conexões do SignalR, as conexões de transporte e as conexões físicas:

  • A conexão do SignalR refere-se a uma relação lógica entre um cliente e uma URL do servidor, mantida pela API do SignalR e identificada exclusivamente por uma ID de conexão. Os dados sobre essa relação são mantidos pelo SignalR e são usados para estabelecer uma conexão de transporte. A relação termina e o SignalR descarta os dados quando o cliente chama o Stop método ou um limite de tempo limite é atingido enquanto o SignalR está tentando restabelecer uma conexão de transporte perdida.
  • A conexão de transporte refere-se a uma relação lógica entre um cliente e um servidor, mantida por uma das quatro APIs de transporte: WebSockets, eventos enviados pelo servidor, quadro para sempre ou sondagem longa. O SignalR usa a API de transporte para criar uma conexão de transporte e a API de transporte depende da existência de uma conexão de rede física para criar a conexão de transporte. A conexão de transporte termina quando o SignalR a encerra ou quando a API de transporte detecta que a conexão física está interrompida.
  • A conexão física refere-se aos links de rede física: fios, sinais sem fio, roteadores etc. – que facilitam a comunicação entre um computador cliente e um computador servidor. A conexão física deve estar presente para estabelecer uma conexão de transporte e uma conexão de transporte deve ser estabelecida para estabelecer uma conexão signalR. No entanto, a interrupção da conexão física nem sempre encerra imediatamente a conexão de transporte ou a conexão do SignalR, como será explicado mais adiante neste tópico.

No diagrama a seguir, a conexão do SignalR é representada pela API de Hubs e pela camada signalR da API PersistentConnection, a conexão de transporte é representada pela camada Transportes e a conexão física é representada pelas linhas entre o servidor e os clientes.

Diagrama de arquitetura do SignalR

Ao chamar o Start método em um cliente SignalR, você está fornecendo o código do cliente SignalR com todas as informações necessárias para estabelecer uma conexão física com um servidor. O código do cliente SignalR usa essas informações para fazer uma solicitação HTTP e estabelecer uma conexão física que usa um dos quatro métodos de transporte. Se a conexão de transporte falhar ou o servidor falhar, a conexão do SignalR não desaparecerá imediatamente porque o cliente ainda tem as informações necessárias para restabelecer automaticamente uma nova conexão de transporte com a mesma URL do SignalR. Nesse cenário, nenhuma intervenção do aplicativo de usuário está envolvida e, quando o código do cliente SignalR estabelece uma nova conexão de transporte, ele não inicia uma nova conexão do SignalR. A continuidade da conexão do SignalR é refletida no fato de que a ID de conexão, que é criada quando você chama o Start método, não é alterada.

O OnReconnected manipulador de eventos no Hub é executado quando uma conexão de transporte é restabelecida automaticamente após ter sido perdida. O OnDisconnected manipulador de eventos é executado no final de uma conexão do SignalR. Uma conexão do SignalR pode terminar de qualquer uma das seguintes maneiras:

  • Se o cliente chamar o Stop método , uma mensagem de parada será enviada ao servidor e o cliente e o servidor encerrarão a conexão do SignalR imediatamente.
  • Depois que a conectividade entre o cliente e o servidor é perdida, o cliente tenta se reconectar e o servidor aguarda o cliente se reconectar. Se as tentativas de reconectar não forem bem-sucedidas e o período de tempo limite de desconexão terminar, o cliente e o servidor encerrarão a conexão do SignalR. O cliente para de tentar se reconectar e o servidor descarta sua representação da conexão do SignalR.
  • Se o cliente parar de ser executado sem ter a chance de chamar o Stop método , o servidor aguardará o cliente se reconectar e encerrará a conexão do SignalR após o período de tempo limite de desconexão.
  • Se o servidor parar de ser executado, o cliente tentará reconectar (recriar a conexão de transporte) e encerrará a conexão do SignalR após o período de tempo limite de desconexão.

Quando não há problemas de conexão e o aplicativo do usuário encerra a conexão do SignalR chamando o Stop método , a conexão do SignalR e a conexão de transporte começam e terminam aproximadamente ao mesmo tempo. As seções a seguir descrevem mais detalhadamente os outros cenários.

Cenários de desconexão de transporte

As conexões físicas podem estar lentas ou pode haver interrupções na conectividade. Dependendo de fatores como o comprimento da interrupção, a conexão de transporte pode ser descartada. O SignalR tenta restabelecer a conexão de transporte. Às vezes, a API de conexão de transporte detecta a interrupção e descarta a conexão de transporte e o SignalR descobre imediatamente que a conexão é perdida. Em outros cenários, nem a API de conexão de transporte nem o SignalR ficam cientes imediatamente de que a conectividade foi perdida. Para todos os transportes, exceto sondagem longa, o cliente SignalR usa uma função chamada keepalive para marcar para perda de conectividade que a API de transporte não consegue detectar. Para obter informações sobre conexões de sondagem longas, consulte Tempo limite e configurações keepalive mais adiante neste tópico.

Quando uma conexão está inativa, periodicamente o servidor envia um pacote keepalive para o cliente. A partir da data em que este artigo está sendo escrito, a frequência padrão é a cada 10 segundos. Ao ouvir esses pacotes, os clientes podem saber se há um problema de conexão. Se um pacote keepalive não for recebido quando esperado, após um curto período de tempo, o cliente assumirá que há problemas de conexão, como lentidão ou interrupções. Se o keepalive ainda não for recebido após um tempo maior, o cliente assumirá que a conexão foi descartada e começará a tentar se reconectar.

O diagrama a seguir ilustra os eventos de cliente e servidor gerados em um cenário típico quando há problemas com a conexão física que não são reconhecidos imediatamente pela API de transporte. O diagrama se aplica às seguintes circunstâncias:

  • O transporte é WebSockets, quadro para sempre ou eventos enviados pelo servidor.
  • Há períodos variados de interrupção na conexão de rede física.
  • A API de transporte não se torna ciente das interrupções, portanto, o SignalR depende da funcionalidade keepalive para detectá-las.

Desconexões de transporte

Se o cliente entrar no modo de reconexão, mas não puder estabelecer uma conexão de transporte dentro do limite de tempo limite de desconexão, o servidor encerrará a conexão do SignalR. Quando isso acontece, o servidor executa o método do OnDisconnected Hub e enfileira uma mensagem de desconexão para enviar ao cliente caso o cliente gerencie a conexão posteriormente. Se o cliente se reconectar, ele receberá o comando disconnect e chamará o Stop método . Nesse cenário, OnReconnected não é executado quando o cliente se reconecta e OnDisconnected não é executado quando o cliente chama Stop. O diagrama a seguir ilustra esse cenário.

Interrupções de transporte – tempo limite do servidor

Os eventos de tempo de vida de conexão do SignalR que podem ser gerados no cliente são os seguintes:

  • ConnectionSlow evento client.

    Gerado quando uma proporção predefinida do período de tempo limite keepalive passou desde que a última mensagem ou ping keepalive foi recebido. O período de aviso de tempo limite keepalive padrão é 2/3 do tempo limite keepalive. O tempo limite keepalive é de 20 segundos, portanto, o aviso ocorre em cerca de 13 segundos.

    Por padrão, o servidor envia pings keepalive a cada 10 segundos e o cliente verifica se há pings keepalive a cada 2 segundos (um terço da diferença entre o valor de tempo limite keepalive e o valor de aviso de tempo limite keepalive).

    Se a API de transporte ficar ciente de uma desconexão, o SignalR poderá ser informado da desconexão antes que o período de aviso de tempo limite keepalive passe. Nesse caso, o ConnectionSlow evento não seria gerado e o SignalR iria diretamente para o Reconnecting evento.

  • Reconnecting evento client.

    Gerado quando (a) a API de transporte detecta que a conexão foi perdida ou (b) o período de tempo limite keepalive passou desde que a última mensagem ou ping keepalive foi recebido. O código do cliente SignalR começa a tentar se reconectar. Você poderá lidar com esse evento se quiser que seu aplicativo tome alguma ação quando uma conexão de transporte for perdida. O período de tempo limite keepalive padrão atualmente é de 20 segundos.

    Se o código do cliente tentar chamar um método hub enquanto o SignalR estiver no modo de reconexão, o SignalR tentará enviar o comando. Na maioria das vezes, tais tentativas falharão, mas em algumas circunstâncias podem ter sucesso. Para os eventos enviados pelo servidor, quadro para sempre e transportes de sondagem longos, o SignalR usa dois canais de comunicação, um que o cliente usa para enviar mensagens e outro que usa para receber mensagens. O canal usado para receber é o permanentemente aberto, e esse é o que é fechado quando a conexão física é interrompida. O canal usado para enviar permanece disponível, portanto, se a conectividade física for restaurada, uma chamada de método de cliente para servidor poderá ser bem-sucedida antes que o canal de recebimento seja restabelecido. O valor retornado não será recebido até que o SignalR abra novamente o canal usado para recebimento.

  • Reconnected evento client.

    Gerado quando a conexão de transporte é restabelecida. O OnReconnected manipulador de eventos no Hub é executado.

  • Closed evento client (disconnected evento em JavaScript).

    Gerado quando o período de tempo limite de desconexão expira enquanto o código do cliente SignalR está tentando se reconectar depois de perder a conexão de transporte. O tempo limite de desconexão padrão é de 30 segundos. (Esse evento também é gerado quando a conexão termina porque o Stop método é chamado.)

Interrupções de conexão de transporte que não são detectadas pela API de transporte e não atrasam a recepção de pings keepalive do servidor por mais tempo do que o período de aviso de tempo limite keepalive pode não fazer com que nenhum evento de tempo de vida de conexão seja gerado.

Alguns ambientes de rede fecham deliberadamente conexões ociosas e outra função dos pacotes keepalive é ajudar a evitar isso, permitindo que essas redes saibam que uma conexão SignalR está em uso. Em casos extremos, a frequência padrão de pings keepalive pode não ser suficiente para impedir conexões fechadas. Nesse caso, você pode configurar pings keepalive para serem enviados com mais frequência. Para obter mais informações, consulte Tempo limite e configurações keepalive mais adiante neste tópico.

Observação

Importante

A sequência de eventos descrita aqui não é garantida. O SignalR faz todas as tentativas de gerar eventos de tempo de vida de conexão de maneira previsível de acordo com esse esquema, mas há muitas variações de eventos de rede e muitas maneiras pelas quais estruturas de comunicação subjacentes, como APIs de transporte, lidam com eles. Por exemplo, o Reconnected evento pode não ser gerado quando o cliente se reconecta ou o OnConnected manipulador no servidor pode ser executado quando a tentativa de estabelecer uma conexão não for bem-sucedida. Este tópico descreve apenas os efeitos que normalmente seriam produzidos por determinadas circunstâncias típicas.

Cenários de desconexão do cliente

Em um cliente do navegador, o código do cliente SignalR que mantém uma conexão SignalR é executado no contexto JavaScript de uma página da Web. É por isso que a conexão do SignalR tem que terminar quando você navega de uma página para outra e é por isso que você tem várias conexões com várias IDs de conexão se você se conectar de várias janelas ou guias do navegador. Quando o usuário fecha uma janela ou guia do navegador ou navega até uma nova página ou atualiza a página, a conexão do SignalR termina imediatamente porque o código do cliente SignalR manipula esse evento do navegador para você e chama o Stop método . Nesses cenários ou em qualquer plataforma de cliente quando seu aplicativo chama o Stop método , o OnDisconnected manipulador de eventos é executado imediatamente no servidor e o cliente aciona o Closed evento (o evento é nomeado disconnected em JavaScript).

Se um aplicativo cliente ou o computador em que ele está em execução falhar ou entrar em suspensão (por exemplo, quando o usuário fechar o laptop), o servidor não será informado sobre o que aconteceu. Até onde o servidor sabe, a perda do cliente pode ser devido à interrupção da conectividade e o cliente pode estar tentando se reconectar. Portanto, nesses cenários, o servidor aguarda para dar ao cliente a chance de se reconectar e OnDisconnected não é executado até que o período de tempo limite de desconexão expire (cerca de 30 segundos por padrão). O diagrama a seguir ilustra esse cenário.

Falha no computador cliente

Cenários de desconexão de servidor

Quando um servidor fica offline – ele reinicializa, falha, o domínio do aplicativo é reciclado etc. -- o resultado pode ser semelhante a uma conexão perdida ou a API de transporte e o SignalR podem saber imediatamente que o servidor desapareceu e o SignalR pode começar a tentar se reconectar sem gerar o ConnectionSlow evento. Se o cliente entrar no modo de reconexão e se o servidor recuperar ou reiniciar ou um novo servidor for colocado online antes que o período de tempo limite de desconexão expire, o cliente se reconectará ao servidor restaurado ou novo. Nesse caso, a conexão signalr continua no cliente e o Reconnected evento é gerado. No primeiro servidor, OnDisconnected nunca é executado e, no novo servidor, é executado, OnReconnected embora nunca tenha OnConnected sido executado para esse cliente nesse servidor antes. (O efeito será o mesmo se o cliente se reconectar ao mesmo servidor após uma reinicialização ou reciclar o domínio do aplicativo, pois quando o servidor reinicia ele não tem memória de atividade de conexão anterior.) O diagrama a seguir pressupõe que a API de transporte fique ciente da conexão perdida imediatamente, de modo que o ConnectionSlow evento não seja gerado.

Falha e reconexão do servidor

Se um servidor não ficar disponível dentro do período de tempo limite de desconexão, a conexão do SignalR terminará. Nesse cenário, o Closed evento (disconnected em clientes JavaScript) é gerado no cliente, mas OnDisconnected nunca é chamado no servidor. O diagrama a seguir pressupõe que a API de transporte não fique ciente da conexão perdida, portanto, ela é detectada pela funcionalidade keepalive do SignalR e o ConnectionSlow evento é gerado.

Falha e tempo limite do servidor

Tempo limite e configurações keepalive

Os valores padrão ConnectionTimeout, DisconnectTimeoute KeepAlive são apropriados para a maioria dos cenários, mas podem ser alterados se o ambiente tiver necessidades especiais. Por exemplo, se o ambiente de rede fechar conexões ociosas por 5 segundos, talvez seja necessário diminuir o valor keepalive.

ConnectionTimeout

Essa configuração representa a quantidade de tempo para deixar uma conexão de transporte aberta e aguardando uma resposta antes de fechá-la e abrir uma nova conexão. O valor padrão é 110 segundos.

Essa configuração só se aplica quando a funcionalidade keepalive está desabilitada, o que normalmente se aplica apenas ao transporte de sondagem longo. O diagrama a seguir ilustra o efeito dessa configuração em uma conexão de transporte de sondagem longa.

Conexão de transporte de sondagem longa

DisconnectTimeout

Essa configuração representa a quantidade de tempo de espera após a perda de uma conexão de transporte antes de gerar o Disconnected evento. O valor padrão é 30 segundos. Quando você define DisconnectTimeout, KeepAlive é definido automaticamente como 1/3 do DisconnectTimeout valor.

KeepAlive

Essa configuração representa a quantidade de tempo de espera antes de enviar um pacote keepalive por uma conexão ociosa. O valor padrão é 10 segundos. Esse valor não deve ser superior a 1/3 do DisconnectTimeout valor.

Se você quiser definir e DisconnectTimeoutKeepAlive, defina KeepAlive após DisconnectTimeout. Caso contrário, a KeepAlive configuração será substituída quando DisconnectTimeout for definida KeepAlive automaticamente como 1/3 do valor de tempo limite.

Se você quiser desabilitar a funcionalidade keepalive, defina KeepAlive como nulo. A funcionalidade keepalive é desabilitada automaticamente para o transporte de sondagem longo.

Como alterar o tempo limite e as configurações keepalive

Para alterar os valores padrão dessas configurações, defina-os no Application_Start arquivo Global.asax , conforme mostrado no exemplo a seguir. Os valores mostrados no código de exemplo são os mesmos que os valores padrão.

protected void Application_Start(object sender, EventArgs e)
{
    // Make long polling connections wait a maximum of 110 seconds for a
    // response. When that time expires, trigger a timeout command and
    // make the client reconnect.
    GlobalHost.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(110);
    
    // Wait a maximum of 30 seconds after a transport connection is lost
    // before raising the Disconnected event to terminate the SignalR connection.
    GlobalHost.Configuration.DisconnectTimeout = TimeSpan.FromSeconds(30);
    
    // For transports other than long polling, send a keepalive packet every
    // 10 seconds. 
    // This value must be no more than 1/3 of the DisconnectTimeout value.
    GlobalHost.Configuration.KeepAlive = TimeSpan.FromSeconds(10);
}

Como notificar o usuário sobre desconexões

Em alguns aplicativos, talvez você queira exibir uma mensagem para o usuário quando houver problemas de conectividade. Você tem várias opções de como e quando fazer isso. Os exemplos de código a seguir são para um cliente JavaScript usando o proxy gerado.

  • Manipule o connectionSlow evento para exibir uma mensagem assim que o SignalR estiver ciente dos problemas de conexão, antes de entrar no modo de reconexão.

    $.connection.hub.connectionSlow(function() {
        notifyUserOfConnectionProblem(); // Your function to notify user.
    });
    
  • Manipule o reconnecting evento para exibir uma mensagem quando o SignalR estiver ciente de uma desconexão e estiver entrando no modo de reconexão.

    $.connection.hub.reconnecting(function() {
        notifyUserOfTryingToReconnect(); // Your function to notify user.
    });
    
  • Manipule o evento para exibir uma mensagem quando uma tentativa de reconectar atingiu o disconnected tempo limite. Nesse cenário, a única maneira de restabelecer uma conexão com o servidor novamente é reiniciar a conexão do SignalR chamando o Start método , que criará uma nova ID de conexão. O exemplo de código a seguir usa um sinalizador para garantir que você emita a notificação somente após um tempo limite de reconexão, não após um final normal para a conexão signalr causada pela chamada do Stop método .

    var tryingToReconnect = false;
    
    $.connection.hub.reconnecting(function() {
        tryingToReconnect = true;
    });
    
    $.connection.hub.reconnected(function() {
        tryingToReconnect = false;
    });
    
    $.connection.hub.disconnected(function() {
        if(tryingToReconnect) {
            notifyUserOfDisconnect(); // Your function to notify user.
        }
    });
    

Como se reconectar continuamente

Em alguns aplicativos, talvez você queira restabelecer automaticamente uma conexão depois que ela for perdida e a tentativa de reconectar atingiu o tempo limite. Para fazer isso, você pode chamar o Start método do manipulador Closed de eventos (disconnected manipulador de eventos em clientes JavaScript). Talvez você queira aguardar um período antes de chamar Start para evitar fazer isso com muita frequência quando o servidor ou a conexão física não estiverem disponíveis. O exemplo de código a seguir é para um cliente JavaScript usando o proxy gerado.

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

Um problema potencial a ser considerado em clientes móveis é que as tentativas de reconexão contínua quando o servidor ou a conexão física não estiver disponível podem causar perda desnecessária de bateria.

Como desconectar um cliente no código do servidor

O SignalR versão 1.1.1 não tem uma API de servidor interna para desconectar clientes. Há planos para adicionar essa funcionalidade no futuro. Na versão atual do SignalR, a maneira mais simples de desconectar um cliente do servidor é implementar um método de desconexão no cliente e chamar esse método do servidor. O exemplo de código a seguir mostra um método de desconexão para um cliente JavaScript usando o proxy gerado.

var myHubProxy = $.connection.myHub
myHubProxy.client.stopClient = function() {
    $.connection.hub.stop();
};

Aviso

Segurança – Nem esse método para desconectar clientes nem a API interna proposta abordará o cenário de clientes hackeados que estão executando código mal-intencionado, pois os clientes podem se reconectar ou o código hackeado pode remover o stopClient método ou alterar o que ele faz. O local apropriado para implementar a proteção dos DOS (negação de serviço) com estado não está na estrutura ou na camada de servidor, mas sim na infraestrutura de front-end.