Freigeben über


Prozessübergreifende Kommunikation mit gRPC und Benannten Rohren

Von James Newton-King

.NET unterstützt prozessübergreifende Kommunikation (Inter-Process Communication, IPC) mithilfe von gRPC. Weitere Informationen zu den ersten Schritten bei der Verwendung von gRPC zur Kommunikation zwischen Prozessen finden Sie unter Inter-Process Communication with gRPC.

Named Pipes ist ein IPC-Transport, der in allen Versionen von Windows unterstützt wird. Benannte Rohre sind gut in die Windows-Sicherheit integriert, um den Clientzugriff auf die Pipe zu steuern. In diesem Artikel wird erläutert, wie Sie die gRPC-Kommunikation über benannte Rohre konfigurieren.

Voraussetzungen

  • .NET 8 oder höher
  • Fenster

Serverkonfiguration

Benannte Pipes werden unterstützt von Kestrel, die in Program.cs konfiguriert sind.

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

Das vorherige Beispiel:

  • Die Endpunkte von Kestrel werden in ConfigureKestrel konfiguriert.
  • Ruft ListenNamedPipe auf, um eine benannte Pipe mit dem angegebenen Namen zu überwachen.
  • Erstellt einen benannten Pipe-Endpunkt, der nicht für die Verwendung von HTTPS konfiguriert ist. Informationen zum Aktivieren von HTTPS finden Sie unter Kestrel HTTPS-Endpunktkonfiguration.

Konfigurieren von PipeSecurity für Named Pipes

Verwenden Sie die NamedPipeTransportOptions Klasse, um zu steuern, welche Benutzer oder Gruppen eine Verbindung herstellen können. Dadurch kann ein benutzerdefiniertes PipeSecurity Objekt angegeben werden.

Beispiel:

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

Das vorherige Beispiel:

  • Verwendet UseNamedPipes, um auf NamedPipeTransportOptions zuzugreifen und es zu konfigurieren.
  • Legt die PipeSecurity Eigenschaft fest, um zu steuern, welche Benutzer oder Gruppen eine Verbindung mit der benannten Pipe herstellen können.
  • Gewährt Lese-/Schreibzugriff auf die Users Gruppe. Zusätzliche Sicherheitsregeln können nach Bedarf für das Szenario hinzugefügt werden.

Anpassen von Kestrel-Named-Pipe-Endpunkten

KestrelDie Unterstützung für benannte Pipes unterstützt erweiterte Anpassungen, sodass Sie verschiedene Sicherheitseinstellungen für jeden Endpunkt mithilfe der CreateNamedPipeServerStream-Option konfigurieren können. Dieser Ansatz eignet sich ideal für Szenarien, in denen mehrere benannte Pipe-Endpunkte eindeutige Zugriffssteuerungen erfordern. Die Möglichkeit zum Anpassen von Rohren pro Endpunkt ist ab .NET 9 verfügbar.

Ein Beispiel dafür, wo dies hilfreich ist, ist eine Kestrel App, die zwei Pipeendpunkte mit unterschiedlicher Zugriffssicherheit erfordert. Die CreateNamedPipeServerStream Option kann verwendet werden, um Pipes mit benutzerdefinierten Sicherheitseinstellungen zu erstellen, je nach Pipename.


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

Clientkonfiguration

GrpcChannel unterstützt gRPC-Aufrufe über benutzerdefinierte Transporte. Wenn ein Kanal erstellt wird, kann er mit einem SocketsHttpHandler konfiguriert werden, der über ein benutzerdefiniertes ConnectCallback verfügt. Mit dem Rückruf kann der Client Verbindungen über benutzerdefinierte Transporte herstellen und dann HTTP-Anforderungen über diesen Transport senden.

Hinweis

Einige Konnektivitätsfeatures von GrpcChannel, wie clientseitiger Lastenausgleich und Kanalstatus, können nicht zusammen mit Named Pipes verwendet werden.

Beispiel für eine Named-Pipes-Verbindungsfabrik

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

Verwenden der benutzerdefinierten Verbindungsfabrik zum Erstellen eines Kanals:

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

Kanäle, die mit dem vorherigen Code erstellt wurden, senden gRPC-Aufrufe über benannte Pipes.