Comprender y controlar eventos de duración de la conexión en SignalR

Advertencia

Esta documentación no se aplica a la última versión de SignalR. Eche un vistazo a SignalR de ASP.NET Core.

Este artículo ofrece una introducción general a los eventos de conexión, reconexión y desconexión de SignalR que puede controlar, así como a las configuraciones de tiempo de espera y KeepAlive que puede establecer.

El artículo parte la premisa de que usted ya posee conocimientos de SignalR y de los eventos de duración de la conexión. Para iniciarse SignalR, consulte Introducción a SignalR. Para ver listas de eventos de duración de la conexión, consulte los siguientes recursos:

Versiones de software empleadas en este tema

Versiones anteriores de este tema

Para obtener información sobre versiones anteriores de SignalR, consulte Versiones anteriores de SignalR.

Preguntas y comentarios

Deje sus comentarios sobre este tutorial y sobre lo que podríamos mejorar en los comentarios en la parte inferior de la página. Si tiene alguna pregunta que no esté directamente relacionadas con el tutorial, puede publicarla en el foro de SignalR de ASP.NET o en StackOverflow.com.

Información general

Este artículo contiene las siguientes secciones:

Los vínculos a los temas de referencia de la API corresponden a la versión .NET 4.5 de la API. Si está usando .NET 4, consulte la versión .NET 4 de los temas de API.

Escenarios y terminología de la duración de la conexión

El controlador de eventos OnReconnected en un concentrador de SignalR puede ejecutarse directamente después de OnConnected, pero no después de OnDisconnected, para un cliente determinado. El motivo por el que puede producirse una reconexión sin una desconexión es que el término «conexión» se utiliza de diversas maneras en SignalR.

Conexiones SignalR, de transporte y físicas

Este artículo distingue entre conexiones SignalR, conexiones de transporte y conexiones físicas:

  • Conexión de SignalR hace referencia a una relación lógica entre un cliente y una URL de servidor, mantenida por la API de SignalR e identificada de forma única por un id. de conexión. SignalR mantiene los datos sobre esta relación, que se usan para establecer una conexión de transporte. La relación termina y SignalR se deshace de los datos cuando el cliente llama al método Stop o se alcanza un límite de tiempo de espera mientras SignalR intenta restablecer una conexión de transporte perdida.
  • Conexión de transporte hace referencia a una relación lógica entre un cliente y un servidor, mantenida por una de las cuatro API de transporte: WebSockets, eventos enviados por el servidor, Forever Frame o sondeo largo. SignalR usa la API de transporte para crear una conexión de transporte, y la API de transporte depende de la existencia de una conexión de red física para crear la conexión de transporte. La conexión de transporte finaliza cuando SignalR la da por terminada o cuando la API de transporte detecta que la conexión física se ha roto.
  • Conexión física hace referencia a los vínculos físicos de la red (cables, señales inalámbricas, enrutadores, etc.) que facilitan la comunicación entre un equipo cliente y un equipo servidor. La conexión física debe estar presente para establecer una conexión de transporte, y debe establecerse una conexión de transporte para establecer una conexión de SignalR. Sin embargo, romper la conexión física no siempre pone fin inmediatamente a la conexión de transporte o a la conexión de SignalR, como se explicará más adelante en este tema.

En el siguiente diagrama, la conexión de SignalR está representada por la capa SignalR de la API Hubs y la API PersistentConnection, la conexión de transporte está representada por la capa Transports y la conexión física está representada por las líneas entre el servidor y los clientes.

SignalR architecture diagram

Cuando llama al método Start en un cliente de SignalR, está proporcionando al código del cliente de SignalR toda la información que necesita para establecer una conexión física con un servidor. El código cliente de SignalR usa esta información para hacer una solicitud HTTP y establecer una conexión física que use uno de los cuatro métodos de transporte. Si falla la conexión de transporte o falla el servidor, la conexión de SignalR no desaparece inmediatamente porque el cliente sigue teniendo la información que necesita para restablecer automáticamente una nueva conexión de transporte con la misma URL de SignalR. En este escenario, no interviene la aplicación del usuario, y cuando el código del cliente de SignalR establece una nueva conexión de transporte, no inicia una nueva conexión de SignalR. La continuidad de la conexión de SignalR se refleja en el hecho de que el id. de la conexión, que se crea al llamar al método Start, no cambia.

