Alta frecuencia en tiempo real con SignalR 1.x

por Patrick Fletcher

Advertencia

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

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

La aplicación que va a crear en este tutorial muestra una forma que los usuarios pueden arrastrar. La posición de la forma en todos los demás exploradores conectados se actualizará para que coincida con la posición de la forma arrastrada mediante actualizaciones programadas.

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

Los comentarios sobre el tutorial son bienvenidos. Si tiene alguna pregunta que no esté directamente relacionadas con el tutorial, puede publicarla en el foro de ASP.NET SignalR o en StackOverflow.com.

Información general

En este tutorial se muestra cómo crear una aplicación que comparte el estado de un objeto con otros exploradores en tiempo real. La aplicación que crearemos se denomina MoveShape. La página MoveShape mostrará un elemento Div HTML que el usuario puede arrastrar; cuando el usuario arrastre el div, su nueva posición se enviará al servidor, que luego indicará a todos los demás clientes conectados que actualicen la posición de la forma para que coincida.

Screenshot showing the MoveShape application page.

La aplicación creada en este tutorial se basa en una demostración de Damian Edwards. Aquí se puede ver un vídeo que contiene esta demostración.

El tutorial comenzará mostrando cómo enviar mensajes de SignalR desde cada evento que se activa a medida que se arrastra la forma. A continuación, cada cliente conectado actualizará la posición de la versión local de la forma cada vez que se recibe un mensaje.

Aunque la aplicación funcionará con este método, no es un modelo de programación recomendado, ya que no habría un límite superior para el número de mensajes que se envían, por lo que los clientes y el servidor podrían sobrecargarse con los mensajes y el rendimiento se degradaría. La animación mostrada en el cliente también se volvería inconexa, ya que la forma sería movida al instante por cada método, en lugar de moverse con fluidez a cada nueva ubicación. En las secciones posteriores del tutorial se muestra cómo crear una función de temporizador que restrinja la velocidad máxima a la que el cliente o el servidor envían los mensajes y cómo mover la forma con fluidez entre ubicaciones. La versión final de la aplicación creada en este tutorial se puede descargar desde la Galería de códigos.

Este tutorial contiene las siguientes secciones:

Requisitos previos

Este tutorial requiere Visual Studio 2012 o Visual Studio 2010. Si usa Visual Studio 2010, el proyecto utilizará .NET Framework 4 en lugar de .NET Framework 4.5.

Si usa Visual Studio 2012, se recomienda instalar la actualización de ASP.NET y Web Tools 2012.2. Esta actualización contiene nuevas características, como mejoras en la publicación, nuevas funcionalidades y plantillas.

Si tiene Visual Studio 2010, asegúrese de que NuGet esté instalado.

Creación del proyecto

En esta sección, crearemos el proyecto en Visual Studio.

  1. En el menú Archivo, haga clic en Nuevo proyecto.

  2. En el cuadro de diálogo Nuevo proyecto, expanda C# en Plantillas y seleccione Web.

  3. Seleccione la plantilla Aplicación web vacía de ASP.NET, nombre el proyecto MoveShapeDemo y haga clic en Aceptar.

    Creating the new project

Agregue los paquetes NuGet de SignalR y JQuery.UI

Puede agregar la funcionalidad de SignalR a un proyecto instalando un paquete NuGet. En este tutorial también se usará el paquete JQuery.UI para permitir que la forma se arrastre y se anime.

  1. Haga clic en Herramientas | Administrador de paquetes NuGet | Consola del administrador de paquetes.

  2. Escriba el siguiente comando en el administrador de paquetes.

    Install-Package Microsoft.AspNet.SignalR -Version 1.1.3
    

    El paquete de SignalR instala una serie de otros paquetes NuGet como dependencias. Una vez finalizada la instalación, tendrá todos los componentes de servidor y cliente necesarios para usar SignalR en una aplicación ASP.NET.

  3. Escriba el siguiente comando en la consola del administrador de paquetes para instalar los paquetes JQuery y JQuery.UI.

    Install-Package jQuery.ui.combined
    

Creación de la aplicación base

