Aplicaciones en tiempo real con SignalR

Bienvenidos a este post donde vamos a ver como crear un chat el cual va a funcionar en tiempo real gracias a SignalR. 

 

1 - Qué son las aplicaciones en tiempo real? 

Hoy en día gran parte de las aplicaciones que utilizamos son en tiempo real. Esto significa por ejemplo que cuando visitamos una página web, no tenemos que volver a cargar esa página para ver la información actualizada, sino que se actualiza automáticamente.

 

Un ejemplo muy sencillo es un chat, tu tienes tu ventana del chat, y cuando recibes un mensaje simplemente lo ves, no tienes que recargar el propio chat. Lo mismo sucede con emails o en juegos online. 

 

Cuando navegamos por internet el flujo normal es que visitemos una pagina y para ello hagamos una peticion HTTP  y el servidor nos devuelva la respuesta:

http request response

Pero esto no es suficiente para las aplicaciones en tiempo real, ya que el servidor necesita enviar mensajes al cliente (navegador) cada vez que algo cambia.

aplicacione en tiempo real

Para obtener información en tiempo real tenemos múltiples opciones:

 

1.1 - Polling 

Una de las técnicas para realizar “actualizaciones en tiempo real” es polling, lo cual significa que desde el navegador vamos a estar haciendo peticiones al servidor para ver si algo ha cambiado. 

Para ello realizamos llamadas http periodicas al servidor, lo cual implica meter todo el codigo de la petición en un bucle  while o for, y el servidor respondera si hay cambios o no.

ejemplo poling

 

1.2 - Long polling

Similar al caso anterior, la diferencia es que el servidor no devuelve la respuesta hasta que haya algún cambio. Por lo que la conexión http se queda abierta. 

En este suceso, a diferencia de Polling, necesitamos también un bucle en el lado del servidor, el cual se ejecutará hasta que haya algún cambio. 

ejemplo long polling

1.3  - Eventos enviados por el servidor (SSE)

Los eventos enviados por el servidor son una funcionalidad de HTML5  para recibir del servidor información en tiempo real. Una vez se realiza la conexión inicial el servidor puede enviar datos al cliente a través del objeto EventSource que tiene un evento llamado onmessage el cual permite procesar los mensajes recibidos, eso sí, están limitados a mensajes que contengan únicamente texto.

eventos enviados por el servidor

En este escenario la comunicación es en una única dirección, del servidor al cliente. 

Además una única conexión http es creada, debido a ello, es una solución mucho mas eficiente que polling o long polling

 

1.4 - Websockets

Los websockets son un estándar que nos permite utilizar un único socket TCP para enviar mensajes entre el servidor y el cliente. 

Tanto el cliente como el servidor pueden mandar mensajes entre sí a través del websocket

. websockets

A diferencia de los casos anteriores con websockets podemos hacer streaming de audio o de vídeo ya que permite enviar tanto texto como binary. 

 

1.4.1 - Cómo funciona websockets

Las llamadas HTTP normales también utilizan un socket TCP, la diferencia es que el estándar de websockets utiliza un handshake para mejorar un socket en uso (el de tu llamada http) a un websocket-socket utilizando el protocolo ws. 

 

Este es una imagen de como funciona un websocket (alto nivel):

funcionamiento de un websocket

Primero se establece la comunicación utilizando un HTTP request, en esta reques es donde el servidor indica que el socket tiene que actualizarse a un websocket y asi dejar el canal abierto para el intercambio de datos hasta que se cierra de forma manual (cerrando el navegador por ejemplo).

Blazor por ejemplo utiliza websockets a través de SignalR en su verisión server side.

 

 

2 - Qué es signalR

SignalR es un framework que abstrae las diferentes técnicas que acabamos de ver para la comunicación en tiempo real, por lo que nos quita gran parte de los dolores de cabeza que configurar todo correctamente nos puede llevar.

 

Así mismo, en caso de que nuestro usuario utiliza un navegador muy antiguo, signalR es capaz de identificarlo y utilizar Eventos Enviados por el servidor, o long polling si es aún más antiguo, de forma automática

 

Además, SignalR utiliza RPC para la comunicación entre el cliente y el servidor lo que hace que vaya muy rápido. (veremos RPC en otro post).

 

Finalmente, es importante tener en cuenta que SignalR ha sido adoptado por microsoft para toda su tecnología en tiempo real, y “signalR servidor” viene como librería en el paquete web de asp.net. 

 

2.1  - Comunicación de SignalR - Los Hubs

SignalR nos provee de Hubs que son componentes que vamos  a ubicar en el servidor el cual envía y recibe mensajes de los clientes

 

Dentro del hub podemos definir si queremos enviar la información a todos los clientes, a un solo cliente, o a un grupo de clientes.

Así como desde el cliente podemos llamar a un método dentro del hub.

Teniendo ambos puntos en cuenta es cómo podemos comunicarnos bidireccionalmente entre cliente y servidor en tiempo real. 

 

Por ejemplo en el caso de uso de un chat grupal. 

El usuario 1 manda un mensaje al hub, y este hub lo propaga por todos los clientes

ejemplo hub chat grupal

Nota: la autorización para asegurarnos de que nadie se conecta de forma inesperada funciona de la misma manera que funciona en los controladores, con el atributo Authorize.

 

 

3  - Creación de un chat con signalR

Vamos a ver como crear de forma práctica un chat en tiempo real utilizando blazor. 

 

3.1 - Configuración del servidor para un chat en tiempo real con signalR

El primer paso es crear una aplicación de consola, la cual nos va a hacer de servidor/host

En el pasado, con net framework, hubiéramos tenido que hacer una aplicación web, pero hoy en día no es necesario, ya que podemos hacer que una app de consola funcione como servidor.

 

Para nuestro caso en concreto únicamente debemos instalar la librería Microsoft.AspNetCore.App la cual nos proporciona todo lo necesario para poder ejecutar un servidor web en una aplicación de consola. 

 

Además hay que tener en cuenta que desde netCore 3 SignalR (servidor) viene instalado con el lenguaje.

 

Primero de todo vamos a crear el Hub al cual los clientes se van a conectar:

class ChatHub : Hub
{
    public async Task SendMessage(string user, string message)
    {
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }
}

Como vemos es muy muy sencillo, únicamente recibe dos parámetros y los propaga a través de ReceiveMessage. Esto quiere decir que cuando nuestro hub sea invocado, se enviará un mensaje a todos los clientes que tengan configurado ReceiveMessage.

 

Finalmente, debemos de configurar la app de consola para que funcione como servidor. Lo hacemos de la misma forma que haríamos en una aplicación web normal, a través de la clase startup:

public class Startup
{
    public IConfiguration Configuration { get; }

    public Startup(IConfiguration configuration) { Configuration = configuration; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors();
        services.AddSignalR();
    }

    public void Configure(IApplicationBuilder app)
    {
        // global cors policy
        app.UseCors(x => x
            .AllowAnyMethod()
            .AllowAnyHeader()
            .SetIsOriginAllowed(origin => true)); //Corregir esta configuración en producción
        
        app.UseRouting();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapHub<ChatHub>("/chat");
        });
    }
}

como vemos hemos añadido cors y signalR al contenedor de dependencias, asi como configurar cors para que acepte cualquier origin. Recuerda cambiar esta configuración si lo vas a poner en producción.

 

Finalmente dentro de la configuración hemos definido un endpoint el cual va a ser el punto de entrada de los clientes al hub.

app.UseEndpoints(endpoints =>
{
    endpoints.MapHub<ChatHub>("/chat");
});

Ahora únicamente debemos ejecutar nuestro servidor, podemos hacerlo desde la linea de comandos con dotnet run

 

3.2 - Configuración del cliente para un chat en tiempo real con signalR y Blazor

Ahora debemos crear un cliente el cual se va a conectar a nuestro servidor. 

Para ello creamos un nuevo proyecto web -> blazor.

 

Ahora si debemos instalar el cliente de signalR, para ello desde nuget instalamos Microsoft.AspNetCore.SignalR.Client y únicamente nos queda configurar una vista para permitir a nuestros usuarios escribir y ver los mensajes del hub.

 

Creamos un componente el cual va a contener dos inputs, uno para el nombre que vamos a utilizar en el chat y otro para el mensaje a enviar. 

<h3>Ejemplo chat</h3>
<div class="form-group row">
    <label for="usuario" class="col-sm-2 col-form-label">Usuario</label>
    <div class="col-sm-10">
        <input type="text" class="form-control" id="usuario" @bind="userInput">
    </div>
</div>
<div class="form-group row">
    <label for="Mensage" class="col-sm-2 col-form-label">Mensaje</label>
    <div class="col-sm-10">
        <input type="text" class="form-control" id="Mensage" @bind="messageInput">
    </div>
</div>

Como podemos observar tenemos los campos enlazados a dos propiedades.

Para poder enviar los datos, vamos a hacerlo con un botón que active un método, pero primero debemos conectarnos al hub, lo cual haremos desde el método onInitializedAsync del componente blazor.

@page "/chat"
@using Microsoft.AspNetCore.SignalR.Client

@code{
    private HubConnection hubConnection;
    private List<string> messages = new List<string>();
    private string userInput;
    private string messageInput;

    protected override async Task OnInitializedAsync()
    {
        hubConnection = new HubConnectionBuilder()
            .WithUrl("http://localhost:5000/chat")
            .Build();

        hubConnection.On<string, string>("ReceiveMessage", (user, message) =>
        {
            var encodedMsg = $"{user}: {message}";
            messages.Add(encodedMsg);
            StateHasChanged();
        });

        await hubConnection.StartAsync();
    }
}

Observamos que nos conectamos al hub a través de HubConnectionBuilder y el endpoint que hemos indicado en el servidor. En nuestro caso http://localhost:5000/chat.

  • Nota: la Url cambiaría en producción

 

Continuamos con el método .on del propio hub el cual es el handler del servidor, osea cada vez que alguien invoque el hub del servidor que contiene este handler, el contenido de dentro de este método se ejecutará.

hubConnection.On<string, string>("ReceiveMessage", (user, message) =>
{
    var encodedMsg = $"{user}: {message}";
    messages.Add(encodedMsg);
    StateHasChanged();
});

En este escenario, añadimos un mensaje a la lista de mensajes y actualizamos el componente. 

 

Finalmente comenzamos la conexión con el servidor con await hubConnection.StartAsync();

 

Ahora debemos crear el botón para poder mandar mensajes, para ello creamos un botón normal. El cual estará desactivado si no estamos conectados al hub. Para saber si estamos conectados podemos utilizar la propiedad State dentro de nuestro tipo hubconnection.

Y por supuesto el método para enviar la información al hub. 

<button @onclick="Send" disabled="@(!IsConnected)">Send</button>

@code {
    async Task Send() =>
        await hubConnection.SendAsync("SendMessage", userInput, messageInput);

    public bool IsConnected =>
        hubConnection.State == HubConnectionState.Connected;
}

Finalmente solo nos queda añadir en el html del componente un bucle para iterar por lo mensajes recibidos en el handler e implementar Idisposable ya que debemos liberar la conexión al dejar el componente. 

@page "/chat"
@using Microsoft.AspNetCore.SignalR.Client
@implements IAsyncDisposable

<h3>Ejemplo chat</h3>
<div class="form-group row">
    <label for="usuario" class="col-sm-2 col-form-label">Usuario</label>
    <div class="col-sm-10">
        <input type="text" class="form-control" id="usuario" @bind="userInput">
    </div>
</div>
<div class="form-group row">
    <label for="Mensage" class="col-sm-2 col-form-label">Mensaje</label>
    <div class="col-sm-10">
        <input type="text" class="form-control" id="Mensage" @bind="messageInput">
    </div>
</div>
<button @onclick="Send" disabled="@(!IsConnected)">Send</button>

<hr>

<ul id="messagesList">
    @foreach (var message in messages)
    {
        <li>@message</li>
    }
</ul>

@code {
    private HubConnection hubConnection;
    private List<string> messages = new List<string>();
    private string userInput;
    private string messageInput;

    protected override async Task OnInitializedAsync()
    {
        hubConnection = new HubConnectionBuilder()
            .WithUrl("http://localhost:5000/chat")
            .Build();

        hubConnection.On<string, string>("ReceiveMessage", (user, message) =>
        {
            var encodedMsg = $"{user}: {message}";
            messages.Add(encodedMsg);
            StateHasChanged();
        });

        await hubConnection.StartAsync();
    }

    async Task Send() =>
        await hubConnection.SendAsync("SendMessage", userInput, messageInput);

    public bool IsConnected =>
        hubConnection.State == HubConnectionState.Connected;

    public async ValueTask DisposeAsync()
    {
        await hubConnection.DisposeAsync();
    }
}

Y este es el resultado final:
chat tiempo real signalR

 

Conclusión

  • En este post hemos visto que son las aplicaciones en tiempo real y que opciones tenemos para crearlas.
  • hemos visto que es SignalR y cómo funciona.
  • Así como crear un chat en tiempo real con signalr y .net.

 


Uso del bloqueador de anuncios adblock

Hola!

Primero de todo bienvenido a la web de NetMentor donde podrás aprender programación en C# y .NET desde un nivel de principiante hasta más avanzado.


Yo entiendo que utilices un bloqueador de anuncios como AdBlock, Ublock o el propio navegador Brave. Pero te tengo que pedir por favor que desactives el bloqueador para esta web.


Intento personalmente no poner mucha publicidad, la justa para pagar el servidor y por supuesto que no sea intrusiva; Si pese a ello piensas que es intrusiva siempre me puedes escribir por privado o por Twitter a @NetMentorTW.


Si ya lo has desactivado, por favor recarga la página.


Un saludo y muchas gracias por tu colaboración

© copyright 2024 NetMentor | Todos los derechos reservados | RSS Feed

Buy me a coffee Invitame a un café