利用 .NET 用戶端呼叫 gRPC 服務
注意
這不是這篇文章的最新版本。 如需目前版本,請參閱本文的 .NET 8 版本。
警告
不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支援原則。 如需目前版本,請參閱本文的 .NET 8 版本。
Grpc.Net.Client NuGet 套件中有提供 .NET gRPC 用戶端程式庫。 本文件說明如何:
- 設定 gRPC 用戶端以呼叫 gRPC 服務。
- 對一元、伺服器串流、用戶端串流和雙向串流方法進行 gRPC 呼叫。
設定 gRPC 用戶端
gRPC 用戶端是從 .proto
檔案產生的具體用戶端類型。 具體 gRPC 用戶端具有可轉譯為 .proto
檔案中 gRPC 服務的方法。 例如,稱為 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 用戶端必須使用與所呼叫服務相同的連線層級安全性。 gRPC 用戶端傳輸層安全性 (TLS) 會在建立 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 中,需要其他的設定,才能使用 .NET 用戶端呼叫不安全的 gRPC 服務。
用戶端效能
通道和用戶端效能和使用方式:
- 建立通道可能是昂貴的作業。 重複使用 gRPC 呼叫的通道可提供效能優勢。
- 通道會管理伺服器的連線。 如果連線已關閉或遺失,則通道會在下次進行 gRPC 呼叫時自動重新連線。
- gRPC 用戶端是使用通道來建立。 gRPC 用戶端是輕量型物件,不需要快取或重複使用。
- 您可以從通道建立多個 gRPC 用戶端,包括不同類型的用戶端。
- 從通道建立的通道和用戶端可以安全地供多個執行緒使用。
- 從通道建立的用戶端可以進行多個同時呼叫。
GrpcChannel.ForAddress
不是建立 gRPC 用戶端的唯一選項。 如果從 ASP.NET Core 應用程式呼叫 gRPC 服務,請考慮 gRPC 用戶端處理站整合。 gRPC 與 HttpClientFactory
整合提供建立 gRPC 用戶端的集中式替代方案。
注意
Xamarin 目前不支援使用 Grpc.Net.Client
透過 HTTP/2 呼叫 gRPC。 我們正努力在未來的 Xamarin 版本中改善 HTTP/2 支援。 Grpc.Core 和 gRPC-Web 是現今可行的替代方案。
進行 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
檔案中的每個一元服務方法都會在具體的 gRPC 用戶端類型上產生兩個 .NET 方法,以便呼叫方法:非同步方法和封鎖方法。 例如,在 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()
來存取,其會傳回中繼資料的集合。 回應完成之後會傳回結尾。 因此,您必須先等候所有回應訊息,才能存取結尾。
在呼叫 GetTrailers()
之前,一元和用戶端串流呼叫必須等候 ResponseAsync
:
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 呼叫期限,因為它會提供呼叫執行時間上限。 它可以阻止行為不當的服務永遠運作並耗盡伺服器資源。 期限是建置可靠應用程式的有用工具。
設定 CallOptions.Deadline
以設定 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.");
}
如需詳細資訊,請參閱具有期限和取消功能的可靠 gRPC 服務。