Julio de 2018

Volumen 33, número 7

Tecnología de vanguardia: usuarios en línea, streaming y otros extras de SignalR

Por Dino Esposito | Julio de 2018

Dino EspositoSi alguna vez usó cualquier versión de SignalR para la plataforma ASP.NET clásica, debe ser bastante familiarizado con el concepto de un concentrador. En SignalR un concentrador es el componente que permite que las aplicaciones cliente y servidor compatibles organizar las llamadas a procedimiento remoto bidireccional, de cliente al servidor y del servidor a los clientes conectados.

En concreto, en términos de software, un concentrador es una clase que hereda de una clase base proporcionado por el sistema y expone los puntos de conexión para los clientes a llamar. En términos conceptuales, tiene unos cuantos puntos en común con un controlador de ASP.NET. En concreto, es la fachada que recibe las llamadas del cliente y reacciona ante ellas y se organiza en torno a un número relativamente pequeño de funciones relacionadas. En ASP.NET Core SignalR, la similitud entre las controladoras y concentradores es, de una manera incluso más cerca. Este es mi tercer artículo acerca de ASP.NET Core SignalR para la columna Cutting Edge, y en ninguno de los dos artículos anteriores ha utilizado una clase de hub vacío. Explicar un poco más sobre los concentradores de SignalR y cómo se implementan en ASP.NET Core es uno de los propósitos de este artículo. Sin embargo, también me referiré a otros aspectos interesantes de SignalR, específicamente datos de transmisión por secuencias y el recuento de usuarios en línea.

Concentradores de SignalR de ASP.NET Core

Un concentrador es el punto de entrada en una canalización de mensajes a través del cual los clientes conectados y servidores intercambian mensajes. Concentradores exponen sus métodos como direcciones URL y procesan los parámetros recibidos a través de enlace de modelos, en la misma forma que un controlador canónico hace. De forma, un concentrador es un controlador dedicado que funciona con dos protocolos integrados. El protocolo predeterminado consta de un objeto JSON, pero se puede usar otro protocolo binario que se basa en MessagePack. Tenga en cuenta que con el fin de admitir MessagePack, los exploradores deben admitir XHR nivel 2. Como nivel 2 se introdujo en 2012, esto podría no ser gran parte de un problema hoy en día, pero si la aplicación requiere compatibilidad con exploradores más antiguos muy merecería la pena mencionar. Aquí se puede realizar una comprobación rápida del explorador: caniuse.com/#feat=xhr2.

Si cualquiera de los clientes conectados solicitan un punto de conexión de SignalR, el concentrador se invoca directamente por el motor en tiempo de ejecución de SignalR. La conversación realiza a través del protocolo seleccionado, principalmente, es probable que WebSockets. Para invocar el servidor, los clientes deben contener un objeto de conexión. Un cliente Web obtendría lo siguiente:

var clockConnection = new signalR.HubConnection("/clockDemo");
clockConnection.start().then(() => {
     clockConnection.invoke("now");
  });

Un cliente invoca un punto de conexión de servidor a través del método "invocar" en el objeto de conexión. Tenga en cuenta que la sintaxis exacta puede variar dependiendo de la tecnología real usada para el cliente. El servidor responde a través de los métodos definidos en la clase base del concentrador y la conversación realiza a través del protocolo de transporte de elección, con mayor frecuencia WebSockets, similar al siguiente:

public class ClockHub : Hub
{
  public Task Now()
   {
    var now = DateTime.UtcNow.ToShortTimeString();
    return Clients.All.SendAsync("now", now);
  }
}

Tenga en cuenta que no podrá supervisar las distintas llamadas mediante una herramienta HTTP como Fiddler. Necesita algo parecido a las herramientas para desarrolladores de Chrome. En todos los ejemplos que escribí para Mis últimas columnas en SignalR, sin embargo, siempre usé una clase de hub vacío.

