使用 gRPC 进行进程间通信

在同一台计算机上运行的进程可以设计为相互通信。 操作系统提供了可实现快速高效的进程间通信 (IPC) 的技术。 IPC 技术的常见示例是 Unix 域套接字和命名管道。

.NET 为使用 gRPC 的进程间通信提供支持。

ASP.NET Core 中对命名管道的内置支持需要 .NET 8 或更高版本。

开始使用

IPC 调用从客户端发送到服务器。 若要使用 gRPC 在计算机上的应用之间进行通信,必须至少一个应用托管 ASP.NET Core gRPC 服务器。

ASP.NET Core gRPC 服务器通常从 gRPC 模板创建。 模板创建的项目文件使用 Microsoft.NET.SDK.Web 作为 SDK:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <ItemGroup>
    <PackageReference Include="Grpc.AspNetCore" Version="2.47.0" />
    <Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
  </ItemGroup>

</Project>

Microsoft.NET.SDK.Web SDK 值会自动添加对 ASP.NET Core 框架的引用。 该引用允许应用使用托管服务器所需的 ASP.NET Core 类型。

还可以将服务器添加到现有的非 ASP.NET Core 项目,例如 Windows 服务、WPF 应用或 WinForms 应用。 有关详细信息,请参阅非 ASP.NET Core 项目中的主机 gRPC

进程间通信 (IPC) 传输

不同计算机上的客户端和服务器之间的 gRPC 调用通常通过 TCP 套接字发送。 TCP 是跨网络或 Internet 进行通信的不错选择。 但是,在同一台计算机上的进程之间通信时,IPC 传输提供了优势:

  • 开销更少,传输速度更快。
  • 与 OS 安全功能集成。
  • 不使用 TCP 端口 - 这是有限的资源。

.NET 支持多个 IPC 传输:

跨平台应用可能需要选择不同的 IPC 传输,具体取决于 OS。 应用可以在启动时检查 OS,并为该平台选择所需的传输:

var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(serverOptions =>
{
    if (OperatingSystem.IsWindows())
    {
        serverOptions.ListenNamedPipe("MyPipeName");
    }
    else
    {
        var socketPath = Path.Combine(Path.GetTempPath(), "socket.tmp");
        serverOptions.ListenUnixSocket(socketPath);
    }

    serverOptions.ConfigureEndpointDefaults(listenOptions =>
    {
        listenOptions.Protocols = HttpProtocols.Http2;
    });
});

安全注意事项

IPC 应用发送和接收 RPC 调用。 外部通信是针对 IPC 应用的潜在攻击途径,必须得到适当的保护。

针对意外调用方保护 IPC 服务器应用

IPC 服务器应用托管 RPC 服务供其他应用调用。 应对传入调用方进行身份验证,以防止不受信任的客户端对服务器进行 RPC 调用。

传输安全性是保护服务器的一种方式。 IPC 传输(如 Unix 域套接字和命名管道)支持基于操作系统权限限制访问:

  • 命名管道支持使用 Windows 访问控制模型保护管道。 使用 PipeSecurity 类启动服务器时,可以在 .NET 中配置访问权限。
  • Unix 域套接字支持使用文件权限保护套接字。

保护 IPC 服务器的另一种方式是使用 ASP.NET Core 中内置的身份验证和授权。 例如,可以将服务器配置为要求证书身份验证。 没有所需证书的客户端应用进行的 RPC 调用失败,具有未授权响应。

验证 IPC 客户端应用中的服务器

客户端应用必须验证所调用的服务器的标识。 验证是防止恶意行动者停止受信任的服务器、运行自己的服务器以及接受来自客户端的传入数据所必需的。

命名管道支持获取运行服务器的帐户。 客户端可以验证服务器是否已由预期的帐户启动:

internal static bool CheckPipeConnectionOwnership(
    NamedPipeClientStream pipeStream, SecurityIdentifier expectedOwner)
{
    var remotePipeSecurity = pipeStream.GetAccessControl();
    var remoteOwner = remotePipeSecurity.GetOwner(typeof(SecurityIdentifier));
    return expectedOwner.Equals(remoteOwner);
}

