Julho de 2018

Volume 33 – Número 7

Cutting Edge - Usuários Online, Streaming e Outras Coisas do SignalR

Por Dino Esposito | Julho de 2018

Dino EspositoSe você usou qualquer versão do SignalR para a plataforma ASP.NET clássica, você deve ser bastante familiarizado com o conceito de um hub. No SignalR um hub é o componente que permite que os aplicativos de cliente e servidor compatíveis organizar as chamadas de procedimento remoto bidirecional, do cliente para servidor e do servidor para clientes conectados.

Concretamente, em termos de software, um hub é uma classe que herda de uma classe base fornecida pelo sistema e expõe pontos de extremidade para chamada dos clientes. Conceitualmente, ele tem alguns pontos em comum com um controlador do ASP.NET. Em particular, é a fachada que recebe as chamadas do cliente e reage a eles e é organizada em torno de um número relativamente pequeno de funções relacionadas. No SignalR do ASP.NET Core, é a semelhança entre os hubs e controladores, de uma maneira ainda mais. Este é meu terceiro artigo sobre o SignalR do ASP.NET Core para a coluna Cutting Edge, e em nenhum dos dois artigos anteriores já usei uma classe de hub não vazio. Explicar um pouco mais sobre os hubs de SignalR e como eles são implementados no ASP.NET Core é uma das finalidades deste artigo. No entanto, eu abordarei também em outros aspectos interessantes do SignalR, especificamente o fluxo de dados e a contagem de usuários online.

Hubs de SignalR do ASP.NET Core

Um hub é o ponto de entrada em um pipeline de mensagem por meio do qual os clientes conectados e servidores trocam mensagens. Os hubs de expõem seus métodos como URLs e processam quaisquer parâmetros recebidos por meio de associação de modelo, da mesma forma que faz um controlador canônico. De forma, um hub é um controlador dedicado que opera em dois protocolos internos. O protocolo padrão consiste em um objeto JSON, mas outro protocolo binário pode ser usado com base no MessagePack. Observe que, para dar suporte a MessagePack, navegadores devem suportar XHR nível 2. Como nível 2 foi introduzido em 2012, isso pode não ser um grande problema hoje, mas se seu aplicativo requer suporte para navegadores muito antigos talvez vale a pena observar. Uma verificação rápida do navegador pode ser feita aqui: caniuse.com/#feat=xhr2.

Se qualquer um dos clientes conectados solicitar um ponto de extremidade do SignalR, o hub é invocado diretamente pelo mecanismo de tempo de execução do SignalR. A conversa ocorre através do protocolo selecionado, principalmente a probabilidade de WebSockets. Para invocar o servidor, os clientes precisam conter um objeto de conexão. Um cliente Web seria obtê-lo da seguinte maneira:

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

Um cliente chama um ponto de extremidade do servidor por meio do método "invocar" no objeto de conexão. Observe que a sintaxe exata pode variar dependendo da tecnologia real usada para o cliente. O servidor responde por meio dos métodos definidos na classe base do Hub e a conversa ocorre através do protocolo de transporte de escolha, com mais frequência WebSockets, assim:

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

Observe que você não poderá monitorar as diversas chamadas usando uma ferramenta de HTTP, como o Fiddler. Você precisa de algo parecido com as ferramentas de desenvolvedor do Chrome. Em todos os exemplos que escrevi para minhas últimas colunas sobre SignalR, no entanto, eu sempre usei uma classe vazia do hub.

A classe hub é a fachada oficial do SignalR para receber chamadas do cliente e a maneira mais rápida para a comunicação cliente/servidor entrar em vigor por causa do pipeline dedicado. Quando a chamada ocorre por meio do hub, o tempo de execução do SignalR pode acompanhar e expor todas as informações disponíveis por meio das propriedades da classe base do Hub. Dessa forma, a ID de conexão do SignalR e todo o contexto da chamada, incluindo quaisquer declarações do usuário autenticado, estão disponíveis.

Além disso, por meio do hub, os desenvolvedores podem manipular se conectar e desconectar de eventos. Sempre que uma nova conexão é configurar ou deixaram de, um método de hub é chamado de volta. Se você usar o SignalR apenas como uma maneira de monitorar as operações de longa execução remotas, em seguida, também pode disparar a tarefa por meio de um controlador sem formatação e injetar um contexto de hub nele para notificações. Ou, como alternativa, você pode invocar o hub e disparar a tarefa de dentro do hub. A escolha é sua. O SignalR funciona como uma estrutura de ponta a ponta, uma ponte entre o cliente e o servidor. Lógica de codificação no hub é aceitável desde que o trabalho não é aprovado em muitos as camadas do seu código. Caso contrário, percorra o controlador — se MVC ou API da Web — e injetar um contexto de hub.

