Aufrufen von gRPC-Diensten mithilfe eines .NET-Clients
Hinweis
Dies ist nicht die neueste Version dieses Artikels. Die aktuelle Version finden Sie in der .NET 9-Version dieses Artikels.
Warnung
Diese Version von ASP.NET Core wird nicht mehr unterstützt. Weitere Informationen finden Sie in der Supportrichtlinie für .NET und .NET Core. Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.
Wichtig
Diese Informationen beziehen sich auf ein Vorabversionsprodukt, das vor der kommerziellen Freigabe möglicherweise noch wesentlichen Änderungen unterliegt. Microsoft gibt keine Garantie, weder ausdrücklich noch impliziert, hinsichtlich der hier bereitgestellten Informationen.
Die aktuelle Version finden Sie in der .NET 9-Version dieses Artikels.
Im NuGet-Paket Grpc.Net.Client ist eine .NET-gRPC-Clientbibliothek verfügbar. Im vorliegenden Dokument wird Folgendes erläutert:
- Konfigurieren eines gRPC-Clients, um gRPC-Dienste aufzurufen
- Durchführen von gRPC-Aufrufe von unären, Serverstreaming- und Clientstreamingmethoden sowie von Methoden für bidirektionales Streaming.
Konfigurieren eines gRPC-Clients
gRPC-Clients sind konkrete Clienttypen, die aus .proto
-Dateien generiert werden. Der konkrete gRPC-Client verfügt über Methoden, die in den gRPC-Dienst in der .proto
-Datei übersetzt werden. Beispielsweise generiert ein Dienst namens Greeter
einen GreeterClient
-Typ mit Methoden zum Aufrufen des Diensts.
Ein gRPC-Client wird aus einem Kanal erstellt. Starten Sie das Erstellen eines Kanals mithilfe von GrpcChannel.ForAddress
, und verwenden Sie dann den Kanal, um einen gRPC-Client zu erstellen:
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greet.GreeterClient(channel);
Ein Kanal steht für eine langlebige Verbindung zu einem gRPC-Dienst. Wenn ein Kanal erstellt wird, wird er mit Optionen zum Aufrufen eines Diensts konfiguriert. Die für Aufrufe verwendete Klasse HttpClient
, die maximale Größe für gesendete und empfange Nachrichten sowie die Protokollierung können in der Klasse GrpcChannelOptions
angegeben und mit GrpcChannel.ForAddress
verwendet werden. Eine vollständige Liste der Optionen finden Sie unter Konfigurieren von Clientoptionen.
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var greeterClient = new Greet.GreeterClient(channel);
var counterClient = new Count.CounterClient(channel);
// Use clients to call gRPC services
Konfigurieren von TLS
Die Sicherheit auf Verbindungsebene von gRPC-Client und dem aufgerufenen Dienst muss übereinstimmen. Beim Erstellen des gRPC-Kanals wird für den gRPC-Client die Verwendung von Transport Layer Security (TLS) konfiguriert. Ein gRPC-Client löst einen Fehler aus, wenn beim Aufrufen eines Diensts die Sicherheit auf Verbindungsebene von Kanal und nicht Dienst nicht übereinstimmen.
Stellen Sie beim Konfigurieren eines gRPC-Kanals für die Verwendung von TLS sicher, dass die Serveradresse mit https
beginnt. GrpcChannel.ForAddress("https://localhost:5001")
verwendet beispielsweise das HTTPS-Protokoll. Der gRPC-Kanal handelt automatisch eine mit TLS gesicherte Verbindung aus und verwendet für gRPC-Aufrufe eine sichere Verbindung.
Tipp
gRPC unterstützt die Authentifizierung mit Clientzertifikaten über TLS. Informationen zum Konfigurieren von Clientzertifikaten mit einem gRPC-Kanal finden Sie unter Authentifizierung und Autorisierung in gRPC für ASP.NET Core.
Stellen Sie zum Aufrufen von unsicheren gRPC-Diensten sicher, dass die Serveradresse mit http
beginnt. GrpcChannel.ForAddress("http://localhost:5000")
verwendet beispielsweise das HTTP-Protokoll. Wenn Sie unsichere gRPC-Dienste mit dem .NET-Client aufrufen möchten, sind in .NET Core 3.1 weitere Konfigurationsschritte erforderlich.
Clientleistung
Kanal- und Clientleistung und -nutzung:
- Das Erstellen eines Kanals kann ein kostspieliger Vorgang sein. Wenn ein Kanal für gRPC-Aufrufe wiederverwendet wird, stehen Leistungsvorteile zur Verfügung.
- Ein Kanal verwaltet Verbindungen mit dem Server. Wenn die Verbindung geschlossen wird oder verloren geht, wird der Kanal beim nächsten Herstellen eines gRPC-Aufrufs automatisch wieder verbunden.
- gRPC-Clients werden mit Kanälen erstellt. Bei gRPC-Clients handelt es sich um Lightweightobjekte. Sie müssen nicht zwischengespeichert oder wiederverwendet werden.
- Aus einem Kanal können mehrere gRPC-Clients erstellt werden, einschließlich verschiedener Clienttypen.
- Ein Kanal und Clients, die aus dem Kanal erstellt wurden, können sicher von mehreren Threads verwendet werden.
- Clients, die aus dem Kanal erstellt wurden, können mehrere gleichzeitige Aufrufe durchführen.
GrpcChannel.ForAddress
ist nicht die einzige Option für das Erstellen eines gRPC-Clients. Wenn Sie gRPC-Dienste in einer ASP.NET Core-App aufrufen, sollten Sie die Integration der gRPC-Clientfactory in Erwägung ziehen. Die gRPC-Integration mit HttpClientFactory
bietet eine zentralisierte Alternative zur Erstellung von gRPC-Clients.
Hinweis
Das Aufrufen von gRPC über HTTP/2 mit Grpc.Net.Client
wird in Xamarin aktuell nicht unterstützt. Die HTTP/2-Unterstützung soll jedoch in einem zukünftigen Release von Xamarin optimiert werden. Grpc.Core und gRPC-Web sind funktionsfähige Alternativen, die aktuell verwendet werden können.
Durchführen von gRPC-Aufrufen
Ein gRPC-Aufruf wird initiiert, indem eine Methode auf dem Client aufgerufen wird. Der gRPC-Client verarbeitet die Nachrichtenserialisierung und leitet den gRPC-Aufruf an den entsprechenden Dienst weiter.
Bei gRPC stehen verschiedene Methodentypen zur Verfügung. Wie der Client verwendet wird, um einen gRPC-Aufruf durchzuführen, hängt vom Typ der aufgerufenen Methode ab. Es gibt die folgenden gRPC-Methodentypen:
- Unär
- Serverstreaming
- Clientstreaming
- Bidirektionales Streaming
Unärer Aufruf
Ein unärer Aufruf beginnt damit, dass der Client eine Anforderungsnachricht sendet. Sobald der Dienst abgeschlossen ist, wird eine Antwortnachricht zurückgegeben.
var client = new Greet.GreeterClient(channel);
var response = await client.SayHelloAsync(new HelloRequest { Name = "World" });
Console.WriteLine("Greeting: " + response.Message);
// Greeting: Hello World
Alle unären Dienstmethoden in der .proto
-Datei resultieren in zwei .NET-Methoden für den konkreten gRPC-Clienttyp, um die Methode aufzurufen: eine asynchrone Methode und eine blockierende Methode. Bei GreeterClient
gibt es beispielsweise zwei Möglichkeiten, SayHello
aufzurufen:
GreeterClient.SayHelloAsync
ruft denGreeter.SayHello
-Dienst asynchron auf und kann erwartet werden.GreeterClient.SayHello
ruft denGreeter.SayHello
-Dienst auf und blockiert, bis der Vorgang abgeschlossen ist. Verwenden Sie diese Möglichkeit nicht für asynchronen Code.
Serverstreamingaufruf
Ein Serverstreamingaufruf beginnt damit, dass der Client eine Anforderungsnachricht sendet. ResponseStream.MoveNext()
liest Nachrichten, die vom Dienst gestreamt werden. Der Serverstreamingaufruf ist abgeschlossen, wenn ResponseStream.MoveNext()
false
zurückgibt.
var client = new Greet.GreeterClient(channel);
using var call = client.SayHellos(new HelloRequest { Name = "World" });
while (await call.ResponseStream.MoveNext())
{
Console.WriteLine("Greeting: " + call.ResponseStream.Current.Message);
// "Greeting: Hello World" is written multiple times
}
Wenn Sie C# 8 oder höher verwenden, kann die await foreach
-Syntax verwendet werden, um Nachrichten zu lesen. Die IAsyncStreamReader<T>.ReadAllAsync()
-Erweiterungsmethode liest alle Nachrichten aus dem Antwortstream:
var client = new Greet.GreeterClient(channel);
using var call = client.SayHellos(new HelloRequest { Name = "World" });
await foreach (var response in call.ResponseStream.ReadAllAsync())
{
Console.WriteLine("Greeting: " + response.Message);
// "Greeting: Hello World" is written multiple times
}
Der vom Starten eines Serverstreamingaufrufs zurückgegebene Typ implementiert IDisposable
. Löschen Sie immer einen Streamingaufruf, um sicherzustellen, dass er angehalten wird und alle Ressourcen bereinigt werden.
Clientstreamingaufruf
Ein Clientstreamingaufruf beginnt, ohne dass der Client eine Nachricht sendet. Der Client kann Nachrichten mit RequestStream.WriteAsync
senden. Wenn der Client das Senden von Nachrichten abgeschlossen hat, sollte RequestStream.CompleteAsync()
aufgerufen werden, damit der Dienst benachrichtigt wird. Der Aufruf ist abgeschlossen, wenn der Dienst eine Antwortnachricht zurückgibt.
var client = new Counter.CounterClient(channel);
using var call = client.AccumulateCount();
for (var i = 0; i < 3; i++)
{
await call.RequestStream.WriteAsync(new CounterRequest { Count = 1 });
}
await call.RequestStream.CompleteAsync();
var response = await call;
Console.WriteLine($"Count: {response.Count}");
// Count: 3
Der vom Starten eines Clientstreamingaufrufs zurückgegebene Typ implementiert IDisposable
. Löschen Sie immer einen Streamingaufruf, um sicherzustellen, dass er angehalten wird und alle Ressourcen bereinigt werden.
Aufruf von bidirektionalem Streaming
Ein Aufruf für bidirektionales Streaming beginnt, ohne dass der Client eine Nachricht sendet. Der Client kann Nachrichten mit RequestStream.WriteAsync
senden. Auf vom Dienst gestreamte Nachrichten kann mit ResponseStream.MoveNext()
oder ResponseStream.ReadAllAsync()
zugegriffen werden. Der Aufruf für bidirektionales Streaming ist abgeschlossen, wenn ResponseStream
keine weiteren Nachrichten mehr hat.
var client = new Echo.EchoClient(channel);
using var call = client.Echo();
Console.WriteLine("Starting background task to receive messages");
var readTask = Task.Run(async () =>
{
await foreach (var response in call.ResponseStream.ReadAllAsync())
{
Console.WriteLine(response.Message);
// Echo messages sent to the service
}
});
Console.WriteLine("Starting to send messages");
Console.WriteLine("Type a message to echo then press enter.");
while (true)
{
var result = Console.ReadLine();
if (string.IsNullOrEmpty(result))
{
break;
}
await call.RequestStream.WriteAsync(new EchoMessage { Message = result });
}
Console.WriteLine("Disconnecting");
await call.RequestStream.CompleteAsync();
await readTask;
Um die beste Leistung zu erzielen und unnötige Fehler beim Client und beim Dienst zu vermeiden, versuchen Sie, bidirektionale Streamingaufrufe ordnungsgemäß abzuschließen. Ein bidirektionaler Aufruf wird ordnungsgemäß abgeschlossen, wenn der Server das Lesen des Anforderungsdatenstroms und der Client das Lesen des Antwortdatenstroms beendet hat. Der vorherige Beispielaufruf ist ein Beispiel für einen bidirektionalen Aufruf, der ordnungsgemäß beendet wird. Im Aufruf führt der Client die folgenden Aktionen aus:
- Er startet einen neuen bidirektionalen Streamingaufruf, indem
EchoClient.Echo
aufgerufen wird. - Er erstellt einen Hintergrundtask zum Lesen von Nachrichten aus dem Dienst mit
ResponseStream.ReadAllAsync()
. - Er sendet Nachrichten mit
RequestStream.WriteAsync
an den Server. - Er benachrichtigt den Server, dass das Senden von Nachrichten mit
RequestStream.CompleteAsync()
abgeschlossen wurde. - Er wartet, bis der Hintergrundtask alle eingehenden Nachrichten gelesen hat.
Während des Aufrufs von bidirektionalem Streaming können sich Client und Dienst jederzeit gegenseitig Nachrichten senden. Die beste Clientlogik für die Interaktion mit einem bidirektionalem Aufruf variiert je nach Dienstlogik.
Der vom Starten eines bidirektionalen Streamingaufrufs zurückgegebene Typ implementiert IDisposable
. Löschen Sie immer einen Streamingaufruf, um sicherzustellen, dass er angehalten wird und alle Ressourcen bereinigt werden.
Zugreifen auf gRPC-Header
gRPC-Aufrufe geben Antwortheader zurück. HTTP-Antwortheader übergeben Name/Wert-Metadaten über einen Befehl, der nicht mit der zurückgegebenen Nachricht verknüpft ist.
Auf Header kann über ResponseHeadersAsync
zugegriffen werden, wodurch eine Sammlung von Metadaten zurückgegeben wird. Da Header in der Regel mit der Antwortnachricht zurückgegeben werden, müssen Sie darauf warten.
var client = new Greet.GreeterClient(channel);
using var call = client.SayHelloAsync(new HelloRequest { Name = "World" });
var headers = await call.ResponseHeadersAsync;
var myValue = headers.GetValue("my-trailer-name");
var response = await call.ResponseAsync;
Verwendung von ResponseHeadersAsync
:
- Auf das Ergebnis von
ResponseHeadersAsync
muss gewartet werden, um die Headersammlung zu erhalten. - Darauf muss nicht vor
ResponseAsync
(oder den Antwortstream beim Streaming) zugegriffen werden. Wenn eine Antwort zurückgegeben wurde, gibtResponseHeadersAsync
umgehend Header zurück. - Löst eine Ausnahme aus, wenn eine Verbindung bestand, oder Serverfehler und Header nicht für den gRPC-Aufruf zurückgegeben wurden.
Zugreifen auf gRPC-Nachspanne
gRPC-Aufrufe können Antwortnachspanne zurückgeben. Nachspanne werden verwendet, um Name/Wert-Metadaten zu einem Aufruf bereitzustellen. Nachspanne stellen eine ähnliche Funktionalität wie HTTP-Header bereit, werden aber am Ende des Aufrufs empfangen.
Auf Nachspanne kann über GetTrailers()
zugegriffen werden, die eine Sammlung von Metadaten zurückgibt. Nach dem Abschluss der Antwort werden Nachspanne zurückgegeben. Daher müssen Sie auf alle Antwortnachrichten warten, bevor Sie auf die Nachspanne zugreifen.
Unäre und Clientstreamingaufrufe müssen auf ResponseAsync
warten, bevor sie GetTrailers()
aufrufen können:
var client = new Greet.GreeterClient(channel);
using var call = client.SayHelloAsync(new HelloRequest { Name = "World" });
var response = await call.ResponseAsync;
Console.WriteLine("Greeting: " + response.Message);
// Greeting: Hello World
var trailers = call.GetTrailers();
var myValue = trailers.GetValue("my-trailer-name");
Server- und bidirektionale Streamingaufrufe müssen zuerst vollständig den Antwortdatenstrom abwarten, bevor sie GetTrailers()
aufrufen können:
var client = new Greet.GreeterClient(channel);
using var call = client.SayHellos(new HelloRequest { Name = "World" });
await foreach (var response in call.ResponseStream.ReadAllAsync())
{
Console.WriteLine("Greeting: " + response.Message);
// "Greeting: Hello World" is written multiple times
}
var trailers = call.GetTrailers();
var myValue = trailers.GetValue("my-trailer-name");
Auf Nachspanne kann auch über RpcException
zugegriffen werden. Ein Dienst kann Nachspanne eventuell zusammen mit einem gRPC-Status von „nicht in Ordnung“ zurückgeben. In dieser Situation werden die Nachspanne vom gRPC-Client aus der ausgelösten Ausnahme abgerufen:
var client = new Greet.GreeterClient(channel);
string myValue = null;
try
{
using var call = client.SayHelloAsync(new HelloRequest { Name = "World" });
var response = await call.ResponseAsync;
Console.WriteLine("Greeting: " + response.Message);
// Greeting: Hello World
var trailers = call.GetTrailers();
myValue = trailers.GetValue("my-trailer-name");
}
catch (RpcException ex)
{
var trailers = ex.Trailers;
myValue = trailers.GetValue("my-trailer-name");
}
Konfigurieren von Fristen
Das Konfigurieren einer gRPC-Aufruffrist wird empfohlen, da sie eine Obergrenze für den Zeitraum vorgibt, in dem ein Aufruf ausgeführt werden kann. Dadurch wird verhindert, dass fehlerhafte Dienste unbegrenzt lange ausgeführt werden und die Serverressourcen belasten. Fristen stellen einen wichtigen Aspekt bei der Erstellung zuverlässiger Apps dar.
Konfigurieren Sie CallOptions.Deadline
, um eine Frist für einen gRPC-Aufruf festzulegen:
var client = new Greet.GreeterClient(channel);
try
{
var response = await client.SayHelloAsync(
new HelloRequest { Name = "World" },
deadline: DateTime.UtcNow.AddSeconds(5));
// Greeting: Hello World
Console.WriteLine("Greeting: " + response.Message);
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.DeadlineExceeded)
{
Console.WriteLine("Greeting timeout.");
}
Weitere Informationen finden Sie unter Zuverlässige gRPC-Dienste mit Fristen und Abbrüchen.