La clase hub es la fachada de SignalR oficial para recibir llamadas de cliente y la forma más rápida para la comunicación cliente/servidor que tenga lugar debido a la canalización dedicada. Cuando se produce la llamada a través del concentrador, el tiempo de ejecución de SignalR puede realizar un seguimiento y exponer toda la información disponible a través de las propiedades de la clase base del concentrador. De este modo, el identificador de conexión de SignalR y todo el contexto de la llamada, incluidas las notificaciones del usuario autenticado, están disponibles.

Además, pueden controlar a los desarrolladores a través del concentrador, conectar y desconectar eventos. Siempre que es configurar una nueva conexión, o si dejaron de serlo, un método de concentrador devolver la llamada. Si usa SignalR solo como una manera de supervisar las operaciones de larga ejecución remotas, también puede desencadenar la tarea a través de un controlador simple e inyectar un contexto del concentrador en él para las notificaciones. O bien, como alternativa, puede invocar el concentrador y desencadenar la tarea desde el concentrador. Es su elección. SignalR funciona como un marco de trabajo de extremo a otro, un puente entre el cliente y el servidor. Codificar la lógica en el concentrador es aceptable siempre y cuando el trabajo no profundice en las capas del código. En caso contrario, vaya a través del controlador, si MVC o API Web e inyectar un contexto del concentrador.

La única diferencia entre usar un concentrador o un controlador es que SignalR no puede realizar un seguimiento el identificador de conexión cuando una solicitud pasa por el controlador. Si el identificador de conexión es relevante para la tarea del servidor, tiene que pasar de alguna manera a través de la dirección URL. El controlador mediante el contexto de solicitud HTTP se puede recuperar toda la demás información que constituye el contexto del llamador de SignalR.

Recuento de usuarios en línea

Algunas aplicaciones Web le resulte útil, o simplemente atractivas para los usuarios, para mostrar el número de conexiones que están actualmente activo. El problema no es el seguimiento de tanta cuando un usuario se conecta, hay muchos puntos de conexión a través del cual puede detectar que, pero en su lugar cuando un usuario se desconecta de la aplicación.

Puede auditar una página de inicio de sesión o el paso posterior a la autenticación. Puede colocar una comprobación en alguna clase de controlador base o en cualquiera de las páginas que puede visitar el usuario. En general, siempre puede encontrar una manera de detectar cuándo un usuario se conecta a la aplicación. El problema es cómo el usuario puede dejar la aplicación, ya sea por el cierre de sesión (fácilmente puede trazar) o salir de la página o cerrando la ventana del explorador. No hay ninguna forma confiable para detectar cuando el usuario cierra la ventana del explorador. Sí, los exploradores suelen desencadenan el evento de beforeunload cuando se cierra el explorador, pero este mismo evento también se desencadena siempre que siga un vínculo, incluso cuando ese vínculo es la misma aplicación. Por lo que no es una solución perfecta.

Es una manera mucho más confiable para contar los usuarios realizar un seguimiento de las conexiones de ASP.NET Core SignalR. Para ello, necesita un concentrador completamente funcional con la conexión configurar a través de él. Cuando el usuario salga del explorador, o simplemente la aplicación, la conexión está publicados y a la escuchas de los clientes una notificación. Al igual que en ASP.NET Core SignalR, no hay ninguna compatibilidad para reconexiones automática, por lo que las cosas son incluso más fáciles. Todo lo que hacer es definir una variable estática global en el concentrador e incrementar su valor arriba o hacia abajo cuando un usuario se conecta o desconecta, como se muestra en figura 1. El tiempo de ejecución de SignalR en ASP.NET Core garantiza que cada conexión se cierra en algún momento, y cualquier nueva conexión eficazmente hace referencia a una nueva conexión. En pocas palabras, el número Obtenga es altamente confiable.

Figura 1 las conexiones de recuento

public class SampleHub : Hub
{
  private static int Count = 0;
  public override Task OnConnectedAsync()
  {
    Count++;
    base.OnConnectedAsync();
    Clients.All.SendAsync("updateCount", Count);
    return Task.CompletedTask;
  }
  public override Task OnDisconnectedAsync(Exception exception)
  {
    Count--;
    base.OnDisconnectedAsync(exception);
    Clients.All.SendAsync("updateCount", Count);
    return Task.CompletedTask;
  }
}