El controlador de eventos OnReconnected del concentrador se ejecuta cuando se restablece automáticamente una conexión de transporte tras haberse perdido. El controlador de eventos OnDisconnected se ejecuta al final de una conexión de SignalR. Una conexión de SignalR puede finalizar de cualquiera de las siguientes maneras:

  • Si el cliente llama al método Stop, se envía un mensaje de parada al servidor, y tanto el cliente como el servidor finalizan inmediatamente la conexión de SignalR.
  • Cuando se pierde la conectividad entre el cliente y el servidor, el cliente intenta volver a conectarse y el servidor espera a que el cliente se vuelva a conectar. Si los intentos de reconexión son infructuosos y finaliza el tiempo de espera de desconexión, tanto el cliente como el servidor finalizan la conexión de SignalR. El cliente deja de intentar volver a conectarse y el servidor se deshace de su representación de la conexión de SignalR.
  • Si el cliente deja de funcionar sin tener la oportunidad de llamar al método Stop, el servidor espera a que el cliente vuelva a conectarse y después finaliza la conexión de SignalR tras el periodo de desconexión.
  • Si el servidor deja de funcionar, el cliente intenta reconectarse (volver a crear la conexión de transporte), y después finaliza la conexión de SignalR tras el periodo de tiempo de espera de desconexión.

Cuando no hay problemas de conexión y la aplicación del usuario termina la conexión de SignalR llamando al método Stop, la conexión de SignalR y la conexión de transporte comienzan y terminan más o menos al mismo tiempo. En las secciones siguientes, se describen con más detalle los demás escenarios.

Escenarios de desconexión del transporte

Las conexiones físicas pueden ser lentas o puede haber interrupciones en la conectividad. En función de factores como la duración de la interrupción, la conexión de transporte podría anularse. Entonces, SignalR intenta restablecer la conexión de transporte. A veces, la API de conexión de transporte detecta la interrupción y anula la conexión de transporte, y SignalR se entera inmediatamente de que se ha perdido la conexión. En otros escenarios, ni la API de conexión de transporte ni SignalR se dan cuenta inmediatamente de que se ha perdido la conectividad. Para todos los transportes excepto el sondeo largo, el cliente de SignalR usa una función llamada KeepAlive para comprobar la pérdida de conectividad que la API de transporte no es capaz de detectar. Para obtener información sobre las conexiones de sondeo largo, consulte Configuración del tiempo de espera y KeepAlive más adelante en este tema.

Cuando una conexión está inactiva, el servidor envía periódicamente un paquete KeepAlive al cliente. A la fecha de redacción de este artículo, la frecuencia predeterminada es cada 10 segundos. Al mantenerse a la escucha de estos paquetes, los clientes pueden saber si hay un problema de conexión. Si no se recibe un paquete KeepAlive cuando se espera, al cabo de poco tiempo el cliente asume que hay problemas de conexión, como lentitud o interrupciones. Si el KeepAlive sigue sin recibirse después de un tiempo más prolongado, el cliente asume que la conexión se ha anulado y comienza a intentar reconectarse.

El siguiente diagrama ilustra los eventos de cliente y servidor que se plantean en un escenario típico cuando hay problemas con la conexión física que no son reconocidos inmediatamente por la API de transporte. El diagrama se aplica a las siguientes circunstancias:

  • el transporte es WebSockets, Forever Frame o eventos enviados del servidor;
  • hay periodos variables de interrupción en la conexión física de la red.
  • La API de transporte no repara en las interrupciones, por lo que SignalR confía en la funcionalidad de KeepAlive para detectarlas.

Transport disconnections

Si el cliente entra en modo de reconexión, pero no puede establecer una conexión de transporte dentro del límite de tiempo de espera de desconexión, el servidor finaliza la conexión de SignalR. Cuando esto ocurre, el servidor ejecuta el método OnDisconnected del centro de conectividad y pone en cola un mensaje de desconexión para enviarlo al cliente en caso de que este consiga conectarse más tarde. Si después el cliente vuelve a conectarse, recibe la orden de desconexión y llama al método Stop. En este escenario, OnReconnected no se ejecuta cuando el cliente se reconecta, y OnDisconnected no se ejecuta cuando el cliente llama a Stop. El siguiente diagrama ilustra este escenario.

Transport disruptions - server timeout