验证服务器的另一种方式是使用 ASP.NET Core 内部的 HTTPS 保护其终结点。 客户端可以将 SocketsHttpHandler 配置为在建立连接时使用预期的证书来验证服务器。

var socketsHttpHandler = new SocketsHttpHandler()
{
    SslOptions = new SslOptions()
    {
        RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
        {
            if (sslPolicyErrors != SslPolicyErrors.None)
            {
                return false;
            }

            // Validate server cert thumbprint matches the expected thumbprint.
        }
    }
};

防范命名管道特权提升

命名管道支持称为模拟的功能。 使用模拟,命名管道服务器可以使用客户端用户的权限执行代码。 这是一项强大的功能,但可以允许低特权服务器模拟高特权调用方,然后运行恶意代码。

客户端可以通过不允许在连接到服务器时模拟来防范此攻击。 除非服务器要求,否则在创建客户端连接时应使用 NoneAnonymousTokenImpersonationLevel 值:

using var pipeClient = new NamedPipeClientStream(
    serverName: ".", pipeName: "testpipe", PipeDirection.In, PipeOptions.None, TokenImpersonationLevel.None);
await pipeClient.ConnectAsync();

TokenImpersonationLevel.None 是没有 impersonationLevel 参数的 NamedPipeClientStream 构造函数中的默认值。

配置客户端和服务器

必须将客户端和服务器配置为使用进程间通信 (IPC) 传输。 有关将 Kestrel 和 SocketsHttpHandler 配置为使用 IPC 的详细信息,请参阅:

注意

ASP.NET Core 中对命名管道的内置支持需要 .NET 8 或更高版本。

在同一台计算机上运行的进程可以设计为相互通信。 操作系统提供了可实现快速高效的进程间通信 (IPC) 的技术。 IPC 技术的常见示例是 Unix 域套接字和命名管道。

.NET 为使用 gRPC 的进程间通信提供支持。

注意

ASP.NET Core 中对命名管道的内置支持需要 .NET 8 或更高版本。
有关详细信息,请参阅本主题的 .NET 8 或更高版本

开始使用

gRPC 调用从客户端发送到服务器。 若要使用 gRPC 在计算机上的应用之间进行通信,必须至少一个应用托管 ASP.NET Core gRPC 服务器。

通过向项目添加 Microsoft.AspNetCore.App 框架,可以在任何使用 .NET Core 3.1 或更高版本的应用中托管 ASP.NET Core 和 gRPC。

<Project Sdk="Microsoft.NET.Sdk">

  <ItemGroup>
    <FrameworkReference Include="Microsoft.AspNetCore.App" />
    <PackageReference Include="Grpc.AspNetCore" Version="2.47.0" />
    <Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
  </ItemGroup>

</Project>

上面的项目文件可完成以下操作:

  • Microsoft.AspNetCore.App 添加框架引用。 该框架引用允许非 ASP.NET Core 应用(例如 Windows 服务、WPF 应用或 WinForms 应用)使用 ASP.NET Core 和托管 ASP.NET Core 服务器。
  • Grpc.AspNetCore 添加 NuGet 包引用。
  • 添加 .proto 文件。

配置 Unix 域套接字

不同计算机上的客户端和服务器之间的 gRPC 调用通常通过 TCP 套接字发送。 TCP 非常适用于网络中的通信。 Unix 域套接字 (UDS) 是一种广泛受支持的 IPC 技术,当客户端和服务器位于同一台计算机上时,它比 TCP 更有效。 .NET 在客户端和服务器应用中提供对 UDS 的内置支持。

要求:

服务器配置

Kestrel 支持 Unix 域套接字 (UDS),该套接字在 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;
                });
            });
        });

上面的示例:

  • ConfigureKestrel 中配置 Kestrel 的终结点。
  • 调用 ListenUnixSocket 来侦听具有指定路径的 UDS。
  • 创建未配置为使用 HTTPS 的 UDS 终结点。 有关启用 HTTPS 的信息,请参阅 Kestrel HTTPS 终结点配置

客户端配置

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

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;
        }
    }
}

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

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
    });
}

使用上述代码创建的通道通过 Unix 域套接字发送 gRPC 调用。 可以使用 Kestrel 和 SocketsHttpHandler 中的扩展性实现对其他 IPC 技术的支持。