Tutorial: Creación de una aplicación en tiempo real de alta frecuencia con SignalR 2

En este tutorial se muestra cómo crear una aplicación web que usa ASP.NET SignalR 2 para proporcionar funcionalidad de mensajería de alta frecuencia. En este caso, "mensajería de alta frecuencia" significa que el servidor envía actualizaciones a una velocidad fija. Envía hasta 10 mensajes por segundo.

La aplicación que crea muestra una forma que los usuarios pueden arrastrar. El servidor actualiza la posición de la forma en todos los exploradores conectados para que coincidan con la posición de la forma arrastrada mediante actualizaciones con tiempo.

Los conceptos introducidos en este tutorial tienen aplicaciones en juegos en tiempo real y otras aplicaciones de simulación.

En este tutorial, hizo lo siguiente:

  • Configuración del proyecto
  • Creación de la aplicación base
  • Asignación al centro cuando se inicia la aplicación
  • Adición del cliente
  • Ejecución de la aplicación
  • Adición del bucle de cliente
  • Adición del bucle de servidor
  • Adición de animación suave

Advertencia

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

Requisitos previos

Configuración del proyecto

En esta sección, creará el proyecto en Visual Studio 2017.

En esta sección se muestra cómo usar Visual Studio 2017 para crear una aplicación web de ASP.NET vacía y agregar las bibliotecas SignalR y jQuery.UI.

  1. En Visual Studio, cree una aplicación web de ASP.NET.

    Create web

  2. En la ventana Nueva aplicación web ASP.NET - MoveShapeDemo, deje Vacío seleccionado y seleccione Aceptar.

  3. En el Explorador de soluciones, haga clic con el botón derecho en el proyecto y seleccione Agregar>nuevo elemento.

  4. En Agregar nuevo elemento - MoveShapeDemo, seleccione Instalado>Visual C#>Web>SignalR y, a continuación, seleccione Clase de concentrador de SignalR (v2).

  5. Asigne a la clase el nombre MoveShapeHub y agréguela al proyecto.

    Este paso crea el archivo de clase MoveShapeHub.cs. Simultáneamente, agrega un conjunto de archivos de script y referencias de ensamblado que admiten SignalR al proyecto.

  6. Seleccione Herramientas>Administrador de paquetes NuGet>Consola del Administrador de paquetes.

  7. En la consola del Administrador de paquetes, ejecute este comando:

    Install-Package jQuery.UI.Combined
    

    El comando instala la biblioteca jQuery UI. Se usa para animar la forma.

  8. En Explorador de soluciones expanda el nodo Scripts.

    Script library references

    Las bibliotecas de scripts para jQuery, jQueryUI y SignalR están visibles en el proyecto.

Creación de la aplicación base

En esta sección, creará una aplicación de explorador. La aplicación envía la ubicación de la forma al servidor durante cada evento de movimiento del mouse. El servidor difunde esta información a todos los demás clientes conectados en tiempo real. Podrá obtener más información sobre esta aplicación en secciones posteriores.

  1. Abra el archivo MoveShapeHub.cs.

  2. Reemplace el código del archivo MoveShapeHub.cs por este código:

    using Microsoft.AspNet.SignalR;
    using Newtonsoft.Json;
    
    namespace MoveShapeDemo
    {
        public class MoveShapeHub : Hub
        {
            public void UpdateModel(ShapeModel clientModel)
            {
                clientModel.LastUpdatedBy = Context.ConnectionId;
                // Update the shape model within our broadcaster
                Clients.AllExcept(clientModel.LastUpdatedBy).updateShape(clientModel);
            }
        }
        public class ShapeModel
        {
            // We declare Left and Top as lowercase with 
            // JsonProperty to sync the client and server models
            [JsonProperty("left")]
            public double Left { get; set; }
            [JsonProperty("top")]
            public double Top { get; set; }
            // We don't want the client to get the "LastUpdatedBy" property
            [JsonIgnore]
            public string LastUpdatedBy { get; set; }
        }
    }
    
  3. Guarde el archivo.