Los eventos de duración de la conexión de SignalR que pueden plantearse en el cliente son los siguientes:

  • Evento de cliente ConnectionSlow.

    Se activa cuando ha pasado una proporción preestablecida del periodo de tiempo de espera de KeepAlive desde que se recibió el último mensaje o ping de KeepAlive. El periodo de aviso de tiempo de espera de KeepAlive predeterminado es de 2/3 del tiempo de espera de KeepAlive. El tiempo de espera de KeepAlive es de 20 segundos, por lo que el aviso se produce a los 13 segundos aproximadamente.

    De manera predeterminada, el servidor envía pings de KeepAlive cada 10 segundos y el cliente comprueba si hay pings de KeepAlive cada 2 segundos aproximadamente (un tercio de la diferencia entre el valor de tiempo de espera de KeepAlive y el valor de advertencia de tiempo de espera de KeepAlive).

    Si la API de transporte se da cuenta de una desconexión, SignalR podría ser informado de la desconexión antes de que pase el periodo de aviso del tiempo de espera de KeepAlive. En ese caso, el evento ConnectionSlow no se produciría y SignalR pasaría directamente al evento Reconnecting.

  • Evento de cliente Reconnecting.

    Se activa cuando (a) la API de transporte detecta que se ha perdido la conexión, o (b) ha pasado el periodo de tiempo de espera de KeepAlive desde que se recibió el último mensaje o ping de KeepAlive. El código del cliente de SignalR comienza a intentar reconectarse. Puede controlar este evento si quiere que su aplicación realice alguna acción cuando se pierda una conexión de transporte. El periodo de tiempo de espera de KeepAlive predeterminado es actualmente de 20 segundos.

    Si el código de su cliente intenta llamar a un método Hub mientras SignalR está en modo de reconexión, SignalR intentará enviar el comando. La mayoría de las veces, estos intentos fracasarán, pero en algunas circunstancias podrían tener éxito. Para los transportes de eventos enviados del servidor, Forever Frame y sondeo largo, SignalR usa dos canales de comunicación, uno que el cliente usa para enviar mensajes y otro que usa para recibir mensajes. El canal usado para la recepción es el que está permanentemente abierto, y es el que se cierra cuando se interrumpe la conexión física. El canal utilizado para el envío sigue estando disponible, por lo que si se restablece la conectividad física, una llamada a método del cliente al servidor podría tener éxito antes de que se restablezca el canal de recepción. El valor de retorno no se recibiría hasta que SignalR volviera a abrir el canal usado para la recepción.

  • Evento de cliente Reconnected.

    Se activa cuando se restablece la conexión de transporte. Se ejecuta el controlador de eventos OnReconnected en el concentrador.

  • Evento de cliente Closed (evento disconnected en JavaScript).

    Se activa cuando expira el tiempo de espera de desconexión mientras el código del cliente de SignalR intenta volver a conectarse tras perder la conexión de transporte. El tiempo de desconexión predeterminado es de 30 segundos. (Este evento también se produce cuando finaliza la conexión porque se llama al método Stop).

Las interrupciones de la conexión de transporte que no son detectadas por la API de transporte y que no retrasan la recepción de pings de KeepAlive desde el servidor durante más tiempo que el periodo de aviso de tiempo de espera de KeepAlive podrían no provocar la aparición de ningún evento de duración de la conexión.

Algunos entornos de red cierran deliberadamente las conexiones inactivas, y otra función de los paquetes KeepAlive es ayudar a evitarlo haciendo saber a estas redes que se está usando una conexión de SignalR. En casos extremos, la frecuencia predeterminada de los pings de KeepAlive podría no ser suficiente para evitar que se cierren las conexiones. En ese caso, puede configurar los pings de KeepAlive para que se envíen con más frecuencia. Para más información, consulte Configuración de tiempo de espera y KeepAlive más adelante en este tema.

Nota:

Importante: la secuencia de eventos aquí descrita no está garantizada. SignalR hace todo lo posible para generar eventos de duración de la conexión de forma predecible según este esquema, pero existen muchas variaciones de eventos de red y muchas formas en las que los marcos de comunicaciones subyacentes, como las API de transporte, los controlan. Por ejemplo, puede que el evento Reconnected no se active cuando el cliente se vuelva a conectar, o que el controlador OnConnected en el servidor se ejecute cuando el intento de establecer una conexión no tenga éxito. Este tema describe solo los efectos que se producirían normalmente en determinadas circunstancias típicas.

Escenarios de desconexión del cliente

En un cliente de explorador, el código del cliente de SignalR que mantiene una conexión de SignalR se ejecuta en el contexto de JavaScript de una página web. Por eso la conexión de SignalR tiene que terminar cuando navega de una página a otra, y por eso tiene varias conexiones con varios id. de conexión si se conecta desde varias ventanas o pestañas del explorador. Cuando el usuario cierra una ventana o pestaña del explorador, o navega a una nueva página o actualiza la página, la conexión de SignalR finaliza inmediatamente porque el código del cliente de SignalR controla ese evento del explorador por usted y llama al método Stop. En estos escenarios, o en cualquier plataforma cliente, cuando su aplicación llama al método Stop, el controlador de eventos OnDisconnected se ejecuta inmediatamente en el servidor y el cliente genera el evento Closed (el evento se denomina disconnected en JavaScript).