Hay una desventaja para el recuento de usuarios con SignalR: Solo funciona si los usuarios visitan la página que establece una conexión con el concentrador donde recuento realiza. Para que sea seguro, debe tener un cliente de SignalR en casi cualquier página que el usuario puede visitar. Esto es especialmente cierto si considera que normalmente el número de usuarios en línea es un valor visible globalmente, es probable que tenga en todos los diseños en el que se basan las vistas.

Tenga en cuenta que en el ejemplo de código de hub, la clase devuelve la llamada a los clientes conectados cada vez que se creó o se cierra una conexión. Tenga en cuenta también que en este modo solo tiene el número total de usuarios, pero no en la lista de identificadores de conexión o, en caso de los usuarios autenticados, la lista de nombres de usuario. Para lograr esto, mejor utilizar un diccionario en lugar de un contador global y agregarle las entradas con identificadores de conexión o notificaciones, como el nombre de usuario.

Otro aspecto que considerar en relación con el código en figura 1 es el uso de una variable estática para contar los usuarios. Una variable estática es por servidor, lo que significa que cuando el escalado horizontal, deberá tener en cuenta cómo almacenar el estado compartido en una ubicación accesible globalmente, como una base de datos o una memoria caché distribuida.

Volver a insertar información

Desde dentro el concentrador o el contexto del concentrador si se conecta al back-end a través de un método de controlador, tendrá muchas maneras diferentes de los clientes conectados de devolución de llamada. Todos los métodos son los miembros expuestos por el objeto de los clientes que, a pesar del nombre, no es una colección, pero una instancia de la clase IClientProxy. Las expresiones de figura 2 indican que el objeto desde el que se invoca el método SendAsync. El método SendAsync toma el nombre del método del cliente para volver a llamar y parámetros que se pasan.

Figura 2 métodos para el servidor de llamada de clientes conectados atrás

Expression Descripción
Clients.All La notificación es de difusión a todos los clientes, independientemente de la tecnología utilizada (Web. NET, .NET Core, Xamarin).
Clients.Client(connectionId) La notificación se envía exclusivamente para el cliente escucha a través de la conexión especificada.
Clients.User(userId) La notificación se envía a todos los clientes cuyo usuario autenticado coincide con el nombre de usuario proporcionado.
Clients.Groups(group) La notificación se envía a todos los clientes que pertenecen al grupo especificado.

 

Un grupo es una colección de clientes relacionados colectivamente recopilada en un nombre. La manera más natural de pensar en grupos en SignalR es salones de chat. Se crea un grupo mediante programación simplemente agregando identificadores de conexión para el grupo de un nombre determinado. Del siguiente modo:

hub.Groups.AddAsync(connectionId, nameOfGroup);

Los clientes conectados reciben sus datos a través de una devolución de llamada. Esto es sólo la técnica más común. En ASP.NET Core SignalR, también puede usar la transmisión por secuencias.

Streaming de datos

Probablemente el aspecto más interesante nueva de SignalR es compatible con transmisión por secuencias. Transmisión por secuencias es similar a la difusión, pero sigue un modelo ligeramente diferente y es básicamente una forma ligeramente diferente de conseguir la misma comunicación de estilo de emisión. Con SignalR de transmisión por secuencias, el concentrador sigue necesitando sondear o escuchan los datos para transmitirlo atrás. En la difusión clásico, el servidor indica un método de cliente cuando hay nuevos datos disponibles.

En el nuevo modelo de transmisión por secuencias, el cliente se suscribe a un nuevo objeto de servidor del tipo de canal y el servidor, el concentrador, realmente, produce los elementos nuevos como que se capturen. En este momento, no hay nada como un verdadero transmitir esa bytes flujos hacia todos los clientes conectados cuando estén disponibles, pero este modelo puede admitirse en el futuro. Tenga en cuenta que el tipo de canal se ha introducido con preview2 y no se admite en las compilaciones anteriores. Las compilaciones anteriores, debe usar objetos observables en su lugar, que requieren una referencia al paquete System.Reactive.Linq NuGet. El cambio entre objetos observables y el nuevo tipo de canal se relaciona con la falta de primitivas de IObservable para trabajar con una resistencia de la red (que es, indica al servidor que se ralentice cuando el cliente no está procesando mensajes lo suficientemente rápidos).

