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

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 jednoargumentowych, przesyłania strumieniowego serwera, przesyłania strumieniowego klienta i dwukierunkowych metod 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 GreeterClient typ z metodami wywoływania 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 skonfigurowany z opcjami związanymi z wywoływaniem usługi. Na przykład HttpClient używane do nawiązywania wywołań, maksymalny rozmiar komunikatu wysyłania i odbierania oraz rejestrowanie można określić na GrpcChannelOptions platformie i używać jej z GrpcChannel.ForAddressprogramem . 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żywanie kanału dla wywołań gRPC zapewnia korzyści 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ą tworzone za pomocą kanałów. Klienci gRPC są lekkimi obiektami i nie muszą być buforowane ani ponownie używane.
  • Na podstawie kanału można utworzyć wielu klientów gRPC, w tym różnych typów klientów.
  • Kanał i klienci utworzoni na podstawie kanału mogą być bezpiecznie używane przez wiele wątków.
  • Klienci utworzoni na podstawie kanału mogą wykonywać wiele równoczesnych wywołań.

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.

Uwaga

Wywoływanie funkcji gRPC za pośrednictwem protokołu HTTP/2 z Grpc.Net.Client programem nie jest obecnie obsługiwane na platformie Xamarin. Pracujemy nad ulepszeniem obsługi protokołu HTTP/2 w przyszłej wersji platformy Xamarin. Grpc.Core i gRPC-Web są realnymi alternatywami, które działają dzisiaj.

Nawiązywanie wywołań gRPC

Wywołanie gRPC jest inicjowane przez wywołanie metody na kliencie. Klient gRPC będzie obsługiwał serializacji komunikatów i zwraca się do 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 klienta
  • Dwukierunkowe przesyłanie strumieniowe

Jednoargumentowe połączenie

Jednoargumentowe wywołanie rozpoczyna się od klienta wysyłającego komunikat żądania. 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 .proto pliku spowoduje utworzenie dwóch metod platformy .NET na konkretnym typie klienta gRPC do wywoływania metody: metody asynchronicznej i metody blokującej. Na przykład GreeterClient istnieją dwa sposoby wywoływania metody SayHello:

  • GreeterClient.SayHelloAsync - wywołuje Greeter.SayHello usługę asynchronicznie. Można oczekiwać.
  • GreeterClient.SayHello - wywołuje Greeter.SayHello usługę i blokuje do ukończenia. Nie używaj w kodzie asynchronicznym.

Wywołanie przesyłania strumieniowego 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 await foreach składnia może służyć 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
}

Wywołanie przesyłania strumieniowego 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.WriteAsyncpolecenia . Po zakończeniu wysyłania komunikatów przez klienta należy wywołać polecenie w RequestStream.CompleteAsync() 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

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.WriteAsyncpolecenia . Komunikaty przesyłane strumieniowo z usługi są dostępne za pomocą ResponseStream.MoveNext() polecenia 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 bezpiecznie wykonać dwukierunkowe wywołania przesyłania strumieniowego. Wywołanie dwukierunkowe kończy się bezpiecznie po zakończeniu odczytywania strumienia żądania przez serwer, a klient zakończył odczytywanie strumienia odpowiedzi. Poprzednie przykładowe wywołanie jest jednym z przykładów dwukierunkowego wywołania, które kończy się bezpiecznie. W wywołaniu klient:

  1. Uruchamia nowe dwukierunkowe wywołanie przesyłania strumieniowego przez wywołanie metody 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, na który zakończył wysyłanie komunikatów za pomocą polecenia 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.

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 z komunikatem odpowiedzi; dlatego trzeba je czekać.

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życia:

  • Musi czekać na wynik ResponseHeadersAsync pobierania kolekcji nagłówków.
  • Nie trzeba uzyskiwać dostępu przed ResponseAsync (ani strumienia odpowiedzi podczas przesyłania strumieniowego). 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 serwera i nagłówki nie zostały zwrócone dla wywołania gRPC.

Dostęp do przyczep gRPC

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

Przyczepy są dostępne przy użyciu elementu 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 przesyłania strumieniowego jednoargumentowego i klienta muszą czekać ResponseAsync przed wywołaniem metody 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 strumień 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");

Przyczepy są również dostępne z RpcExceptionwitryny . 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

Zalecane jest skonfigurowanie terminu ostatecznego wywołania gRPC, ponieważ zapewnia górny limit czasu uruchomienia wywołania. Przestaje działać nieprawidłowo działające usługi na zawsze i wyczerpać zasoby serwera. Terminy ostateczne to przydatne narzędzie do tworzenia niezawodnych aplikacji.

Skonfiguruj CallOptions.Deadline , aby ustawić termin ostatecznego wywołania 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