Si una aplicación cliente o el equipo en el que se ejecuta se bloquea o entra en suspensión (por ejemplo, cuando el usuario cierra el portátil), el servidor no es informado de lo sucedido. Por lo que sabe el servidor, la pérdida del cliente podría deberse a una interrupción de la conectividad y el cliente podría estar intentando volver a conectarse. Por lo tanto, en estos escenarios, el servidor espera a que el cliente pueda volver a conectarse y OnDisconnected no se ejecuta hasta que expire el período de tiempo de espera de desconexión (unos 30 segundos de forma predeterminada). El siguiente diagrama ilustra este escenario.

Client computer failure

Escenarios de desconexión del servidor

Cuando un servidor se desconecta (se reinicia, falla, el dominio de la aplicación se recicla, etc.), el resultado puede ser similar a una conexión perdida, o la API de transporte y SignalR podrían saber inmediatamente que el servidor se ha perdido y SignalR podría empezar a intentar reconectarse sin generar el evento ConnectionSlow. Si el cliente pasa al modo de reconexión, y si el servidor se recupera o se reinicia o se pone en línea un nuevo servidor antes de que expire el tiempo de espera de desconexión, el cliente volverá a conectarse al servidor restaurado o al nuevo servidor. En ese caso, la conexión de SignalR continúa en el cliente y se produce el evento Reconnected. En el primer servidor, OnDisconnected nunca se ejecuta, y en el nuevo servidor, OnReconnected se ejecuta aunque OnConnected nunca se ejecutó antes para ese cliente en ese servidor. (El efecto es el mismo si el cliente vuelve a conectarse al mismo servidor tras un reinicio o un reciclado del dominio de la aplicación, porque cuando el servidor se reinicia no tiene memoria de la actividad de conexión anterior). El siguiente diagrama supone que la API de transporte se da cuenta de la conexión perdida inmediatamente, por lo que no se produce el evento ConnectionSlow.

Server failure and reconnection

Si un servidor no está disponible dentro del tiempo de espera de desconexión, la conexión de SignalR finaliza. En este escenario, el evento Closed (disconnected en los clientes de JavaScript) se genera en el cliente, pero nunca se llama a OnDisconnected en el servidor. El siguiente diagrama supone que la API de transporte no se da cuenta de la pérdida de conexión, por lo que se detecta mediante la funcionalidad KeepAlive de SignalR y se genera el evento ConnectionSlow.

Server failure and timeout

Configuración de tiempo de espera y KeepAlive

Los valores predeterminados de ConnectionTimeout, DisconnectTimeout y KeepAlive son apropiados para la mayoría de los escenarios, pero pueden cambiarse si su entorno tiene necesidades especiales. Por ejemplo, si su entorno de red cierra las conexiones que están inactivas durante 5 segundos, es posible que tenga que disminuir el valor de KeepAlive.

ConnectionTimeout

Esta configuración representa la cantidad de tiempo que se debe dejar abierta una conexión de transporte y esperar una respuesta antes de cerrarla y abrir una nueva conexión. El valor predeterminado es 110 segundos.

Esta configuración se aplica solo cuando se deshabilita la funcionalidad KeepAlive, que normalmente se aplica solo al transporte de sondeo largo. El siguiente diagrama ilustra el efecto de esta configuración en una conexión de transporte de sondeo largo.

Long polling transport connection

DisconnectTimeout

Esta configuración representa la cantidad de tiempo que hay que esperar después de que se pierda una conexión de transporte antes de lanzar el evento Disconnected. El valor predeterminado es 30 segundos. Al establecer DisconnectTimeout, KeepAlive se ajusta automáticamente a 1/3 del valor de DisconnectTimeout.

KeepAlive

Esta configuración representa la cantidad de tiempo que se debe esperar antes de enviar un paquete KeepAlive a través de una conexión inactiva. El valor predeterminado es 10 segundos. Este valor no debe ser más de un tercio del valor de DisconnectTimeout.

Si quiere establecer tanto DisconnectTimeout como KeepAlive, establezca KeepAlive después de DisconnectTimeout. De lo contrario, su configuración de KeepAlive se sobrescribirá cuando DisconnectTimeout establezca automáticamente KeepAlive a un tercio del valor del tiempo de espera.

Si quiere deshabilitar la funcionalidad de KeepAlive, establezca KeepAlive a null. La funcionalidad de KeepAlive se deshabilita automáticamente para el transporte de sondeo largo.