La clase MoveShapeHub es una implementación de un centro de SignalR. Como en el tutorial Introducción a SignalR, el centro tiene un método al que los clientes llaman directamente. En este caso, el cliente envía un objeto con las nuevas coordenadas X e Y de la forma al servidor. Esas coordenadas se transmiten a todos los demás clientes conectados. SignalR serializa automáticamente este objeto mediante JSON.

La aplicación envía el objeto ShapeModel al cliente. Tiene miembros para almacenar la posición de la forma. La versión del objeto en el servidor también tiene un miembro para realizar un seguimiento de los datos del cliente que se almacenan. Este objeto impide que el servidor devuelva los datos de un cliente a sí mismo. Este miembro usa el atributo JsonIgnore para evitar que la aplicación serialice los datos y los devuelva al cliente.

Asignación al centro cuando se inicia la aplicación

A continuación, configurará la asignación al centro cuando se inicie la aplicación. En SignalR 2, al agregar una clase de inicio de OWIN se crea la asignación.

  1. En el Explorador de soluciones, haga clic con el botón derecho en el proyecto y seleccione Agregar>nuevo elemento.

  2. En Agregar nuevo elemento - MoveShapeDemo seleccione Instalado>Visual C#>Web y, a continuación, seleccione Clase de startup de OWIN.

  3. Asigne a la clase el nombre Startup y seleccione Aceptar.

  4. Sustituya el código predeterminado del archivo Startup.cs por este código:

    using Microsoft.Owin;
    using Owin;
    
    [assembly: OwinStartup(typeof(MoveShapeDemo.Startup))]
    namespace MoveShapeDemo
    {
        public class Startup
        {
            public void Configuration(IAppBuilder app)
            {
                // Any connection or hub wire up and configuration should go here
                app.MapSignalR();
            }
        }
    }
    

La clase de inicio de OWIN llama a MapSignalR cuando la aplicación ejecuta el método Configuration. La aplicación agrega la clase al proceso de inicio de OWIN mediante el atributo de ensamblado OwinStartup.

Adición del cliente

Agregue la página HTML para el cliente.

  1. En el Explorador de soluciones, haga clic con el botón derecho en el proyecto y seleccione Agregar>página HTML.

  2. Asigne a la página el nombre Default y seleccione Aceptar.

  3. En el Explorador de soluciones, haga clic con el botón derecho en Default.html y seleccione Establecer como página de inicio.

  4. Reemplace el código predeterminado del archivo Default.html por este código:

    <!DOCTYPE html>
    <html>
    <head>
        <title>SignalR MoveShape Demo</title>
        <style>
            #shape {
                width: 100px;
                height: 100px;
                background-color: #FF0000;
            }
        </style>
    </head>
    <body>
    <script src="Scripts/jquery-1.10.2.min.js"></script>
    <script src="Scripts/jquery-ui-1.10.4.min.js"></script>
    <script src="Scripts/jquery.signalR-2.1.0.js"></script>
    <script src="/signalr/hubs"></script>
    <script>
     $(function () {
                var moveShapeHub = $.connection.moveShapeHub,
                $shape = $("#shape"),
                shapeModel = {
                    left: 0,
                    top: 0
                };
                moveShapeHub.client.updateShape = function (model) {
                    shapeModel = model;
                    $shape.css({ left: model.left, top: model.top });
                };
                $.connection.hub.start().done(function () {
                    $shape.draggable({
                        drag: function () {
                            shapeModel = $shape.offset();
                            moveShapeHub.server.updateModel(shapeModel);
                        }
                    });
                });
            });
    </script>
        
        <div id="shape" />
    </body>
    </html>
    
  5. En el Explorador de soluciones, expanda Scripts.

    Las bibliotecas de scripts para jQuery y SignalR están visibles en el proyecto.

    Importante

    El administrador de paquetes instala una versión más reciente de los scripts de SignalR.

  6. Actualice las referencias de script en el bloque de código para que se correspondan con las versiones de los archivos de script del proyecto.

Este código HTML y JavaScript crea un div rojo denominado shape. Habilita el comportamiento de arrastre de la forma mediante la biblioteca jQuery y usa el evento drag para enviar la posición de la forma al servidor.

Ejecución de la aplicación

