Вызов служб gRPC с помощью клиента .NET
Примечание.
Это не последняя версия этой статьи. В текущем выпуске смотрите версию этой статьи .NET 9.
Предупреждение
Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. См. версию .NET 9 этой статьи в текущем выпуске.
Внимание
Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
В текущем выпуске смотрите версию .NET 9 этой статьи.
Клиентская библиотека .NET gRPC доступна в пакете NuGet Grpc.Net.Client. В этом документе объясняется, как выполнять следующие задачи:
- Настройка клиента gRPC для вызова служб gRPC.
- Совершайте вызовы gRPC для унарного метода, потоковой передачи от сервера, от клиента и двунаправленной передачи данных.
Настройка клиента gRPC
Клиенты gRPC — это конкретные типы клиентов, создаваемые в файлах .proto
. Конкретный клиент gRPC имеет методы, которые соответствуют gRPC службе в файле .proto
. Например, служба с именем Greeter
создает тип GreeterClient
с методами для вызова службы.
Клиент gRPC создается из канала. Для начала воспользуйтесь GrpcChannel.ForAddress
, чтобы создать канал, а затем используйте канал для создания клиента gRPC:
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greet.GreeterClient(channel);
Канал представляет собой долгосрочное подключение к службе gRPC. При создании канала он настраивается с опциями, связанными с вызовом службы. Например, HttpClient
, используемый для выполнения вызовов, максимальный размер сообщения для отправки и получения, а также ведение журнала можно указать в GrpcChannelOptions
и использовать с GrpcChannel.ForAddress
. Полный список параметров см. в разделе, посвященном параметрам конфигурации клиента.
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
Настройка TLS
Клиент gRPC должен использовать ту же систему безопасности на уровне подключения, что и вызванная служба. Протокол транспортного уровня безопасности (TLS) для клиента gRPC настраивается при создании канала gRPC. Если система безопасности на уровне подключения канала и службы не совпадает, при вызове службы от клиента gRPC поступает сообщение об ошибке.
Чтобы настроить канал gRPC для использования протокола TLS, убедитесь, что адрес сервера начинается с https
. Например, GrpcChannel.ForAddress("https://localhost:5001")
использует протокол HTTPS. Канал gRPC автоматически согласует подключение, защищенное с помощью TLS, и использует безопасное соединение для выполнения вызовов gRPC.
Совет
gRPC поддерживает проверку подлинности на основе сертификата клиента по протоколу TLS. Сведения о настройке сертификатов клиента с помощью канала gRPC см. в статье Проверка подлинности и авторизация в gRPC для ASP.NET Core.
Чтобы вызвать незащищенные службы gRPC, убедитесь, что адрес сервера начинается с http
. Например, GrpcChannel.ForAddress("http://localhost:5000")
использует протокол HTTP. В .NET Core 3.1 для вызова незащищенных служб gRPC с помощью клиента .NET требуется дополнительная настройка.
Производительность клиента
Производительность и использование канала и клиента:
- Создание канала может потребовать значительных ресурсов. Повторное использование канала для вызовов gRPC обеспечивает выигрыш в производительности.
- Канал управляет подключениями к серверу. Если подключение закрыто или потеряно, канал автоматически повторно подключается при следующем вызове gRPC.
- Клиенты gRPC создаются с помощью каналов. Клиенты gRPC являются облегченными объектами и не нуждаются в кэшировании или повторном использовании.
- Из одного канала можно создать несколько клиентов gRPC, включая различные типы клиентов.
- Канал и клиенты, созданные из канала, могут безопасно использоваться несколькими потоками.
- Клиенты, созданные из канала, могут выполнять несколько одновременных вызовов.
GrpcChannel.ForAddress
— не единственный вариант создания клиента gRPC. При вызове службы gRPC из приложения ASP.NET Core, рассмотрите возможность интеграции фабрики клиента gRPC. Интеграция gRPC с HttpClientFactory
предлагает централизованную альтернативу созданию клиентов gRPC.
При вызове методов gRPC предпочитайте использовать асинхронное программирование с асинхронным и ожидаемым. Выполнение вызовов gRPC с блокировкой, например использование Task.Result
или Task.Wait()
, запрещает другим задачам использовать поток. Это может привести к истощению пула потоков и низкой производительности. Это может привести к зависанию приложения с взаимной блокировкой. Дополнительные сведения см. в асинхронные вызовы в клиентских приложениях.
Выполните вызовы gRPC
Вызов gRPC инициируется путем вызова метода в клиенте. Клиент gRPC будет выполнять сериализацию сообщений и направлять вызов gRPC к правильной службе.
gRPC имеет различные типы методов. Способ использования клиента для выполнения вызова gRPC зависит от типа вызываемого метода. Типы методов gRPC:
- Унарный
- Потоковая передача сервера
- Клиентский стриминг
- Двунаправленная потоковая передача
Унарный вызов
Унарный вызов начинается с клиента, отправляющего сообщение с запросом. После завершения работы сервиса возвращается ответное сообщение.
var client = new Greet.GreeterClient(channel);
var response = await client.SayHelloAsync(new HelloRequest { Name = "World" });
Console.WriteLine("Greeting: " + response.Message);
// Greeting: Hello World
Каждый метод унарной службы в .proto
файле приведет к двум методам .NET для конкретного типа клиента gRPC для вызова метода: асинхронного метода и метода блокировки. Например, в GreeterClient
существует два способа вызова SayHello
.
-
GreeterClient.SayHelloAsync
— асинхронно вызывает службуGreeter.SayHello
. Может быть ожидаем. -
GreeterClient.SayHello
— вызывает службуGreeter.SayHello
и блокирует до её завершения. Не используйте его в асинхронном коде. Может вызвать проблемы с производительностью и надежностью.
Для получения дополнительной информации см. Асинхронные вызовы в клиентских приложениях.
Вызов потоковой передачи сервера
Вызов потоковой передачи сервера начинается с того, что клиент отправляет сообщение с запросом.
ResponseStream.MoveNext()
считывает сообщения, переданные в службу путем потоковой передачи. Вызов потоковой передачи сервера завершается, когда ResponseStream.MoveNext()
возвращает 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
}
Если используется C# 8 или более поздней версии, для чтения сообщений можно использовать синтаксис await foreach
. Метод расширения IAsyncStreamReader<T>.ReadAllAsync()
считывает все сообщения из потока ответов:
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
}
Тип, возвращаемый при запуске серверного потокового вызова, реализует IDisposable
. Всегда удаляйте вызов потоковой передачи, чтобы убедиться, что он остановлен и все ресурсы очищаются.
Клиентский потоковый вызов
Вызов потокового передачи клиента начинается без отправки сообщения клиентом. Клиент может выбрать отправку сообщений с помощью RequestStream.WriteAsync
. Когда клиент завершит отправку сообщений, следует вызвать RequestStream.CompleteAsync()
, чтобы уведомить службу. Вызов завершается, когда служба возвращает ответное сообщение.
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
Тип, возвращаемый при запуске потокового вызова клиента, реализует IDisposable
. Всегда удаляйте вызов потоковой передачи, чтобы убедиться, что он остановлен и все ресурсы очищаются.
Запрос двунаправленного стриминга
Вызов двунаправленной потоковой передачи начинается без отправки клиентом сообщения. Клиент может выбрать отправку сообщений с помощью RequestStream.WriteAsync
. Сообщения, поступающие в службу, доступны с ResponseStream.MoveNext()
или ResponseStream.ReadAllAsync()
. Вызов двунаправленной потоковой передачи завершается, когда сообщений в ResponseStream
больше не остаётся.
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;
Чтобы обеспечить наилучшую производительность и избежать ненужных ошибок в клиенте и службе, старайтесь правильно выполнять двунаправленные потоковые вызовы. Двунаправленный вызов завершается корректно, когда сервер завершил чтение потока запроса, а клиент завершил чтение потока ответа. Пример вызова приведён выше и является примером двунаправленного вызова, который завершается успешно. При вызове клиент:
- Запускает новый двунаправленный потоковый вызов путем вызова
EchoClient.Echo
. - Создает фоновую задачу для чтения сообщений из службы с помощью
ResponseStream.ReadAllAsync()
. - Отправляет сообщения на сервер с помощью
RequestStream.WriteAsync
. - Сообщает серверу, что он закончил отправку сообщений с помощью
RequestStream.CompleteAsync()
. - Ожидает, пока фоновая задача не прочитает все входящие сообщения.
Во время вызова двунаправленной потоковой передачи клиент и служба могут обмениваться сообщениями в любое время. Наиболее подходящая логика клиента для взаимодействия с двунаправленным вызовом зависит от логики сервиса.
Тип, возвращаемый во время начала двунаправленного вызова потоковой передачи, реализует IDisposable
. Всегда удаляйте потоковый вызов, чтобы остановить его и удалить все ресурсы.
Доступ к заголовкам gRPC
Вызовы gRPC возвращают заголовки ответа. Заголовки ответа HTTP передают метаданные (имя и значение) вызова без связи с возвращаемым сообщением.
Заголовки доступны с использованием ResponseHeadersAsync
, который возвращает коллекцию метаданных. Заголовки обычно возвращаются с ответным сообщением. Поэтому их необходимо ожидать.
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
:
- Нужно дождаться результата
ResponseHeadersAsync
, чтобы получить коллекцию заголовков. - Не нужно получать доступ перед
ResponseAsync
(или потока ответа при потоковой передаче). Если ответ был получен,ResponseHeadersAsync
немедленно возвращает заголовки. - Выдается исключение, если возникла ошибка подключения или сервера и для вызова gRPC заголовки не получены.
Доступ к трейлерам gRPC
Вызовы gRPC могут возвращать трейлеры ответа. Трейлеры содержат метаданные (имя и значение) вызова. Трейлеры содержат аналогичные функции для заголовков HTTP, но они принимаются в конце вызова.
Трейлеры доступны при использовании GetTrailers()
(возвращает коллекцию метаданных). Трейлеры возвращаются после завершения ответа. Поэтому необходимо дождаться всех ответных сообщений, прежде чем обращаться к трейлерам.
Унарные и клиентские потоковые вызовы должны дождаться ResponseAsync
перед вызовом 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");
Вызовы сервера и двунаправленной потоковой передачи должны дождаться завершения ответного потока перед вызовом 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");
Трейлеры также доступны на RpcException
. Служба может вернуть трейлеры вместе со статусом gRPC, отличным от "ОК". В этой ситуации трейлеры извлекаются из исключения, вызываемого клиентом 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");
}
Настройка крайнего срока
Рекомендуется настраивать ограничение по времени для вызова gRPC, так как это позволяет задать верхний предел длительности выполнения вызова. Это позволяет предотвратить бесконечное выполнение служб, работающих некорректно, и исчерпание ресурсов сервера. Крайние сроки — это полезное средство для повышения надежности приложений.
Чтобы задать крайний срок для вызова gRPC, настройте CallOptions.Deadline
:
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.");
}
Для получения дополнительной информации см. статью Надежные службы gRPC со сроками выполнения и отменой.
Дополнительные ресурсы
ASP.NET Core