Cómo cambiar la configuración de tiempo de espera y KeepAlive

Para cambiar los valores predeterminados de estas configuraciones, establézcalos como Application_Start en su archivo Global.asax, como se muestra en el siguiente ejemplo. Los valores mostrados en el código de ejemplo son los mismos que los valores predeterminados.

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);
    
    RouteTable.Routes.MapHubs();
}

Cómo notificar al usuario las desconexiones

En algunas aplicaciones, es posible que quiera mostrar un mensaje al usuario cuando haya problemas de conectividad. Tiene varias opciones sobre cómo y cuándo hacerlo. Los siguientes ejemplos de código son para un cliente JavaScript utilizando el proxy generado.

  • Controlar el evento connectionSlow para mostrar un mensaje en cuanto SignalR tenga conocimiento de problemas de conexión, antes de que entre en modo de reconexión.

    $.connection.hub.connectionSlow(function() {
        notifyUserOfConnectionProblem(); // Your function to notify user.
    });
    
  • Controlar el evento reconnecting para mostrar un mensaje cuando SignalR es consciente de una desconexión y entra en modo de reconexión.

    $.connection.hub.reconnecting(function() {
        notifyUserOfTryingToReconnect(); // Your function to notify user.
    });
    
  • Controlar el evento disconnected para mostrar un mensaje cuando se ha agotado el tiempo de espera de un intento de reconexión. En este caso, la única forma de restablecer de nuevo la conexión con el servidor es reiniciar la conexión de SignalR llamando al método Start, que creará un nuevo id. de conexión. El siguiente código de ejemplo usa una marca para asegurarse de que solo se emite la notificación tras un tiempo de espera de reconexión, y no tras un final normal de la conexión de SignalR provocado por la llamada al método Stop.

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

Cómo reconectar continuamente

En algunas aplicaciones puede querer restablecer automáticamente una conexión después de que se haya perdido y el intento de volver a conectarse haya agotado el tiempo de espera. Para ello, puede llamar al método Start desde su controlador de eventos Closed (controlador de eventos disconnected en clientes JavaScript). Es posible que quiera esperar un tiempo antes de llamar a Start para evitar hacerlo con demasiada frecuencia cuando el servidor o la conexión física no estén disponibles. El siguiente código de ejemplo es para un cliente JavaScript usando el proxy generado.

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

Un problema potencial que hay que tener en cuenta en los clientes móviles es que los continuos intentos de reconexión cuando el servidor o la conexión física no están disponibles podrían provocar un consumo innecesario de la batería.

Cómo desconectar un cliente en el código del servidor

La versión 2 de SignalR no tiene incorporada una API de servidor para desconectar a los clientes. Existen planes para añadir esta funcionalidad en el futuro. En la versión actual de SignalR, la forma más sencilla de desconectar un cliente del servidor es implementar un método de desconexión en el cliente y llamar a ese método desde el servidor. El siguiente código de ejemplo muestra un método de desconexión para un cliente JavaScript usando el proxy generado.

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

Advertencia

Seguridad: ni este método para desconectar clientes ni la API integrada propuesta abordarán el escenario de clientes hackeados que ejecutan código malintencionado, ya que los clientes podrían volver a conectarse o el código hackeado podría quitar el método stopClient o cambiar lo que hace. El lugar adecuado para implementar la protección contra la denegación de servicio (DOS) con estado no es el marco ni la capa del servidor, sino la infraestructura del front-end.

Detección del motivo de una desconexión

SignalR 2.1 agrega una sobrecarga al evento de servidor OnDisconnect que indica si el cliente se desconecta deliberadamente en lugar de agotar el tiempo de espera. El parámetro StopCalled es "true" si el cliente cerró a propósito la conexión. En JavaScript, si un error de servidor ha llevado al cliente a desconectarse, la información de error se transmitirá al cliente como $.connection.hub.lastError.

Código de servidor de C#: parámetro stopCalled

public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled)
{
    if (stopCalled)
    {
        Console.WriteLine(String.Format("Client {0} explicitly closed the connection.", Context.ConnectionId));
    }
    else
    {
        Console.WriteLine(String.Format("Client {0} timed out .", Context.ConnectionId));
    }
            
    return base.OnDisconnected(stopCalled);
}

Código de cliente JavaScript: acceso a lastError en el disconnect evento.

$.connection.hub.disconnected(function () {
    if ($.connection.hub.lastError) 
        { alert("Disconnected. Reason: " +  $.connection.hub.lastError.message); }
});