Figura 3 presenta el código para el concentrador.

Figura 3. la clase de concentrador que transmita de vuelta

public class ClockHub : Hub
{
  private static bool _clockRunning = false;
  public void Start()
  {
    _clockRunning = true;
    Clients.All.SendAsync("clockStarted");
  }
  public void Stop()
  {
    _clockRunning = false;
    Clients.All.SendAsync("clockStopped");
  }
  public ChannelReader<string> Tick()
  {    var channel = Channel.CreateUnbounded<string>();
    Task.Run(async() => {
      while(_clockRunning)
      {
        var time = DateTime.UtcNow.ToString("HH:mm:ss");
        await channel.Writer.WriteAsync(time);
        await Task.Delay(1000);
      }
      channel.Writer.TryComplete();    });
  }
}

El concentrador ofrece tres métodos para iniciar, detener y operar el reloj. Una variable global controla el estado de ejecución de la transmisión por secuencias y el inicio y detención métodos establecer la variable de control y notificar a los métodos de espera del cliente como de costumbre en un concentrador SignalR. La parte complicada es el método de graduación. El nombre del método coincide con el nombre de la secuencia a la que se suscribirán los clientes. El método devuelve un objeto de canal de un tipo determinado. En el ejemplo, el tipo es una cadena simple, pero puede ser lo más sofisticadas.

Cada invocación del cliente al servidor o servidor a cliente, consta de una entidad que se envía un mensaje de invocación y la otra parte finalmente responde con un mensaje de finalización que lleva a un resultado o un error. En un escenario de streaming de SignalR, en su lugar, la otra parte responde con varios mensajes, cada uno de los que lleva a un elemento de datos, antes de concluir, finalmente, la comunicación con un mensaje de finalización. Como resultado, el cliente finaliza el procesamiento de varios elementos incluso antes de que se recibe el mensaje de finalización.

Escalado a varias instancias

SignalR mantiene todos los identificadores de conexión en la memoria, lo que significa que el momento en que la aplicación se escala a varias instancias, la difusión (pero también de transmisión por secuencias, como se describe más adelante) se ve comprometido, ya que cada instancia sólo podría realizar un seguimiento de una parte de todos los clientes conectados. Para evitar esto, SignalR es compatible con una caché de Redis que garantiza que las nuevas conexiones se comparten automáticamente entre las instancias. Para habilitar Redis, se necesita el paquete SignalR.Redis y una llamada al método AddRedis en el método ConfigureServices de la clase startup, así:

services.AddSignalR()
        .AddRedis("connection string");

El parámetro de opción tiene la finalidad de la especificación de la cadena de conexión a la instancia en ejecución de Redis.

Resumen

SignalR de ASP.NET Core incluye dos cambios significativos de la versión no esenciales. Uno es la falta de reconexión automática, que tiene un impacto en cómo contar el usuario en línea y conectar o desconectar acceso se controla mediante programación. Esto significa que ahora tiene que controlar la lógica de conexión/desconexión de todas las aplicaciones y es probable que tenga que identificar la diferencia entre un usuario se conecta por primera vez y el usuario volver a conectarse debido a un error. El otro cambio es la compatibilidad con streaming de datos. Transmisión de datos se basa en los canales y en este momento solo admite elementos de datos específicos en lugar de secuencias sin formato.

Por último, mi exploración de SignalR carece de una parte más, que abordaré en una columna futura: autentica a los usuarios y grupos.


Dino Esposito ha escrito más de 20 libros y 1000 artículos en su carrera de 25 años. Autor de “The Sabbatical Break”, un espectáculo de estilo teatral, Esposito se dedica a escribir software para un mundo más verde como estratega digital de BaxEnergy. Puede seguirle en Twitter: @despos.

Gracias al siguiente experto de Microsoft por su ayuda en la revisión de este artículo: Andrew Stanton-Nurse


Discuta sobre este artículo en el foro de MSDN Magazine