Tempo real de alta frequência com SignalR 1.x

por Patrick Fletcher

Aviso

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

Este tutorial mostra como criar um aplicativo Web que usa ASP.NET SignalR para fornecer funcionalidade de mensagens de alta frequência. O sistema de mensagens de alta frequência nesse caso significa atualizações enviadas a uma taxa fixa; no caso desse aplicativo, até 10 mensagens por segundo.

O aplicativo que você criará neste tutorial exibe uma forma que os usuários podem arrastar. A posição da forma em todos os outros navegadores conectados será atualizada para corresponder à posição da forma arrastada usando atualizações cronometradas.

Os conceitos introduzidos neste tutorial têm aplicativos em jogos em tempo real e outros aplicativos de simulação.

Os comentários sobre o tutorial são bem-vindos. Se você tiver perguntas que não estão diretamente relacionadas ao tutorial, poderá postá-las no fórum do ASP.NET SignalR ou StackOverflow.com.

Visão geral

Este tutorial demonstra como criar um aplicativo que compartilha o estado de um objeto com outros navegadores em tempo real. O aplicativo que criaremos se chama MoveShape. A página MoveShape exibirá um elemento Div HTML que o usuário pode arrastar; quando o usuário arrastar o Div, sua nova posição será enviada para o servidor, o que informará a todos os outros clientes conectados para atualizar a posição da forma para corresponder.

Captura de tela mostrando a página do aplicativo MoveShape.

O aplicativo criado neste tutorial é baseado em uma demonstração de Damian Edwards. Um vídeo que contém essa demonstração pode ser visto aqui.

O tutorial começará demonstrando como enviar mensagens do SignalR de cada evento que é acionado à medida que a forma é arrastada. Cada cliente conectado atualizará a posição da versão local da forma sempre que uma mensagem for recebida.

Embora o aplicativo funcione usando esse método, esse não é um modelo de programação recomendado, pois não haveria limite superior para o número de mensagens enviadas, para que os clientes e o servidor pudessem ficar sobrecarregados com mensagens e o desempenho degradaria. A animação exibida no cliente também seria desarticulada, pois a forma seria movida instantaneamente por cada método, em vez de se mover suavemente para cada novo local. Seções posteriores do tutorial demonstrarão como criar uma função de temporizador que restringe a taxa máxima na qual as mensagens são enviadas pelo cliente ou pelo servidor e como mover a forma suavemente entre os locais. A versão final do aplicativo criado neste tutorial pode ser baixada da Galeria de Códigos.

Este tutorial contém as seguintes seções:

Pré-requisitos

Este tutorial requer o Visual Studio 2012 ou o Visual Studio 2010. Se o Visual Studio 2010 for usado, o projeto usará .NET Framework 4 em vez de .NET Framework 4.5.

Se você estiver usando o Visual Studio 2012, é recomendável instalar a atualização do ASP.NET and Web Tools 2012.2. Essa atualização contém novos recursos, como aprimoramentos na publicação, novas funcionalidades e novos modelos.

Se você tiver o Visual Studio 2010, verifique se o NuGet está instalado.

Criar o projeto

Nesta seção, criaremos o projeto no Visual Studio.

  1. No menu Arquivo , clique em Novo Projeto.

  2. Na caixa de diálogo Novo Projeto , expanda C# em Modelos e selecione Web.

  3. Selecione o modelo ASP.NET Aplicativo Web Vazio , nomeie o projeto MoveShapeDemo e clique em OK.

    Criando o novo projeto

Adicionar os pacotes NuGet do SignalR e do JQuery.UI

Você pode adicionar a funcionalidade do SignalR a um projeto instalando um pacote NuGet. Este tutorial também usará o pacote JQuery.UI para permitir que a forma seja arrastada e animada.

  1. Clique em Ferramentas | Gerenciador de Pacotes NuGet | Console do Gerenciador de Pacotes.

  2. Insira o comando a seguir no gerenciador de pacotes.

    Install-Package Microsoft.AspNet.SignalR -Version 1.1.3
    

    O pacote SignalR instala vários outros pacotes NuGet como dependências. Quando a instalação for concluída, você terá todos os componentes de servidor e cliente necessários para usar o SignalR em um aplicativo ASP.NET.

  3. Insira o comando a seguir no console do gerenciador de pacotes para instalar os pacotes JQuery e JQuery.UI.

    Install-Package jQuery.ui.combined
    