A única diferença entre usar um hub ou um controlador é que o SignalR não é possível rastrear a ID de conexão quando uma solicitação passará o controlador. Se a ID de conexão é relevante para a tarefa do servidor, ele deve ser passado de alguma forma por meio da URL. Todas as outras informações de que forma o contexto do chamador SignalR podem ser recuperadas pelo controlador por meio do contexto de solicitação HTTP.

Contagem de usuários Online

Alguns aplicativos Web ache útil ou apenas atraentes para os usuários, para mostrar a quantas conexões estão ativas no momento. O problema não é tanto de acompanhamento quando um usuário se conecta — há muitos pontos de extremidade por meio do qual você pode detectar que — mas em vez disso, quando um usuário se desconecta do aplicativo.

Você pode fazer a auditoria de uma página de logon ou a etapa de pós-autenticação. Você pode colocar uma verificação em alguma classe de base de controlador ou em qualquer uma das páginas que o usuário pode acessar. Em geral, você sempre pode encontrar uma maneira de detectar quando um usuário se conecta ao aplicativo. O problema é como o usuário pode deixar o aplicativo, por logoff (facilmente rastreável) ou ao navegar para fora da página ou desligando a janela do navegador. Não há nenhuma maneira confiável para detectar quando o usuário fecha a janela do navegador. Sim, navegadores geralmente acionam o evento beforeunload quando o navegador é fechado, mas esse mesmo evento também é acionado sempre que você seguirá um link — mesmo quando esse link está dentro do mesmo aplicativo. Portanto, não é uma solução perfeita.

É uma maneira muito mais confiável de contagem de usuários controlar as conexões do SignalR do ASP.NET Core. Para fazer isso, você precisa de um hub totalmente funcional com a conexão configurar por meio dele. Quando o usuário deixa o navegador ou apenas o aplicativo, a conexão é liberados e escutando clientes notificados. Como no SignalR do ASP.NET Core, não há nenhum suporte para reconexões automático, para que as coisas são mais fáceis. Tudo que você fazer é definir uma variável estática global no hub e incrementar seu valor para cima ou para baixo quando um usuário se conecta ou desconecta, conforme mostrado na Figura 1. O tempo de execução de SignalR no ASP.NET Core garante que cada conexão é fechada em algum momento, e nenhuma nova conexão com eficiência se refere a uma nova conexão. Em resumo, o número que você obtém é altamente confiável.

Figura 1 contando conexões

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

Há uma desvantagem para contagem de usuários com o SignalR: Ele só funciona se os usuários visitam a página que estabelece uma conexão para o hub em que a contagem ocorre. Para ser seguro, você precisa ter um cliente do SignalR em quase qualquer página que o usuário pode acessar. Isso é especialmente verdadeiro se você considerar que normalmente o número de usuários online é um valor globalmente visível, que você provavelmente tem todos os layouts na qual seus modos de exibição são baseados.

Observe que no código de exemplo de hub, a classe chama de volta os clientes conectados sempre que uma conexão é criada ou fechado. Observe também que dessa forma você apenas ter o número total de usuários, mas não a lista de IDs de conexão ou, no caso de usuários autenticados, a lista de nomes de usuário. Para fazer isso, é melhor usar um dicionário em vez de um contador global e adicionar a ele entradas com IDs de conexão ou declarações, como o nome de usuário.

Outro ponto a considerar com o código na referência a Figura 1 é o uso de uma variável estática para contar os usuários. Uma variável estática é por servidor, que significa que quando você escala horizontalmente você precisa considerar como armazenar o estado compartilhado em um local acessível globalmente, como um banco de dados ou um cache distribuído.

Enviar informações de volta

De dentro do hub ou o contexto do hub se você se conectar ao back-end por meio de um método de controlador, você tem várias maneiras diferentes de clientes conectados de retorno de chamada. Todos os métodos são os membros expostos pelo objeto de clientes que, apesar do nome, não é uma coleção, mas uma instância da classe IClientProxy. As expressões Figura 2 indicam o objeto do qual o método SendAsync é invocado. O método SendAsync usa o nome do método cliente para o retorno de chamada e os parâmetros a serem passados.

Figura 2 métodos para o servidor de chamada de volta os clientes conectados

Expression Descrição
Clients.All A notificação é difusão para todos os clientes, independentemente da tecnologia que está sendo usado (Web. .NET, .NET Core, Xamarin).
Clients.Client(connectionId) A notificação é enviada exclusivamente para o cliente escuta sobre a conexão especificada.
Clients.User(userId) A notificação é enviada a todos os clientes cujo usuário autenticado corresponde ao nome de usuário fornecido.
Clients.Groups(group) A notificação é enviada a todos os clientes que pertencem ao grupo especificado.

 

Um grupo é uma coleção de clientes relacionados coletivamente reunidos em um nome. A maneira mais natural de pensar em grupos no SignalR é salas de chat. Um grupo é criado por meio de programação simplesmente adicionando as IDs de conexão para o grupo de um determinado nome. Veja como:

