Comunicación entre procesos con gRPC

Por James Newton-King

Las llamadas gRPC entre un cliente y un servicio se envían normalmente a través de sockets TCP. TCP se ha diseñado para comunicarse a través de una red. La comunicación entre procesos (IPC) es más eficaz que TCP cuando el cliente y el servicio están en el mismo equipo. En este documento se explica cómo usar gRPC con transportes personalizados en escenarios de IPC.

Configuración del servidor

Kestrel admite transportes personalizados. Kestrel se configura en Program.cs:

public static readonly string SocketPath = Path.Combine(Path.GetTempPath(), "socket.tmp");

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
            webBuilder.ConfigureKestrel(options =>
            {
                if (File.Exists(SocketPath))
                {
                    File.Delete(SocketPath);
                }
                options.ListenUnixSocket(SocketPath, listenOptions =>
                {
                    listenOptions.Protocols = HttpProtocols.Http2;
                });
            });
        });

Ejemplo anterior:

Kestrel tiene compatibilidad integrada con los puntos de conexión de UDS. UDS se admite en Linux, macOS y las versiones modernas de Windows.

Configuración de cliente

GrpcChannel admite la realización de llamadas gRPC a través de transportes personalizados. Cuando se crea un canal, se puede configurar con un elemento SocketsHttpHandler que tenga un elemento ConnectCallback personalizado. La devolución de llamada permite al cliente realizar conexiones a través de transportes personalizados y, después, enviar solicitudes HTTP a través de ese transporte.

Ejemplo de fábrica de conexiones de sockets de dominio de Unix:

public class UnixDomainSocketConnectionFactory
{
    private readonly EndPoint _endPoint;

    public UnixDomainSocketConnectionFactory(EndPoint endPoint)
    {
        _endPoint = endPoint;
    }

    public async ValueTask<Stream> ConnectAsync(SocketsHttpConnectionContext _,
        CancellationToken cancellationToken = default)
    {
        var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);

        try
        {
            await socket.ConnectAsync(_endPoint, cancellationToken).ConfigureAwait(false);
            return new NetworkStream(socket, true);
        }
        catch
        {
            socket.Dispose();
            throw;
        }
    }
}

Uso del generador de conexiones personalizadas para crear un canal:

public static readonly string SocketPath = Path.Combine(Path.GetTempPath(), "socket.tmp");

public static GrpcChannel CreateChannel()
{
    var udsEndPoint = new UnixDomainSocketEndPoint(SocketPath);
    var connectionFactory = new UnixDomainSocketConnectionFactory(udsEndPoint);
    var socketsHttpHandler = new SocketsHttpHandler
    {
        ConnectCallback = connectionFactory.ConnectAsync
    };

    return GrpcChannel.ForAddress("http://localhost", new GrpcChannelOptions
    {
        HttpHandler = socketsHttpHandler
    });
}

Los canales creados con el código anterior envían llamadas gRPC a través de sockets de dominio de Unix. Se puede implementar la compatibilidad con otras tecnologías IPC por medio de la extensibilidad en Kestrel y SocketsHttpHandler.