Criar o aplicativo base

Nesta seção, criaremos um aplicativo de navegador que envia o local da forma para o servidor durante cada evento de movimentação do mouse. Em seguida, o servidor transmite essas informações para todos os outros clientes conectados conforme elas são recebidas. Expandiremos esse aplicativo em seções posteriores.

  1. Em Gerenciador de Soluções, clique com o botão direito do mouse no projeto e selecione Adicionar, Classe.... Nomeie a classe MoveShapeHub e clique em Adicionar.

  2. Substitua o código na nova classe MoveShapeHub pelo código a seguir.

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

    A MoveShapeHub classe acima é uma implementação de um hub SignalR. Assim como no tutorial Introdução com o SignalR, o hub tem um método que os clientes chamarão diretamente. Nesse caso, o cliente enviará um objeto que contém as novas coordenadas X e Y da forma para o servidor, que, em seguida, é transmitido para todos os outros clientes conectados. O SignalR serializará automaticamente esse objeto usando JSON.

    O objeto que será enviado ao cliente (ShapeModel) contém membros para armazenar a posição da forma. A versão do objeto no servidor também contém um membro para rastrear quais dados do cliente estão sendo armazenados, para que um determinado cliente não envie seus próprios dados. Esse membro usa o JsonIgnore atributo para evitar que ele seja serializado e enviado ao cliente.

  3. Em seguida, configuraremos o hub quando o aplicativo for iniciado. Em Gerenciador de Soluções, clique com o botão direito do mouse no projeto e clique em Adicionar | Classe de aplicativo global. Aceite o nome padrão de Global e clique em OK.

    Adicionar classe de aplicativo global

  4. Adicione a instrução a seguir using após as instruções using fornecidas na classe Global.asax.cs.

    using System.Web.Routing;
    
  5. Adicione a seguinte linha de código no Application_Start método da classe Global para registrar a rota padrão para o SignalR.

    RouteTable.Routes.MapHubs();
    

    Seu arquivo global.asax deve ser semelhante ao seguinte:

    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. Em seguida, adicionaremos o cliente. Em Gerenciador de Soluções, clique com o botão direito do mouse no projeto e clique em Adicionar | Novo Item. Na caixa de diálogo Adicionar Novo Item , selecione Página Html. Dê à página um nome apropriado (como Default.html) e clique em Adicionar.

  7. Em Gerenciador de Soluções, clique com o botão direito do mouse na página que você acabou de criar e clique em Definir como Página Inicial.

  8. Substitua o código padrão na página HTML pelo snippet de código a seguir.

    Observação

    Verifique se as referências de script abaixo correspondem aos pacotes adicionados ao projeto na pasta Scripts. No Visual Studio 2010, a versão do JQuery e do SignalR adicionada ao projeto pode não corresponder aos números de versão abaixo.

    <!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>
    

    O código HTML e JavaScript acima cria um Div vermelho chamado Shape, permite o comportamento de arrastar da forma usando a biblioteca jQuery e usa o evento da drag forma para enviar a posição da forma para o servidor.

  9. Inicie o aplicativo pressionando F5. Copie a URL da página e cole-a em uma segunda janela do navegador. Arraste a forma em uma das janelas do navegador; a forma na outra janela do navegador deve se mover.

    Captura de tela mostrando como uma forma que você arrasta em uma janela do navegador se move em outra janela.

Adicionar o loop do cliente

Como enviar o local da forma em cada evento de movimentação do mouse criará uma quantidade desnecessária de tráfego de rede, as mensagens do cliente precisam ser limitadas. Usaremos a função javascript setInterval para configurar um loop que envia novas informações de posição para o servidor a uma taxa fixa. Esse loop é uma representação muito básica de um "loop de jogo", uma função chamada repetidamente que conduz toda a funcionalidade de um jogo ou outra simulação.

  1. Atualize o código do cliente na página HTML para corresponder ao snippet de código a seguir.

    <!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>
    

    A atualização acima adiciona a updateServerModel função , que é chamada em uma frequência fixa. Essa função envia os dados de posição para o servidor sempre que o moved sinalizador indica que há novos dados de posição a serem enviados.

  2. Inicie o aplicativo pressionando F5. Copie a URL da página e cole-a em uma segunda janela do navegador. Arraste a forma em uma das janelas do navegador; a forma na outra janela do navegador deve se mover. Como o número de mensagens enviadas ao servidor será limitado, a animação não aparecerá tão suave quanto na seção anterior.

    Captura de tela mostrando como uma forma que você arrasta em uma janela do navegador se move em outra janela quando você adiciona um loop de cliente.