hub.Groups.AddAsync(connectionId, nameOfGroup);

Clientes conectados recebem dados por meio de um retorno de chamada. Isso é apenas a técnica mais comum. No SignalR do ASP.NET Core, você também pode usar o streaming.

O fluxo de dados

Provavelmente, o aspecto de novo mais interessante do SignalR é suporte para streaming. Streaming é semelhante a difusão, mas ele segue um modelo ligeiramente diferente e é essencialmente uma forma ligeiramente diferente de conseguir a mesma comunicação estilo de transmissão. Com o SignalR streaming, o hub ainda precisa sondar ou escutar os dados para transmiti-lo novamente. Na transmissão clássico, o servidor informa a um método de cliente quando novos dados estão disponíveis.

No novo modelo de streaming, o cliente se inscreve para um novo objeto de servidor do tipo de canal e o servidor — hub, na verdade — gera novos itens conforme eles sejam capturados. No momento, não há nada como um verdadeiro fluxo de bytes que fluxos para todos os clientes conectados assim que estiverem disponíveis, mas esse modelo pode ter suporte no futuro. Observe que o tipo de canal foi introduzido com a visualização 2 e não tem suporte em builds anteriores. Em builds anteriores, você deve usar observáveis em vez disso, que exigem uma referência ao pacote System.Reactive.Linq NuGet. A alternância entre observáveis e o novo tipo de que canal está relacionado à falta de primitivas no IObservable para trabalhar com a pressão de retorno de rede (isto é, informando que o servidor lento quando o cliente não está processando mensagens com rapidez suficiente).

Figura 3 apresenta o código para o hub.

Figura 3 classe Hub que transmita de volta

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

O hub oferece três métodos para iniciar, parar e operar o relógio. Uma variável global controla o status de execução do fluxo e iniciar e os métodos de parada de definir a variável de controle e notificar o cliente voltar métodos como de costume em um hub SignalR. A parte complicada é o método de escala. O nome do método coincide com o nome do fluxo ao qual os clientes se inscreverá. O método retorna um objeto de canal de um determinado tipo. No exemplo, o tipo é uma cadeia de caracteres simple, mas pode ser qualquer coisa mais sofisticados.

Cada invocação, do cliente para o servidor ou servidor para cliente, consiste em uma parte enviando uma mensagem de invocação e a outra parte, eventualmente, respondendo com uma mensagem de conclusão que transporta um resultado ou erro. Em um cenário de streaming do SignalR, em vez disso, a outra parte responde com várias mensagens, cada um contendo um item de dados, antes de concluir, eventualmente, a comunicação com uma mensagem de conclusão. Como resultado, o cliente acaba contando vários itens de processamento, até mesmo antes de receber a mensagem de conclusão.

Dimensionamento para várias instâncias

O SignalR mantém a conexão de todas as IDs de na memória, que significa que no momento em que o aplicativo é dimensionado para várias instâncias, a difusão (mas também em streaming, conforme discutido posteriormente) fica comprometido, já que cada instância seria apenas acompanhar uma parte de todos os clientes conectados. Para evitar isso, o SignalR dá suporte a um cache com base em Redis que garante que novas conexões são compartilhadas automaticamente entre instâncias. Para habilitar o Redis, você precisa o pacote SignalR.Redis e uma chamada para o método AddRedis no método ConfigureServices da classe de inicialização, da seguinte forma:

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

O parâmetro de opção serve ao propósito de especificar a cadeia de conexão para a instância em execução do Redis.

Conclusão

SignalR do ASP.NET Core vem com duas alterações significativas da versão não essenciais. Uma é a falta de reconexão automática, que tem um impacto sobre como conectar/desconectar e usuário online a contagem é tratada por meio de programação. Isso significa que, agora, cada aplicativo tem que lidar com a lógica de conexão/desconexão e provavelmente tem que identificar a diferença entre um usuário que se conecta pela primeira vez e um usuário se reconectar devido a um erro. A outra alteração é o suporte para streaming de dados. Streaming de dados é baseado em canais e no momento apenas oferece suporte a itens de dados específico em vez de fluxos brutos.

Por fim, minha exploração do SignalR não tem mais uma parte, que abordarei em uma futura coluna: autenticado usuários e grupos.


Dino Esposito é autor de mais de 20 livros e de 1.000 artigos em seus 25 anos de carreira. Autor de “The Sabbatical Break”, um show de estilo cênico, Esposito se ocupa escrevendo software para um mundo mais ecológico, como estrategista digital da BaxEnergy. Siga-o no Twitter: @despos.

Agradecemos ao seguinte especialista da Microsoft pela revisão deste artigo: Andrew Stanton-Nurse


Discuta esse artigo no fórum do MSDN Magazine