利用 .NET 用戶端呼叫 gRPC 服務Call gRPC services with the .NET client

.NET gRPC 用戶端程式庫可在 gRPC .net. 用戶端 NuGet 套件中取得。A .NET gRPC client library is available in the Grpc.Net.Client NuGet package. 本檔說明如何:This document explains how to:

  • 設定 gRPC 用戶端以呼叫 gRPC services。Configure a gRPC client to call gRPC services.
  • 對一元、伺服器串流、用戶端串流和雙向串流方法進行 gRPC 呼叫。Make gRPC calls to unary, server streaming, client streaming, and bi-directional streaming methods.

設定 gRPC 用戶端Configure gRPC client

gRPC 用戶端是 * proto 檔案產生的具體用戶端類型。gRPC clients are concrete client types that are generated from *.proto files. 具體 gRPC 用戶端的方法會轉譯為 * proto 檔案中的 gRPC 服務。The concrete gRPC client has methods that translate to the gRPC service in the *.proto file. 例如,名為的服務會 Greeter 產生 GreeterClient 具有方法的型別來呼叫服務。For example, a service called Greeter generates a GreeterClient type with methods to call the service.

GRPC 用戶端是從通道建立。A gRPC client is created from a channel. 首先,使用 GrpcChannel.ForAddress 建立通道,然後使用通道來建立 gRPC 用戶端:Start by using GrpcChannel.ForAddress to create a channel, and then use the channel to create a gRPC client:

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

通道代表 gRPC 服務的長時間連接。A channel represents a long-lived connection to a gRPC service. 建立通道時,會使用與呼叫服務相關的選項進行設定。When a channel is created, it is configured with options related to calling a service. 例如, HttpClient 用來進行呼叫、最大傳送和接收訊息大小,以及記錄可以在上指定 GrpcChannelOptions 和使用 GrpcChannel.ForAddressFor example, the HttpClient used to make calls, the maximum send and receive message size, and logging can be specified on GrpcChannelOptions and used with GrpcChannel.ForAddress. 如需完整的選項清單,請參閱 用戶端設定選項For a complete list of options, see client configuration options.

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

設定 TLSConfigure TLS

GRPC 用戶端必須使用與所呼叫服務相同的連接層級安全性。A gRPC client must use the same connection-level security as the called service. 建立 gRPC 通道時,會設定 (TLS) 的 gRPC 用戶端傳輸層安全性。gRPC client Transport Layer Security (TLS) is configured when the gRPC channel is created. GRPC 用戶端在呼叫服務且通道與服務的連接層級安全性不相符時,會擲回錯誤。A gRPC client throws an error when it calls a service and the connection-level security of the channel and service don't match.

若要將 gRPC 通道設定為使用 TLS,請確定伺服器位址的開頭為 httpsTo configure a gRPC channel to use TLS, ensure the server address starts with https. 例如,會 GrpcChannel.ForAddress("https://localhost:5001") 使用 HTTPS 通訊協定。For example, GrpcChannel.ForAddress("https://localhost:5001") uses HTTPS protocol. GRPC 通道會自動協調受 TLS 保護的連線,並使用安全連線來進行 gRPC 呼叫。The gRPC channel automatically negotiates a connection secured by TLS and uses a secure connection to make gRPC calls.

提示

gRPC 支援透過 TLS 的用戶端憑證驗證。gRPC supports client certificate authentication over TLS. 如需使用 gRPC 通道設定用戶端憑證的詳細資訊,請參閱 GRPC 中的驗證和授權 ASP.NET CoreFor information on configuring client certificates with a gRPC channel, see GRPC 中的驗證和授權 ASP.NET Core.

若要呼叫不安全的 gRPC 服務,請確定伺服器位址的開頭為 httpTo call unsecured gRPC services, ensure the server address starts with http. 例如,會 GrpcChannel.ForAddress("http://localhost:5000") 使用 HTTP 通訊協定。For example, GrpcChannel.ForAddress("http://localhost:5000") uses HTTP protocol. 在 .NET Core 3.1 或更新版本中,需要額外的設定,才能 使用 .net 用戶端呼叫不安全的 gRPC 服務In .NET Core 3.1 or later, additional configuration is required to call insecure gRPC services with the .NET client.

用戶端效能Client performance

通道和用戶端效能和使用方式:Channel and client performance and usage:

  • 建立通道可能是昂貴的作業。Creating a channel can be an expensive operation. 重複使用 gRPC 呼叫的通道可提供效能優勢。Reusing a channel for gRPC calls provides performance benefits.
  • gRPC 用戶端會使用通道建立。gRPC clients are created with channels. gRPC 用戶端是輕量物件,不需要快取或重複使用。gRPC clients are lightweight objects and don't need to be cached or reused.
  • 您可以從通道建立多個 gRPC 用戶端,包括不同類型的用戶端。Multiple gRPC clients can be created from a channel, including different types of clients.
  • 通道和從通道建立的用戶端可以安全地由多個執行緒使用。A channel and clients created from the channel can safely be used by multiple threads.
  • 從通道建立的用戶端可能會進行多個同時呼叫。Clients created from the channel can make multiple simultaneous calls.

GrpcChannel.ForAddress 不是建立 gRPC 用戶端的唯一選項。GrpcChannel.ForAddress isn't the only option for creating a gRPC client. 如果從 ASP.NET Core 應用程式呼叫 gRPC 服務,請考慮 gRPC 用戶端工廠整合If calling gRPC services from an ASP.NET Core app, consider gRPC client factory integration. gRPC 整合 HttpClientFactory 提供了建立 gRPC 用戶端的集中式替代方案。gRPC integration with HttpClientFactory offers a centralized alternative to creating gRPC clients.

注意

Grpc.Net.Client在 Xamarin 上,目前不支援透過 HTTP/2 呼叫 gRPC。Calling gRPC over HTTP/2 with Grpc.Net.Client is currently not supported on Xamarin. 我們正在努力改善未來 Xamarin 版本中的 HTTP/2 支援。We are working to improve HTTP/2 support in a future Xamarin release. Grpc CoreGrpc-Web 是一 種可行的替代方案,可立即運作。Grpc.Core and gRPC-Web are viable alternatives that work today.

進行 gRPC 呼叫Make gRPC calls

藉由呼叫用戶端上的方法來起始 gRPC 呼叫。A gRPC call is initiated by calling a method on the client. GRPC 用戶端會處理訊息序列化,並將 gRPC 呼叫定址至正確的服務。The gRPC client will handle message serialization and addressing the gRPC call to the correct service.

gRPC 具有不同類型的方法。gRPC has different types of methods. 用戶端如何用來進行 gRPC 呼叫,取決於呼叫的方法類型。How the client is used to make a gRPC call depends on the type of method called. GRPC 方法類型為:The gRPC method types are:

  • 一元Unary
  • 伺服器串流Server streaming
  • 用戶端串流Client streaming
  • 雙向串流Bi-directional streaming

一元呼叫Unary call

一元呼叫會從傳送要求訊息的用戶端開始。A unary call starts with the client sending a request message. 當服務完成時,會傳迴響應消息。A response message is returned when the service finishes.

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

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

在該 * proto 檔中,每個一元服務方法都會在實體 gRPC 用戶端類型上產生兩個 .net 方法,以呼叫方法:非同步方法和封鎖方法。Each unary service method in the *.proto file will result in two .NET methods on the concrete gRPC client type for calling the method: an asynchronous method and a blocking method. 例如, GreeterClient 有兩種方式可呼叫 SayHelloFor example, on GreeterClient there are two ways of calling SayHello:

  • GreeterClient.SayHelloAsync - Greeter.SayHello 以非同步方式呼叫服務。GreeterClient.SayHelloAsync - calls Greeter.SayHello service asynchronously. 可以等候。Can be awaited.
  • GreeterClient.SayHello -呼叫 Greeter.SayHello 服務並封鎖直到完成為止。GreeterClient.SayHello - calls Greeter.SayHello service and blocks until complete. 請勿在非同步程式碼中使用。Don't use in asynchronous code.

伺服器串流呼叫Server streaming call

伺服器串流呼叫會從傳送要求訊息的用戶端開始。A server streaming call starts with the client sending a request message. ResponseStream.MoveNext() 讀取從服務資料流程處理的訊息。ResponseStream.MoveNext() reads messages streamed from the service. 傳回時,伺服器串流呼叫已 ResponseStream.MoveNext() 完成 falseThe server streaming call is complete when ResponseStream.MoveNext() returns 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 可以使用此語法來讀取訊息。When using C# 8 or later, the await foreach syntax can be used to read messages. IAsyncStreamReader<T>.ReadAllAsync()擴充方法會讀取來自回應資料流程的所有訊息:The IAsyncStreamReader<T>.ReadAllAsync() extension method reads all messages from the response stream:

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
}

用戶端串流呼叫Client streaming call

用戶端串流呼叫會在沒有用戶端傳送訊息的 情況下 啟動。A client streaming call starts without the client sending a message. 用戶端可以選擇傳送訊息給 RequestStream.WriteAsyncThe client can choose to send messages with RequestStream.WriteAsync. 當用戶端完成傳送訊息時, RequestStream.CompleteAsync() 應呼叫以通知服務。When the client has finished sending messages, RequestStream.CompleteAsync() should be called to notify the service. 當服務傳迴響應消息時,就會完成呼叫。The call is finished when the service returns a response message.

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

雙向串流呼叫Bi-directional streaming call

雙向串流呼叫會在沒有用戶端傳送訊息的 情況下 啟動。A bi-directional streaming call starts without the client sending a message. 用戶端可以選擇傳送訊息給 RequestStream.WriteAsyncThe client can choose to send messages with RequestStream.WriteAsync. 從服務串流處理的訊息可使用 ResponseStream.MoveNext() 或存取 ResponseStream.ReadAllAsync()Messages streamed from the service are accessible with ResponseStream.MoveNext() or ResponseStream.ReadAllAsync(). 當沒有其他訊息時,雙向串流呼叫就會完成 ResponseStreamThe bi-directional streaming call is complete when the ResponseStream has no more messages.

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;

為了獲得最佳效能,並避免用戶端與服務發生不必要的錯誤,請嘗試正常完成雙向串流呼叫。For best performance, and to avoid unnecessary errors in the client and service, try to complete bi-directional streaming calls gracefully. 當伺服器完成讀取要求資料流程,且用戶端已完成讀取回應串流時,雙向呼叫就會正常完成。A bi-directional call completes gracefully when the server has finished reading the request stream and the client has finished reading the response stream. 上述的範例呼叫是以正常結束的雙向呼叫範例之一。The preceding sample call is one example of a bi-directional call that ends gracefully. 在此呼叫中,用戶端:In the call, the client:

  1. 藉由呼叫來啟動新的雙向串流呼叫 EchoClient.EchoStarts a new bi-directional streaming call by calling EchoClient.Echo.
  2. 使用建立從服務讀取訊息的背景工作 ResponseStream.ReadAllAsync()Creates a background task to read messages from the service using ResponseStream.ReadAllAsync().
  3. 使用將訊息傳送至伺服器 RequestStream.WriteAsyncSends messages to the server with RequestStream.WriteAsync.
  4. 通知伺服器它已完成傳送訊息 RequestStream.CompleteAsync()Notifies the server it has finished sending messages with RequestStream.CompleteAsync().
  5. 等候背景工作讀取所有傳入的訊息。Waits until the background task has read all incoming messages.

在雙向串流呼叫期間,用戶端和服務可以隨時傳送訊息給彼此。During a bi-directional streaming call, the client and service can send messages to each other at any time. 與雙向呼叫互動的最佳用戶端邏輯會根據服務邏輯而有所不同。The best client logic for interacting with a bi-directional call varies depending upon the service logic.

存取權 gRPC 尾端Access gRPC trailers

gRPC 呼叫可能會傳回 gRPC 尾端。gRPC calls may return gRPC trailers. gRPC 尾端用來提供關於呼叫的名稱/值中繼資料。gRPC trailers are used to provide name/value metadata about a call. 尾端提供類似于 HTTP 標頭的功能,但會在通話結束時收到。Trailers provide similar functionality to HTTP headers, but are received at the end of the call.

您可以使用來存取 gRPC GetTrailers() 結尾,後者會傳回中繼資料的集合。gRPC trailers are accessible using GetTrailers(), which returns a collection of metadata. 當回應完成之後,就會傳回尾端,因此,您必須先等候所有回應訊息,才能存取尾端。Trailers are returned after the response is complete, therefore, you must await all response messages before accessing the trailers.

一元和用戶端串流呼叫必須等待, ResponseAsync 才能呼叫 GetTrailers()Unary and client streaming calls must await ResponseAsync before calling 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()Server and bidirectional streaming calls must finish awaiting the response stream before calling 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");

您也可以從存取 gRPC 尾端 RpcExceptiongRPC trailers are also accessible from RpcException. 服務可能會傳回尾端的 gRPC 狀態,並將其設定為不確定。A service may return trailers together with a non-OK gRPC status. 在此情況下,會從 gRPC 用戶端擲回的例外狀況中取出尾端:In this situation the trailers are retrieved from the exception thrown by the gRPC client:

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

設定期限Configure deadline

建議您設定 gRPC 呼叫期限,因為它會提供呼叫可執行檔時間上限。Configuring a gRPC call deadline is recommended because it provides an upper limit on how long a call can run for. 它會停止不正常的服務,使其無法執行永久和耗盡的伺服器資源。It stops misbehaving services from running forever and exhausting server resources. 期限是建立可靠應用程式的有用工具。Deadlines are a useful tool for building reliable apps.

CallOptions.Deadline設定以設定 gRPC 呼叫的期限:Configure CallOptions.Deadline to set a deadline for a gRPC call:

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 服務For more information, see 具有期限和取消的可靠 gRPC 服務.

其他資源Additional resources