Puede ejecutar la aplicación para ver cómo funciona. Al arrastrar la forma alrededor de una ventana del explorador, la forma también se mueve en los demás exploradores.

  1. En la barra de herramientas, active Depuración de scripts y, a continuación, seleccione el botón de reproducción para ejecutar la aplicación en modo de depuración.

    Screenshot of user turning on debugging mode and selecting play.

    Se abre una ventana del explorador con la forma roja en la esquina superior derecha.

  2. Copie la dirección URL de la página.

  3. Abra otro explorador y pegue la dirección URL en la barra de direcciones.

  4. Arrastre la forma en una de las ventanas del explorador. La forma de la otra ventana del explorador sigue.

Aunque las funciones de aplicación usan este método, no es un modelo de programación recomendado. No hay ningún límite superior para el número de mensajes que se envían. Como resultado, los clientes y el servidor se sobrecargan con mensajes y el rendimiento se degrada. Además, la aplicación muestra una animación incorrecta en el cliente. Esta animación incorrecta se produce porque la forma se mueve al instante según cada método. Es mejor si la forma se mueve suavemente a cada nueva ubicación. A continuación, aprenderá a corregir esos problemas.

Adición del bucle de cliente

El envío de la ubicación de la forma en cada evento de movimiento del mouse crea una cantidad innecesaria de tráfico de red. La aplicación debe limitar los mensajes del cliente.

Use la función de javascript setInterval para configurar un bucle que envíe nueva información de posición al servidor a una velocidad fija. Este bucle es una representación básica de un "bucle de juego". Es una función llamada repetidamente que impulsa toda la funcionalidad de un juego.

  1. Reemplace el código de cliente en el archivo Default.html por este código:

    <!DOCTYPE html>
    <html>
    <head>
    <title>SignalR MoveShape Demo</title>
    <style>
        #shape {
            width: 100px;
            height: 100px;
            background-color: #FF0000;
        }
    </style>
    </head>
    <body>
    <script src="Scripts/jquery-1.10.2.min.js"></script>
    <script src="Scripts/jquery-ui-1.10.4.min.js"></script>
    <script src="Scripts/jquery.signalR-2.1.0.js"></script>
    <script src="/signalr/hubs"></script>
    <script>
        $(function () {
            var moveShapeHub = $.connection.moveShapeHub,
                $shape = $("#shape"),
                // Send a maximum of 10 messages per second 
                // (mouse movements trigger a lot of messages)
                messageFrequency = 10, 
                // Determine how often to send messages in
                // time to abide by the messageFrequency
                updateRate = 1000 / messageFrequency, 
                shapeModel = {
                    left: 0,
                    top: 0
                },
                moved = false;
            moveShapeHub.client.updateShape = function (model) {
                shapeModel = model;
                $shape.css({ left: model.left, top: model.top });
            };
            $.connection.hub.start().done(function () {
                $shape.draggable({
                    drag: function () {
                        shapeModel = $shape.offset();
                        moved = true;
                    }
                });
                // Start the client side server update interval
                setInterval(updateServerModel, updateRate);
            });
            function updateServerModel() {
                // Only update server if we have a new movement
                if (moved) {
                    moveShapeHub.server.updateModel(shapeModel);
                    moved = false;
                }
            }
        });
    </script>
       
    <div id="shape" />
    </body>
    </html>
    

    Importante

    Debe reemplazar las referencias de script de nuevo. Deben coincidir con las versiones de los scripts del proyecto.

    Este nuevo código agrega la función updateServerModel. Se llama en una frecuencia fija. La función envía los datos de posición al servidor cada vez que la marca moved indica que hay nuevos datos de posición que se van a enviar.

  2. Selección de botón Reproducir para iniciar la aplicación

  3. Copie la dirección URL de la página.

  4. Abra otro explorador y pegue la dirección URL en la barra de direcciones.

  5. Arrastre la forma en una de las ventanas del explorador. La forma de la otra ventana del explorador sigue.

Dado que la aplicación limita el número de mensajes que se envían al servidor, la animación no aparecerá tan bien como al principio.

Adición del bucle de servidor

En la aplicación actual, los mensajes enviados desde el servidor al cliente salen con tanta frecuencia como se reciben. Este tráfico de red presenta un problema similar al que vemos en el cliente.

