[Traducción] transmisión bidireccional y presentación de ASP.NET Core 3.0 gRPC

Relacionado original

 原文作者:Eduard Los
 原文地址:https://medium.com/@eddyf1xxxer/bi-directional-streaming-and-introduction-to-grpc-on-asp-net-core-3-0-part-2-d9127a58dcdb
 Demo地址:https://github.com/f1xxxer/CustomerGrpc

Ahora, echemos un vistazo al código. Puede crear fácilmente un proyecto de servicio gRPC utilizando la interfaz de usuario de Visual Studio o los comandos de línea de comandos: dotnet new grpc -n YourGrpcService

En mi solución, tanto el servidor gRPC como el código del cliente están en C #. El servidor gRPC está administrando conexiones de clientes y procesando mensajes, y transmitiendo mensajes a todos los clientes conectados. El cliente recibe la entrada del cliente, la envía al servidor y también acepta mensajes enviados por otros clientes desde el servidor.

Primero miramos el código del servidor en el proyecto CustomerGrpc, pero antes de eso, quiero señalar algo en los archivos estándar Startup.cs y Program.cs .

public class Startup {
    // This method gets called by the runtime. Use this method to add services to the container.
    // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
    public void ConfigureServices (IServiceCollection services) {
        services.AddGrpc (opts => {
            opts.EnableDetailedErrors = true;
            opts.MaxReceiveMessageSize = 4096;
            opts.MaxSendMessageSize = 4096;
        });
        services.AddTransient<IChatRoomService, ChatRoomService> ();
        services.AddSingleton<IChatRoomProvider, ChatRoomProvider> ();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure (IApplicationBuilder app, IWebHostEnvironment env) {
        if (env.IsDevelopment ()) {
            app.UseDeveloperExceptionPage ();
        }

        app.UseRouting ();

        app.UseEndpoints (endpoints => {
            endpoints.MapGrpcService<Services.CustomerService> ();

            endpoints.MapGet ("/",
                async context => {
                    await context.Response.WriteAsync (
                        "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
                });
        });
    }
}

El método services.AdddGrpc () ; habilita gRPC. Este método agrega diferentes servicios y middleware que se utilizan para construir tuberías para manejar llamadas gRPC. El método Undercover usa la clase GrpcMarkerService para garantizar que se agreguen todos los servicios gRPC necesarios, y también se agrega algún middleware que opera en mensajes HTTP / 2 básicos. También puede proporcionar la configuración del servicio a través del tipo GrpcServiceOptions , que se utilizará más adelante en la tubería. Por ejemplo, el tamaño máximo de mensaje entrante se puede configurar de esta manera:


services.AddGrpc(opts =>
{
    opts.EnableDetailedErrors = true;
    opts.MaxReceiveMessageSize = 4096;
    opts.MaxSendMessageSize = 4096;
});

Para nosotros, otro método interesante es el enrutamiento de puntos finales: endpoints.MapGrpcService <Services.CustomerService> (); Este método agrega el servicio gRPC a la canalización de enrutamiento. En ASP.NET Core, la canalización de enrutamiento se comparte entre el middleware y las funciones, lo que significa que podemos tener otros controladores de solicitudes, como los controladores MVC. Otros manejadores de solicitudes funcionan en paralelo con el servicio gRPC configurado. Ver abajo:


app.UseEndpoints(endpoints =>
{
	endpoints.MapGrpcService<Services.CustomerService>();

	endpoints.MapGet("/",	async context =>
		{
			await context.Response.WriteAsync(
				"Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: 						https://go.microsoft.com/fwlink/?linkid=2086909");
		});
});

Ahora necesitamos configurar el servidor Kestrel.

Kestrel gRPC punto final:

  • Se requiere HTTP / 2.
  • HTTPS debe usarse para protección.

HTTP / 2
gRPC requiere HTTP / 2, y el gRPC de ASP.NET Core verifica que HttpRequest.Protocol . Es HTTP / 2.

Kestrel admite Http / 2 en la mayoría de los sistemas operativos modernos . De manera predeterminada, los puntos finales Kestrel están configurados para admitir conexiones HTTP / 1.1 y HTTP / 2.

Los
puntos finales HTTPS Kestrel para gRPC deben protegerse con HTTPS. En desarrollo, https: // localhost: 5001 creará automáticamente un punto final HTTPS cuando haya un certificado de desarrollo ASP.NET Core. No se requiere configuración.
En producción, HTTPS debe configurarse explícitamente. En nuestro caso, tenemos lo siguiente:


public class Program
{
	public static void Main(string[] args)
	{
		CreateHostBuilder(args).Build().Run();
	}

	// Additional configuration is required to successfully run gRPC on macOS.
	// For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682
	public static IHostBuilder CreateHostBuilder(string[] args) =>
		Host.CreateDefaultBuilder(args)
			.ConfigureWebHostDefaults(webBuilder =>
			{
				webBuilder.ConfigureKestrel(options =>
				{
					// Setup a HTTP/2 endpoint without TLS for OSX.
					options.ListenLocalhost(5001, o => o.Protocols = HttpProtocols.Http2);
				});
				webBuilder.UseStartup<Startup>();
			});
}

Tenga en cuenta que existe un problema conocido de que macOS no es compatible con ASP.NET Core gRPC con Transport Layer Security ( Http / 2. ). Para ejecutar con éxito el servicio gRPC en macOS, se requiere una configuración adicional. Esta es exactamente nuestra situación, por lo que tenemos que deshabilitar TLS usando líneas HTTP / 2 sin TLS en las opciones.ListenLocalhost (5001, o => o.Protocols = HttpProtocols.Http2); Usado durante el desarrollo de la aplicación. Las aplicaciones de producción siempre deben usar seguridad de transmisión. Para obtener más información, consulte Consideraciones de seguridad en gRPC de ASP.NET Core .

Ahora, echemos un vistazo a nuestro servicio al cliente de gRPC. Al principio vemos es nuestra CustomerService clase de CustomerGrpc.CustomerService.CustomerServiceBase herencia de clases.

Esta clase se genera en base a nuestro archivo .proto. Si presiona la tecla F12 en el archivo, verá un montón de códigos generados automáticamente que describen nuestros servicios, mensajes y comunicaciones.
En nuestra clase, anulamos dos métodos personalizados definidos en la clase principal: JoinCustomerChat y SendMessageToChatRoom Esto es básicamente todo nuestro código. JoinCustomerChat muestra un modelo de solicitud / respuesta relativamente simple, en el que enviamos información del cliente de JoinCustomerRequest y la recibimos en el RoomId de la sala de chat donde el cliente se une a JoinCustomerReply .


public override async Task<JoinCustomerReply> JoinCustomerChat (JoinCustomerRequest request, ServerCallContext context) 
{
    return new JoinCustomerReply { RoomId = await _chatRoomService.AddCustomerToChatRoomAsync (request.Customer) };
}

Luego, tenemos un método SendMessageToChatRoom más interesante , que es una demostración de transmisión bidireccional.


public override async Task SendMessageToChatRoom (IAsyncStreamReader<ChatMessage> requestStream, IServerStreamWriter<ChatMessage> responseStream, ServerCallContext context)
 {
    var httpContext = context.GetHttpContext ();
    _logger.LogInformation ($"Connection id: {httpContext.Connection.Id}");

    if (!await requestStream.MoveNext ()) {
        return;
    }

    _chatRoomService.ConnectCustomerToChatRoom (requestStream.Current.RoomId, Guid.Parse (requestStream.Current.CustomerId), responseStream);
    var user = requestStream.Current.CustomerName;
    _logger.LogInformation ($"{user} connected");

    try {
        while (await requestStream.MoveNext ())
        {
            if (!string.IsNullOrEmpty (requestStream.Current.Message)) 
            {
                if (string.Equals (requestStream.Current.Message, "qw!", StringComparison.OrdinalIgnoreCase)) {
                    break;
                }
                await _chatRoomService.BroadcastMessageAsync (requestStream.Current);
            }
        }
    } 
    catch (IOException)
    {
        _chatRoomService.DisconnectCustomer (requestStream.Current.RoomId, Guid.Parse (requestStream.Current.CustomerId));
        _logger.LogInformation ($"Connection for {user} was aborted.");
    }
}

Echemos un vistazo más de cerca a los parámetros del método. IAsyncStreamReader requestStream Representa el flujo de mensajes del cliente, podemos leer e iterar requestStream.MoveNext () . Si hay mensajes disponibles, el método devuelve verdadero; si no hay otros mensajes (es decir, la secuencia está cerrada), el método devuelve falso.

IServerStreamWriter respuesta También es un flujo de mensajes, esta vez se puede escribir y se utiliza para enviar el mensaje de vuelta al cliente.

El contexto ServerCallContext proporciona algunos atributos convenientes, como HttpContext y HostName de la llamada.
La lógica en este método no es complicada. Cuando se recibe una llamada en el servidor, primero almacenamos IServerStreamWriter respuesta Se usa para transmitir mensajes a clientes,


_chatRoomService.ConnectCustomerToChatRoom(requestStream.Current.RoomId, Guid.Parse(requestStream.Current.CustomerId), responseStream);

Luego, en un momento (aguarde el ciclo requestStream.MoveNext ()) , examinamos los mensajes recibidos del cliente y los transmitimos a otro cliente que está conectado actualmente.


await _chatRoomService.BroadcastMessageAsync(requestStream.Current);

El código en el método BroadcastMessageAsync () atravesará la secuencia de respuesta de todos los clientes conectados y escribirá mensajes en la secuencia.


foreach (var customer in chatRoom.CustomersInRoom)
{
   await customer.Stream.WriteAsync(message);
   Console.WriteLine($"Sent message from {message.CustomerName} to   {customer.Name}");
}

También tenemos un bloque try / catch para manejar la falla de conexión y eliminar la secuencia correspondiente de la sala de chat.


catch (IOException)
{
   _chatRoomService.DisconnectCustomer(requestStream.Current.RoomId,     Guid.Parse(requestStream.Current.CustomerId));
   _logger.LogInformation($"Connection for {user} was aborted.");
}

Esto es básicamente toda la lógica del servidor directamente relacionada con gRPC. He creado algunas características adicionales en servicios y envoltorios, que puedes encontrar aquí.

Ahora, podemos ver brevemente el código del cliente. Primero, creamos un GrpcChannel y se lo proporcionamos al cliente gRPC.


var channel = GrpcChannel.ForAddress("http://localhost:5001", new GrpcChannelOptions { Credentials = ChannelCredentials.Insecure });
var client = new CustomerService.CustomerServiceClient(channel);

Luego, el cliente devuelve JoinCustomerChatAsync al RoomId de la sala de chat. Después de eso, abrimos un flujo bidireccional ejecutando la llamada SendMessageToChatRoom e iniciando el intercambio de mensajes.


using (var streaming = client.SendMessageToChatRoom (new Metadata { new Metadata.Entry ("CustomerName", customer.Name) })) 
{
    var response = Task.Run (async () => 
    {
        while (await streaming.ResponseStream.MoveNext ()) 
        {
            Console.ForegroundColor = Enum.Parse<ConsoleColor> (streaming.ResponseStream.Current.Color);
            Console.WriteLine ($"{streaming.ResponseStream.Current.CustomerName}: {streaming.ResponseStream.Current.Message}");
            Console.ForegroundColor = Enum.Parse<ConsoleColor> (customer.ColorInConsole);
        }
    });

    ...
    
    while (!string.Equals (line, "qw!", StringComparison.OrdinalIgnoreCase)) {
        await streaming.RequestStream.WriteAsync (new ChatMessage {
            Color = customer.ColorInConsole,
                CustomerId = customer.Id,
                CustomerName = customer.Name,
                Message = line,
                RoomId = joinCustomerReply.RoomId
        });
        line = Console.ReadLine ();
        DeletePrevConsoleLine ();
    }
    await streaming.RequestStream.CompleteAsync ();
}

Después de abrir la conexión, comenzamos un ciclo en la tarea en segundo plano, que lee el mensaje del flujo de respuesta y luego lo muestra en la consola. El segundo bucle simplemente toma la entrada del teclado y envía la entrada al servidor.
En general, el código no es tan complicado, pero indica dónde debe usar o diseñar opciones para usar .net core y c # para implementar su propio servicio gRPC.

Muchas gracias al autor Eduard Los . Compártelo con nosotros.
Si te gusta, puede que también te guste.

Supongo que te gusta

Origin www.cnblogs.com/ancold/p/12693924.html
Recomendado
Clasificación