Udostępnij za pośrednictwem


Wywoływanie usług gRPC przy użyciu klienta platformy .NET

Uwaga

Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu platformy .NET 9.

Ostrzeżenie

Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz zasady pomocy technicznej platformy .NET i platformy .NET Core. Aby zapoznać się z bieżącą wersją, zobacz wariant tego artykułu .NET 9.

Ważne

Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.

Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu dla .NET 9.

Biblioteka klienta gRPC platformy .NET jest dostępna w pakiecie NuGet Grpc.Net.Client . W tym dokumencie wyjaśniono, jak:

  • Skonfiguruj klienta gRPC w celu wywoływania usług gRPC.
  • Wykonaj wywołania gRPC do metod jednorzędowych, serwerowego przesyłania strumieniowego, klienckiego przesyłania strumieniowego i dwukierunkowego przesyłania strumieniowego.

Konfigurowanie klienta gRPC

Klienci gRPC to konkretne typy klientów generowane na podstawie plików .proto. Konkretny klient gRPC ma metody, które przekładają się na usługę gRPC w pliku .proto. Na przykład usługa o nazwie Greeter generuje typ GreeterClient z metodami do wywoływania tej usługi.

Klient gRPC jest tworzony na podstawie kanału. Zacznij od utworzenia GrpcChannel.ForAddress kanału, a następnie użyj kanału, aby utworzyć klienta gRPC:

var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greet.GreeterClient(channel);

Kanał reprezentuje długotrwałe połączenie z usługą gRPC. Po utworzeniu kanału jest on konfigurowany z opcjami związanymi z wywoływaniem usługi. Na przykład HttpClient używane do wykonywania połączeń, maksymalny rozmiar komunikatów wysyłania i odbierania oraz rejestrowanie można określić na GrpcChannelOptions i używać z GrpcChannel.ForAddress. Aby uzyskać pełną listę opcji, zobacz Opcje konfiguracji klienta.

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

Konfigurowanie protokołu TLS

Klient gRPC musi używać tych samych zabezpieczeń na poziomie połączenia co wywołana usługa. Protokół TLS (Transport Layer Security) klienta gRPC jest konfigurowany podczas tworzenia kanału gRPC. Klient gRPC zgłasza błąd, gdy wywołuje usługę, a zabezpieczenia na poziomie połączenia kanału i usługi nie są zgodne.

Aby skonfigurować kanał gRPC do używania protokołu TLS, upewnij się, że adres serwera rozpoczyna się od https. Na przykład GrpcChannel.ForAddress("https://localhost:5001") używa protokołu HTTPS. Kanał gRPC automatycznie negocjuje połączenie zabezpieczone przez protokół TLS i używa bezpiecznego połączenia do nawiązywania wywołań gRPC.

Napiwek

Usługa gRPC obsługuje uwierzytelnianie certyfikatu klienta za pośrednictwem protokołu TLS. Aby uzyskać informacje na temat konfigurowania certyfikatów klienta za pomocą kanału gRPC, zobacz Uwierzytelnianie i autoryzacja w gRPC dla ASP.NET Core.

Aby wywołać niezabezpieczone usługi gRPC, upewnij się, że adres serwera zaczyna się od http. Na przykład GrpcChannel.ForAddress("http://localhost:5000") używa protokołu HTTP. W programie .NET Core 3.1 wymagana jest dodatkowa konfiguracja do wywoływania niezabezpieczonych usług gRPC przy użyciu klienta platformy .NET.

Wydajność klienta

Wydajność i użycie kanału i klienta:

  • Tworzenie kanału może być kosztowną operacją. Ponowne użycie kanału dla wywołań gRPC zapewnia korzyści związane z wydajnością.
  • Kanał zarządza połączeniami z serwerem. Jeśli połączenie zostanie zamknięte lub utracone, kanał automatycznie ponownie nawiąż połączenie przy następnym wywołaniu gRPC.
  • Klienci gRPC są tworzeni za pomocą kanałów. Klienci gRPC są lekkimi obiektami i nie muszą być buforowane ani ponownie używane.
  • Z kanału można stworzyć wielu klientów gRPC, w tym różnych typów klientów.
  • Kanał i klienci utworzeni na podstawie kanału mogą być bezpiecznie używani przez wiele wątków.
  • Klienci utworzeni z kanału mogą wykonywać wiele równoczesnych połączeń.

GrpcChannel.ForAddress nie jest jedyną opcją tworzenia klienta gRPC. W przypadku wywoływania usług gRPC z aplikacji ASP.NET Core należy rozważyć integrację z fabryką klienta gRPC. Integracja gRPC z usługą HttpClientFactory oferuje scentralizowaną alternatywę dla tworzenia klientów gRPC.