La aplicación puede enviar mensajes con más frecuencia de lo necesario. Como resultado, la conexión puede verse sobrecargada. En esta sección se describe cómo actualizar el servidor para agregar un temporizador que limita la velocidad de los mensajes salientes.

  1. Reemplace el contenido de MoveShapeHub.cs por el código siguiente:

    using System;
    using System.Threading;
    using Microsoft.AspNet.SignalR;
    using Newtonsoft.Json;
    
    namespace MoveShapeDemo
    {
        public class Broadcaster
        {
            private readonly static Lazy<Broadcaster> _instance = 
                new Lazy<Broadcaster>(() => new Broadcaster());
            // We're going to broadcast to all clients a maximum of 25 times per second
            private readonly TimeSpan BroadcastInterval = 
                TimeSpan.FromMilliseconds(40); 
            private readonly IHubContext _hubContext;
            private Timer _broadcastLoop;
            private ShapeModel _model;
            private bool _modelUpdated;
            public Broadcaster()
            {
                // Save our hub context so we can easily use it 
                // to send to its connected clients
                _hubContext = GlobalHost.ConnectionManager.GetHubContext<MoveShapeHub>();
                _model = new ShapeModel();
                _modelUpdated = false;
                // Start the broadcast loop
                _broadcastLoop = new Timer(
                    BroadcastShape, 
                    null, 
                    BroadcastInterval, 
                    BroadcastInterval);
            }
            public void BroadcastShape(object state)
            {
                // No need to send anything if our model hasn't changed
                if (_modelUpdated)
                {
                    // This is how we can access the Clients property 
                    // in a static hub method or outside of the hub entirely
                    _hubContext.Clients.AllExcept(_model.LastUpdatedBy).updateShape(_model);
                    _modelUpdated = false;
                }
            }
            public void UpdateShape(ShapeModel clientModel)
            {
                _model = clientModel;
                _modelUpdated = true;
            }
            public static Broadcaster Instance
            {
                get
                {
                    return _instance.Value;
                }
            }
        }
            
        public class MoveShapeHub : Hub
        {
            // Is set via the constructor on each creation
            private Broadcaster _broadcaster;
            public MoveShapeHub()
                : this(Broadcaster.Instance)
            {
            }
            public MoveShapeHub(Broadcaster broadcaster)
            {
                _broadcaster = broadcaster;
            }
            public void UpdateModel(ShapeModel clientModel)
            {
                clientModel.LastUpdatedBy = Context.ConnectionId;
                // Update the shape model within our broadcaster
                _broadcaster.UpdateShape(clientModel);
            }
        }
        public class ShapeModel
        {
            // We declare Left and Top as lowercase with 
            // JsonProperty to sync the client and server models
            [JsonProperty("left")]
            public double Left { get; set; }
            [JsonProperty("top")]
            public double Top { get; set; }
            // We don't want the client to get the "LastUpdatedBy" property
            [JsonIgnore]
            public string LastUpdatedBy { get; set; }
        }
        
    }
    
  2. Seleccione el botón Reproducir para iniciar la aplicación.

  3. Copie la dirección URL de la página.

  4. Abra otro explorador y pegue la dirección URL en la barra de direcciones.

  5. Arrastre la forma en una de las ventanas del explorador.

Este código expande el cliente para agregar la clase Broadcaster. La nueva clase limita los mensajes salientes mediante la clase Timer de .NET Framework.

Es bueno saber que el propio centro es transitorio. Se crea cada vez que es necesario. Por lo tanto, la aplicación crea Broadcaster como singleton. Usa la inicialización diferida para aplazar la creación de Broadcaster hasta que sea necesario. Esto garantiza que la aplicación crea la primera instancia del centro completamente antes de iniciar el temporizador.

A continuación, la llamada a la función UpdateShape de los clientes se mueve fuera del método del centro UpdateModel. Ya no se llama inmediatamente cuando la aplicación recibe mensajes entrantes. En su lugar, la aplicación envía los mensajes a los clientes a una velocidad de 25 llamadas por segundo. El temporizador administra el proceso _broadcastLoop desde dentro de la clase Broadcaster.