Adicionar o loop do servidor

No aplicativo atual, as mensagens enviadas do servidor para o cliente saem sempre que são recebidas. Isso apresenta um problema semelhante, como foi visto no cliente; as mensagens podem ser enviadas com mais frequência do que são necessárias e a conexão pode ser inundada como resultado. Esta seção descreve como atualizar o servidor para implementar um temporizador que limita a taxa das mensagens de saída.

  1. Substitua o conteúdo de pelo snippet de MoveShapeHub.cs código a seguir.

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

    O código acima expande o cliente para adicionar a Broadcaster classe , que limita as mensagens de saída usando a Timer classe do .NET Framework.

    Como o próprio hub é transitório (ele é criado sempre que necessário), o Broadcaster será criado como um singleton. A inicialização lenta (introduzida no .NET 4) é usada para adiar sua criação até que seja necessária, garantindo que a primeira instância do hub seja completamente criada antes que o temporizador seja iniciado.

    A chamada para a função dos UpdateShape clientes é então movida para fora do método do UpdateModel hub, para que ela não seja mais chamada imediatamente sempre que as mensagens de entrada forem recebidas. Em vez disso, as mensagens para os clientes serão enviadas a uma taxa de 25 chamadas por segundo, gerenciadas pelo _broadcastLoop temporizador de dentro da Broadcaster classe .

    Por fim, em vez de chamar o método cliente diretamente do hub, a Broadcaster classe precisa obter uma referência para o hub operacional atualmente (_hubContext) usando o GlobalHost.

  2. Inicie o aplicativo pressionando F5. Copie a URL da página e cole-a em uma segunda janela do navegador. Arraste a forma em uma das janelas do navegador; a forma na outra janela do navegador deve se mover. Não haverá uma diferença visível no navegador da seção anterior, mas o número de mensagens enviadas ao cliente será limitado.

    Captura de tela mostrando como uma forma que você arrasta em uma janela do navegador se move em outra janela quando você adiciona um loop de servidor.

Adicionar animação suave ao cliente

O aplicativo está quase completo, mas podemos fazer mais uma melhoria no movimento da forma no cliente conforme ela é movida em resposta às mensagens do servidor. Em vez de definir a posição da forma para o novo local dado pelo servidor, usaremos a função da biblioteca de interface do usuário JQuery animate para mover a forma suavemente entre sua posição atual e nova.

  1. Atualize o método do updateShape cliente para se parecer com o código realçado abaixo:

    <!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>
    

    O código acima move a forma do local antigo para o novo dado pelo servidor ao longo do intervalo de animação (nesse caso, 100 milissegundos). Qualquer animação anterior em execução na forma é desmarcada antes do início da nova animação.

  2. Inicie o aplicativo pressionando F5. Copie a URL da página e cole-a em uma segunda janela do navegador. Arraste a forma em uma das janelas do navegador; a forma na outra janela do navegador deve se mover. O movimento da forma na outra janela deve parecer menos irregular, pois seu movimento é interpolado ao longo do tempo em vez de ser definido uma vez por mensagem de entrada.

    Captura de tela mostrando como uma forma que você arrasta em uma janela do navegador se move em outra janela quando você adiciona animação suave ao cliente.

Etapas adicionais

Neste tutorial, você aprendeu a programar um aplicativo SignalR que envia mensagens de alta frequência entre clientes e servidores. Esse paradigma de comunicação é útil para desenvolver jogos online e outras simulações, como o jogo ShootR criado com o SignalR.

O aplicativo completo criado neste tutorial pode ser baixado da Galeria de Códigos.

Para saber mais sobre os conceitos de desenvolvimento do SignalR, visite os seguintes sites para código-fonte e recursos do SignalR: