Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 17 de diciembre de 2019
gRPC logo
Días atrás veíamos lo sencillo que resultaba crear un servicio gRPC que hacía uso de streaming unidireccional de mensajes, en sentido servidor-cliente. Como recordaréis, en este post implementábamos un servicio generador de números al que los clientes podrían conectarse para recibir una secuencia de enteros a intervalos periódicos.

Servicio de streaming unidireccional en ejecución

Hoy vamos a complicar algo el escenario, pues veremos una opción aún más potente: la creación de streaming bidireccional, es decir, cómo cliente y servidor pueden enviar mensajes al lado opuesto de forma asíncrona, una vez establecida la comunicación entre ambos.

Para ello, implementaremos un sencillo juego, con las siguientes funcionalidades:
  • El lado cliente, una aplicación de consola, abrirá una conexión con el servicio. En ese momento, el servidor elegirá un número al azar (1-100) y lanzará un mensaje de bienvenida, que deberá ser mostrado en el lado cliente.
  • Desde el lado cliente solicitaremos al usuario un número entero, que enviaremos al servidor por el stream ascendente.
  • El servidor recibirá el número y lo comparará con el que tiene almacenado, enviando por el canal descendente el resultado de la comparación.
  • El proceso finalizará cuando el número sea encontrado.

Implementación del lado servidor

Lo primero que vamos a hacer, tras crear el proyecto partiendo de la plantilla para servicios ASP.NET Core gRPC, en primer lugar le añadiremos el contrato protobuf del servicio a implementar en el archivo Protos/NumberGenerator.proto, con el siguiente contenido:
option csharp_namespace = "DemoGrpc";

service GuessGame {
  rpc Guess(stream Number) returns (stream Response);
}

message Number {
  int32 value = 1;
}

message Response {
  bool found = 1;
  string message = 2;
}
La implementación del servicio la haremos, como de costumbre, heredando de la clase autogenerada GuessGame.GuessGameBase y sobrescribiendo el método Guess() con el bucle principal de nuestro juego en el lado servidor:
public class GuessGameService : GuessGame.GuessGameBase
{
    public override async Task Guess(
        IAsyncStreamReader<Number> requestStream,
        IServerStreamWriter<Response> responseStream, 
        ServerCallContext context)
    {
        var numberToGuess = new Random().Next(1, 101);
        var round = 0;

        var response = new Response { Message = "Hey, guess a number between 1 and 100!" };
        await responseStream.WriteAsync(response);

        await foreach (var number in requestStream.ReadAllAsync())
        {
            round++;
            if (number.Value < numberToGuess)
            {
                response.Message = $"My number is greather than {number.Value}";
            }
            else if (number.Value > numberToGuess)
            {
                response.Message = $"My number is less than {number.Value}";
            }
            else
            {
                response.Found = true;
                response.Message = $"Hey, you found the number {number.Value} in {round} rounds!";
            }
            await responseStream.WriteAsync(response);
        }
    }
}
Fijaos que principalmente se trata de ir leyendo los mensajes enviados desde el cliente a través del stream ascendente (requestStream) y utilizar responseStream para enviar el resultado de la comprobación y un mensaje de ayuda. El bucle finalizará cuando desde el cliente se dé por finalizada la petición.

Implementación del lado cliente

En el lado cliente tenemos que jugar un poco con paralelismo. Por un lado, tendremos un hilo recibiendo y mostrando por consola los mensajes recibidos desde el servidor, y por otro implementaremos el bucle principal desde el que solicitamos el número al usuario y los enviamos por el stream ascendente para que sea comprobado:
class Program
{
    static async Task Main(string[] args)
    {
        var channel = GrpcChannel.ForAddress("https://localhost:5001");

        var client = new GuessGame.GuessGameClient(channel);
        using (var stream = client.Guess())
        {
            var found = false;

            var readTask = Task.Run(async () =>
            {
                await foreach (var response in stream.ResponseStream.ReadAllAsync())
                {
                    Console.WriteLine(response.Message);
                    if (response.Found)
                    {
                        found = true;
                    }
                }
                Console.WriteLine("Game finished, enter to exit");
            });

            while (!found)
            {
                Console.Write("> ");
                var ns = Console.ReadLine();
                if (!found && int.TryParse(ns, out var number))
                {
                    await stream.RequestStream.WriteAsync(new Number { Value = number });
                }
            }

            await stream.RequestStream.CompleteAsync();
            await readTask;
        }
        Console.WriteLine("Press a key to close");
        Console.ReadKey();
    }
}
Como podéis observar, el bucle de envío de números finaliza cuando acertamos el número "pensado" por el servidor. En ese momento, damos por completada la petición con CompleteAsync() y esperamos a que la tarea en segundo plano finalice para evitar problemas.

La siguiente captura de pantalla muestra el sistema en funcionamiento:

Stream gRPC bidireccional

Y con esto, damos por finalizada esta miniserie sobre streaming con gRPC, donde hemos visto cómo esta tecnología nos permite mucha más flexibilidad a la hora de enviar y recibir mensajes que los servicios REST/HTTP tradicionales.

Espero que os haya resultado interesante y útil para aplicarlo en vuestros proyectos. Pero ojo, antes de lanzaros a implementar servicios gRPC como posesos, recordad que estos servicios no pueden ser consumidos por cualquier tipo de sistema de forma directa (por ejemplo, los browsers aún no lo soportan) y que tampoco pueden ser desplegados en algunos entornos (como IIS o Azure App Services).

Publicado bidireccionalmente en Variable not found.

Aún no hay comentarios, ¡sé el primero!