En esta sección, vamos a crear una aplicación de explorador que envíe la ubicación de la forma al servidor durante cada evento de movimiento del mouse. Luego, el servidor difunde esta información a todos los demás clientes conectados a medida que se recibe. Vamos a ampliar la información de esta aplicación en secciones posteriores.

  1. En el lExplorador de soluciones, haga clic con el botón derecho en el proyecto y seleccione Agregar, Clase.... Asigne un nombre a la clase MoveShapeHub y haga clic en Agregar.

  2. Reemplace el código de la nueva clase MoveShapeHub por el código siguiente.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Microsoft.AspNet.SignalR;
    using Microsoft.AspNet.SignalR.Hubs;
    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; }
        }
    }
    

    La clase MoveShapeHub anterior 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 enviará un objeto que contiene las nuevas coordenadas X e Y de la forma al servidor, que luego se difundirá a todos los demás clientes conectados. SignalR serializará automáticamente este objeto mediante JSON.

    El objeto que se enviará al cliente (ShapeModel) contiene miembros para almacenar la posición de la forma. La versión del objeto en el servidor también contiene un miembro para realizar un seguimiento de los datos del cliente que se almacenan, de modo que no se envíen a un cliente determinado sus propios datos. Este miembro usa el atributo JsonIgnore para evitar que se serialice y envíe al cliente.

  3. A continuación, configuraremos el centro cuando se inicie la aplicación. En el Explorador de soluciones, haga clic con el botón derecho en el proyecto y, después, en Agregar | Clase de aplicación global. Acepte el nombre predeterminado de Global y haga clic en Aceptar.

    Add Global Application Class

  4. Agregue la siguiente instrucción using después de las instrucciones using proporcionadas en la clase Global.asax.cs.

    using System.Web.Routing;
    
  5. Agregue la siguiente línea de código en el método Application_Start de la clase Global a fin de registrar la ruta predeterminada para SignalR.

    RouteTable.Routes.MapHubs();
    

    El archivo global.asax debe ser similar al siguiente:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Security;
    using System.Web.SessionState;
    
    using System.Web.Routing;
    
    namespace MoveShapeDemo
    {
        public class Global : System.Web.HttpApplication
        {
            protected void Application_Start(object sender, EventArgs e)
            {
                RouteTable.Routes.MapHubs();
            }
        }
    }
    
  6. A continuación, agregaremos el cliente. En el Explorador de soluciones, haga clic con el botón derecho en el proyecto y después haga clic en Agregar | Nuevo elemento. En el cuadro de diálogo Agregar nuevo elemento, seleccione Página Html. Asigne a la página un nombre adecuado (como Default.html) y haga clic en Agregar.

  7. En el Explorador de soluciones, haga clic con el botón derecho en la página que acaba de crear y haga clic en Establecer como página de inicio.

  8. Reemplace el código predeterminado de la página HTML por el siguiente fragmento de código.

    Nota:

    Compruebe que las referencias de script siguientes coincidan con los paquetes agregados al proyecto en la carpeta Scripts. En Visual Studio 2010, es posible que las versiones de JQuery y SignalR agregadas al proyecto no coincidan con los números de versión siguientes.

    <!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.6.4.js"></script>
    <script src="Scripts/jquery-ui-1.10.2.js"></script>
    <script src="Scripts/jquery.signalR-1.0.1.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>
    

    El código HTML y JavaScript anterior crea un Div rojo denominado Shape, habilita el comportamiento de arrastre de la forma mediante la biblioteca jQuery y utiliza el evento drag de la forma para enviar la posición de esta al servidor.

  9. Inicie la aplicación presionando F5. Copie la dirección URL de la página y péguela en una segunda ventana del explorador. Arrastre la forma a una de las ventanas del explorador; la forma de la otra ventana del explorador debe moverse.

    Screenshot showing how a shape you drag in one browser window moves in another window.

Incorporación del bucle de cliente