W przypadku wywoływania metod gRPC preferuj używanie programowania asynchronicznego z asynchroniczną funkcją async i await. Wykonywanie wywołań gRPC z blokowaniem, jak przy użyciu Task.Result lub Task.Wait(), uniemożliwia innym zadaniom korzystanie z wątku. Może to prowadzić do wyczerpania zasobów puli wątków i niskiej wydajności. Może to spowodować zawieszenie aplikacji z zakleszczeniem. Aby uzyskać więcej informacji, zobacz wywołania asynchroniczne w aplikacjach klienckich.

Nawiązywanie wywołań gRPC

Wywołanie gRPC jest inicjowane przez wywołanie metody na kliencie. Klient gRPC będzie obsługiwał serializację komunikatów oraz kierowanie wywołania gRPC do właściwej usługi.

GRPC ma różne typy metod. Sposób użycia klienta do wywołania gRPC zależy od typu wywoływanej metody. Typy metod gRPC to:

  • Jednoargumentowy
  • Przesyłanie strumieniowe serwera
  • Przesyłanie strumieniowe dla klienta
  • Dwukierunkowe przesyłanie strumieniowe

Jednoargumentowe wywołanie

Wywołanie unary rozpoczyna się od klienta wysyłającego żądanie. Po zakończeniu działania usługi zostanie zwrócony komunikat odpowiedzi.

var client = new Greet.GreeterClient(channel);
var response = await client.SayHelloAsync(new HelloRequest { Name = "World" });

Console.WriteLine("Greeting: " + response.Message);
// Greeting: Hello World

Każda metoda usługi jednoargumentowej w pliku .proto spowoduje utworzenie dwóch metod platformy .NET na konkretnym typie klienta gRPC dla wywołania metody: metody asynchronicznej i metody blokującej. Na przykład na GreeterClient są dwa sposoby na wywołanie SayHello:

  • GreeterClient.SayHelloAsync — asynchronicznie wywołuje usługę Greeter.SayHello. Można na to czekać.
  • GreeterClient.SayHello — wywołuje usługę Greeter.SayHello i blokuje aż do ukończenia. Nie używaj w kodzie asynchronicznym. Może powodować problemy z wydajnością i niezawodnością.

Aby uzyskać więcej informacji, zobacz wywołania asynchroniczne w aplikacjach klienckich.

Wywołanie strumieniowania serwera

Wywołanie przesyłania strumieniowego serwera rozpoczyna się od klienta wysyłającego komunikat żądania. ResponseStream.MoveNext() odczytuje komunikaty przesyłane strumieniowo z usługi. Wywołanie przesyłania strumieniowego serwera jest ukończone, gdy ResponseStream.MoveNext() zwraca wartość false.

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
}

W przypadku korzystania z języka C# 8 lub nowszego można użyć składni await foreach do odczytywania komunikatów. Metoda IAsyncStreamReader<T>.ReadAllAsync() rozszerzenia odczytuje wszystkie komunikaty ze strumienia odpowiedzi:

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
}

Typ zwracany przy uruchamianiu strumieniowego połączenia serwera implementuje IDisposable. Zawsze zamykaj wywołanie przesyłania strumieniowego, aby upewnić się, że zostaje zatrzymane, a wszystkie zasoby zostają oczyszczone.

Połączenie streamingu klienta

Wywołanie przesyłania strumieniowego klienta jest uruchamiane bez wysyłania komunikatu przez klienta. Klient może wybrać wysyłanie komunikatów za pomocą RequestStream.WriteAsync. Po zakończeniu wysyłania komunikatów przez klienta, należy wywołać RequestStream.CompleteAsync() w celu powiadomienia usługi. Wywołanie jest zakończone, gdy usługa zwraca komunikat odpowiedzi.

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

Typ zwrócony z uruchomienia wywołania przesyłania strumieniowego klienta implementuje IDisposable. Zawsze usuwaj wywołanie przesyłania strumieniowego, aby upewnić się, że jest zatrzymany, a wszystkie zasoby są czyszczone.

Dwukierunkowe wywołanie przesyłania strumieniowego

Dwukierunkowe wywołanie przesyłania strumieniowego rozpoczyna się bez wysyłania komunikatu przez klienta. Klient może wybrać wysyłanie komunikatów za pomocą RequestStream.WriteAsync. Komunikaty przesyłane strumieniowo z usługi są dostępne za pomocą ResponseStream.MoveNext() lub ResponseStream.ReadAllAsync(). Dwukierunkowe wywołanie przesyłania strumieniowego jest ukończone, gdy ResponseStream nie ma więcej komunikatów.

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;

Aby uzyskać najlepszą wydajność i uniknąć niepotrzebnych błędów w kliencie i usłudze, spróbuj w sposób płynny wykonać wywołania przesyłania strumieniowego dwukierunkowego. Wywołanie dwukierunkowe kończy się bezpiecznie po zakończeniu odczytywania strumienia żądania przez serwer, a klient zakończył odczytywanie strumienia odpowiedzi. Wcześniejsze przykładowe wywołanie jest jednym z przykładów dwukierunkowego wywołania, które kończy się elegancko. Podczas rozmowy telefonicznej klient:

  1. Uruchamia nowe dwukierunkowe wywołanie przesyłania strumieniowego, wywołując EchoClient.Echo.
  2. Tworzy zadanie w tle do odczytywania komunikatów z usługi przy użyciu polecenia ResponseStream.ReadAllAsync().
  3. Wysyła komunikaty do serwera za pomocą polecenia RequestStream.WriteAsync.
  4. Powiadamia serwer, że zakończono wysyłanie komunikatów za pomocą RequestStream.CompleteAsync().
  5. Czeka, aż zadanie w tle odczytuje wszystkie komunikaty przychodzące.

Podczas dwukierunkowego wywołania przesyłania strumieniowego klient i usługa mogą wysyłać komunikaty do siebie w dowolnym momencie. Najlepsza logika klienta do interakcji z wywołaniem dwukierunkowym różni się w zależności od logiki usługi.

Typ zwracany przez rozpoczęcie dwukierunkowego wywołania przesyłania strumieniowego implementuje IDisposable. Zawsze usuwaj wywołanie transmisji strumieniowej, aby upewnić się, że jest ono zatrzymane, a wszystkie zasoby są zwolnione.

Uzyskiwanie dostępu do nagłówków gRPC

GRPC wywołuje nagłówki odpowiedzi zwracanej. Nagłówki odpowiedzi HTTP przekazują metadane nazwy/wartości dotyczące wywołania, które nie jest powiązane z zwróconym komunikatem.

Nagłówki są dostępne przy użyciu metody ResponseHeadersAsync, która zwraca kolekcję metadanych. Nagłówki są zwykle zwracane wraz z komunikatem odpowiedzi; dlatego trzeba na nie oczekiwać.

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;

ResponseHeadersAsync użycie:

  • Musi poczekać na wynik ResponseHeadersAsync, aby uzyskać kolekcję nagłówków.
  • Nie trzeba uzyskiwać dostępu do ResponseAsync ani strumienia odpowiedzi przed przesyłaniem strumieniowym. Jeśli odpowiedź została zwrócona, ResponseHeadersAsync zwraca nagłówki natychmiast.
  • Zgłosi wyjątek, jeśli wystąpił błąd połączenia lub błąd serwera i jeśli nagłówki nie zostały zwrócone dla wywołania gRPC.

Dostęp do przyczep gRPC

Wywołania gRPC mogą zwracać nagłówki końcowe odpowiedzi. Przyczepy służą do podawania metadanych nazwy/wartości dotyczących wywołania. Trailery zapewniają podobne funkcje do nagłówków HTTP, ale są odbierane na końcu wywołania.

Trailery są dostępne przy użyciu GetTrailers(), który zwraca kolekcję metadanych. Przyczepy są zwracane po zakończeniu odpowiedzi. W związku z tym należy czekać na wszystkie komunikaty odpowiedzi przed uzyskaniem dostępu do zwiastunów.

Wywołania jednoargumentowe oraz przesyłania strumieniowego klienta muszą poczekać na ResponseAsync przed wywołaniem GetTrailers():

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

Wywołania serwera i dwukierunkowego przesyłania strumieniowego muszą zakończyć oczekiwanie na otrzymanie strumienia odpowiedzi przed wywołaniem metody GetTrailers():

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

Zwiastuny są również dostępne z witryny RpcException. Usługa może zwracać przyczepy wraz ze stanem gRPC innej niż OK. W takiej sytuacji przyczepy są pobierane z wyjątku zgłaszanego przez klienta gRPC:

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

Konfigurowanie terminu ostatecznego

Zaleca się skonfigurowanie limitu czasu dla wywołania gRPC, ponieważ określa on maksymalny czas trwania wywołania. Zapobiega nieprawidłowemu działaniu usług, aby nie działały w nieskończoność i nie wyczerpywały zasobów serwera. Terminy ostateczne to przydatne narzędzie do tworzenia niezawodnych aplikacji.

Skonfiguruj CallOptions.Deadline, aby ustawić termin na wywołanie gRPC.

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

Aby uzyskać więcej informacji, zobacz Reliable gRPC services with deadline and cancellation (Niezawodne usługi gRPC z terminami i anulowaniem).

Dodatkowe zasoby