Por último, en lugar de llamar al método de cliente desde el centro directamente, la clase Broadcaster debe obtener una referencia al centro operativo actualmente _hubContext. Obtiene la referencia con GlobalHost.

Adición de animación suave

La aplicación está casi terminada, pero podríamos mejorarla un poco más. La aplicación mueve la forma en el cliente en respuesta a los mensajes del servidor. En lugar de establecer la posición de la forma en la nueva ubicación dada por el servidor, use la función animate de la biblioteca JQuery UI. Puede mover la forma sin problemas entre su posición actual y nueva.

  1. Actualice el método del cliente updateShape en el archivo Default.html para que tenga un aspecto similar al código resaltado:

    <!DOCTYPE html>
    <html>
    <head>
        <title>SignalR MoveShape Demo</title>
        <style>
            #shape {
                width: 100px;
                height: 100px;
                background-color: #FF0000;
            }
        </style>
    </head>
    <body>
    <script src="Scripts/jquery-1.10.2.min.js"></script>
    <script src="Scripts/jquery-ui-1.10.4.min.js"></script>
    <script src="Scripts/jquery.signalR-2.1.0.js"></script>
    <script src="/signalr/hubs"></script>
    <script>
            $(function () {
                var moveShapeHub = $.connection.moveShapeHub,
                    $shape = $("#shape"),
                    // Send a maximum of 10 messages per second 
                    // (mouse movements trigger a lot of messages)
                    messageFrequency = 10, 
                    // Determine how often to send messages in
                    // time to abide by the messageFrequency
                    updateRate = 1000 / messageFrequency, 
                    shapeModel = {
                        left: 0,
                        top: 0
                    },
                    moved = false;
                moveShapeHub.client.updateShape = function (model) {
                     shapeModel = model;
                     // Gradually move the shape towards the new location (interpolate)
                     // The updateRate is used as the duration because by the time 
                     // we get to the next location we want to be at the "last" location
                     // We also clear the animation queue so that we start a new 
                     // animation and don't lag behind.
                     $shape.animate(shapeModel, { duration: updateRate, queue: false });
                };
                $.connection.hub.start().done(function () {
                    $shape.draggable({
                        drag: function () {
                            shapeModel = $shape.offset();
                            moved = true;
                        }
                    });
                    // Start the client side server update interval
                    setInterval(updateServerModel, updateRate);
                });
                function updateServerModel() {
                    // Only update server if we have a new movement
                    if (moved) {
                        moveShapeHub.server.updateModel(shapeModel);
                        moved = false;
                    }
                }
            });
    </script>
       
        <div id="shape" />
    </body>
    </html>
    
  2. Seleccione el botón Reproducir para iniciar la aplicación.

  3. Copie la dirección URL de la página.

  4. Abra otro explorador y pegue la dirección URL en la barra de direcciones.

  5. Arrastre la forma en una de las ventanas del explorador.

El movimiento de la forma en la otra ventana aparece más correcta. La aplicación interpola su movimiento con el tiempo, en lugar de establecerse una vez por mensaje entrante.

Este código mueve la forma de la ubicación antigua a la nueva. El servidor proporciona la posición de la forma durante el intervalo de animación. En este caso, es de 100 milisegundos. La aplicación borra cualquier animación anterior que se ejecute en la forma antes de que se inicie la nueva animación.

Obtención del código

Descargar el proyecto completado

Recursos adicionales

El paradigma de comunicación que acaba de aprender es útil para desarrollar juegos en línea y otras simulaciones, como el juego ShootR creado con SignalR.

Para más información sobre SignalR, consulte los siguientes recursos:

Pasos siguientes

En este tutorial ha:

  • Configuración del proyecto
  • Se ha creado la aplicación base
  • Se ha asignado al centro cuando se inicia la aplicación
  • Se ha agregado el cliente
  • Se ha ejecutado la aplicación
  • Se ha agregado el bucle de cliente
  • Se ha agregado el bucle de servidor
  • Se ha agregado la animación suave

Vaya al siguiente artículo para aprender a crear una aplicación web que use ASP.NET SignalR 2 para proporcionar funcionalidad de difusión de servidores.