Como el envío de la ubicación de la forma en cada evento de movimiento del mouse creará una cantidad innecesaria de tráfico de red, se deben limitar los mensajes del cliente. Vamos a utilizar la función setInterval de JavaScript para configurar un bucle que envíe información de la nueva posición al servidor a una velocidad fija. Este bucle es una representación muy básica de un "bucle de juego", una función llamada repetidamente que controla toda la funcionalidad de un juego u otra simulación.

  1. Actualice el código de cliente en la página HTML para que coincida con el siguiente fragmento de 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.6.4.js"></script>
    <script src="Scripts/jquery-ui-1.10.2.js"></script>
    <script src="Scripts/jquery.signalR-1.0.1.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>
    

    La actualización anterior agrega la función updateServerModel, a la que se llama con una frecuencia fija. Esta 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. Inicie la aplicación presionando F5. Copie la dirección URL de la página y péguela en una segunda ventana del explorador. Arrastre la forma a una de las ventanas del explorador; la forma de la otra ventana del explorador debe moverse. Como se va a limitar el número de mensajes que se envían al servidor, la animación no aparecerá tan fluida como en la sección anterior.

    Screenshot showing how a shape you drag in one browser window moves in another window when you add a client loop.

Incorporació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. Esto presenta un problema similar al que se ha visto en el cliente; los mensajes se pueden enviar con más frecuencia de lo que sea necesario y, en consecuencia, la conexión podría desbordarse. En esta sección se describe cómo actualizar el servidor para implementar un temporizador que limita la velocidad de los mensajes salientes.

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

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    
    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; }
        }
        
    }
    

    El código anterior expande el cliente para agregar la clase Broadcaster, que limita los mensajes salientes mediante la clase Timer de .NET Framework.

    Puesto que el propio centro es transitorio (se crea cada vez que sea necesario), Broadcaster se creará como singleton. La inicialización diferida (introducida en .NET 4) se usa para aplazar su creación hasta que sea necesario, lo que garantiza que la primera instancia del centro se cree por completo antes de que se inicie el temporizador.

    La llamada a la función UpdateShape de los clientes se mueve luego fuera del método UpdateModel del centro, de modo que ya no se le llama de inmediato cuando se reciben mensajes entrantes. En su lugar, los mensajes a los clientes se enviarán a una velocidad de 25 llamadas por segundo, administradas por el temporizador _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 actual (_hubContext) mediante GlobalHost.

  2. Inicie la aplicación presionando F5. Copie la dirección URL de la página y péguela en una segunda ventana del explorador. Arrastre la forma a una de las ventanas del explorador; la forma de la otra ventana del explorador debe moverse. No habrá ninguna diferencia visible en el explorador de la sección anterior, pero se limitará el número de mensajes que se envían al cliente.

    Screenshot showing how a shape you drag in one browser window moves in another window when you add a server loop.

Incorporación de una animación fluida en el cliente

La aplicación está casi completa, pero podríamos mejorar algo más en el movimiento de la forma en el cliente a medida que se mueve 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, vamos a utilizar la función animate de la biblioteca de la interfaz de usuario de JQuery para mover la forma con fluidez entre su posición actual y la nueva.

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

    <!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.6.4.js"></script>
    <script src="Scripts/jquery-ui-1.10.2.js"></script>
    <script src="Scripts/jquery.signalR-1.0.1.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>
    

    El código anterior mueve la forma de la ubicación antigua a la nueva dada por el servidor a lo largo del intervalo de animación (en este caso, 100 milisegundos). Cualquier animación anterior que se ejecute en la forma se borra antes de que se inicie la nueva animación.

  2. Inicie la aplicación presionando F5. Copie la dirección URL de la página y péguela en una segunda ventana del explorador. Arrastre la forma a una de las ventanas del explorador; la forma de la otra ventana del explorador debe moverse. El movimiento de la forma en la otra ventana debería aparecer menos entrecortado, ya que su movimiento se interpola a lo largo del tiempo en lugar de establecerse una vez por mensaje entrante.

    Screenshot showing how a shape you drag in one browser window moves in another window when you add smooth animation on the client.

Pasos adicionales

En este tutorial, ha aprendido a programar una aplicación SignalR que envía mensajes de alta frecuencia entre clientes y servidores. Este paradigma de comunicación resulta útil para desarrollar juegos en línea y otras simulaciones, como el juego ShootR creado con SignalR.

La aplicación completa creada en este tutorial se puede descargar desde la Galería de códigos.

Para obtener más información sobre conceptos de desarrollo de SignalR, visite los siguientes sitios para obtener el código fuente y los recursos de SignalR: