使用 gRPC 和命名管道进行进程间通讯

作者:James Newton-King

.NET 支持使用 gRPC 的进程间通信(IPC)。 有关开始使用 gRPC 在进程之间通信的详细信息,请参阅 与 gRPC 的进程间通信

命名管道 是一种 IPC 传输方式,支持 Windows 的所有版本。 命名管道与 Windows 安全性 集成良好,以控制客户端对管道的访问。 本文讨论如何通过命名管道配置 gRPC 通信。

先决条件

  • .NET 8 或更高版本
  • Windows操作系统

服务器配置

命名管道由配置Program.csKestrel支持。

var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(serverOptions =>
{
    serverOptions.ListenNamedPipe("MyPipeName", listenOptions =>
    {
        listenOptions.Protocols = HttpProtocols.Http2;
    });
});

上面的示例:

  • ConfigureKestrel中配置Kestrel的终结点。
  • 调用 ListenNamedPipe 以侦听具有指定名称的命名管道。
  • 创建未配置为使用 HTTPS 的命名管道终结点。 有关启用 HTTPS 的信息,请参阅 Kestrel HTTPS 终结点配置

为命名管道配置“PipeSecurity”

若要控制哪些用户或组可以连接,请使用该 NamedPipeTransportOptions 类。 这允许指定自定义 PipeSecurity 对象。

示例:

using Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes;
using System.IO.Pipes;
using System.Security.AccessControl;

var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(serverOptions =>
{
    serverOptions.ListenNamedPipe("MyPipeName", listenOptions =>
    {
        listenOptions.Protocols = HttpProtocols.Http2;

        // Configure PipeSecurity
        listenOptions.UseNamedPipes(options =>
        {
            var pipeSecurity = new PipeSecurity();
            // Grant read/write access to the Users group
            pipeSecurity.AddAccessRule(new PipeAccessRule(
                "Users",
                PipeAccessRights.ReadWrite,
                AccessControlType.Allow));
            // Add additional rules as needed

            options.PipeSecurity = pipeSecurity;
        });
    });
});

上面的示例:

  • 使用 UseNamedPipes 来访问和配置 NamedPipeTransportOptions
  • 设置属性 PipeSecurity 以控制哪些用户或组可以连接到命名管道。
  • Users 组授予读/写访问权限。 可以根据需要为方案添加其他安全规则。

自定义 Kestrel 命名管道终结点

Kestrel的命名管道支持启用高级自定义,允许你使用此选项为每个终结点配置不同的安全设置。 此方法非常适合多个命名管道终结点需要唯一访问控制的方案。 从 .NET 9 开始,可以自定义每个终结点的管道。

在某些情况下,一个需要两个具有不同访问安全性的管道终结点的Kestrel应用程序可以非常有用。 CreateNamedPipeServerStream 选项可用于使用自定义安全设置创建管道,具体取决于管道名称。


var builder = WebApplication.CreateBuilder();
builder.WebHost.ConfigureKestrel(options =>
{
    options.ListenNamedPipe("pipe1");
    options.ListenNamedPipe("pipe2");
});

builder.WebHost.UseNamedPipes(options =>
{
    options.CreateNamedPipeServerStream = (context) =>
    {
        var pipeSecurity = CreatePipeSecurity(context.NamedPipeEndpoint.PipeName);

        return NamedPipeServerStreamAcl.Create(context.NamedPipeEndpoint.PipeName, PipeDirection.InOut,
            NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Byte,
            context.PipeOptions, inBufferSize: 0, outBufferSize: 0, pipeSecurity);
    };
});

客户端配置

GrpcChannel 支持通过自定义传输进行 gRPC 调用。 创建通道后,可以使用具有自定义SocketsHttpHandler的通道进行配置ConnectCallback。 回调允许客户端通过自定义传输建立连接,然后通过该传输发送 HTTP 请求。

注释

GrpcChannel 的某些连接功能(例如客户端负载均衡和通道状态)不能与命名管道一起使用。

命名管道连接工厂示例:

public class NamedPipesConnectionFactory
{
    private readonly string pipeName;

    public NamedPipesConnectionFactory(string pipeName)
    {
        this.pipeName = pipeName;
    }

    public async ValueTask<Stream> ConnectAsync(SocketsHttpConnectionContext _,
        CancellationToken cancellationToken = default)
    {
        var clientStream = new NamedPipeClientStream(
            serverName: ".",
            pipeName: this.pipeName,
            direction: PipeDirection.InOut,
            options: PipeOptions.WriteThrough | PipeOptions.Asynchronous,
            impersonationLevel: TokenImpersonationLevel.Anonymous);

        try
        {
            await clientStream.ConnectAsync(cancellationToken).ConfigureAwait(false);
            return clientStream;
        }
        catch
        {
            clientStream.Dispose();
            throw;
        }
    }
}

使用自定义连接工厂创建通道:

public static GrpcChannel CreateChannel()
{
    var connectionFactory = new NamedPipesConnectionFactory("MyPipeName");
    var socketsHttpHandler = new SocketsHttpHandler
    {
        ConnectCallback = connectionFactory.ConnectAsync
    };

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

使用上述代码创建的通道通过命名管道发送 gRPC 调用。