ASP.NET Core: Gọi các dịch vụ gRPC bằng máy khách .NET
Trong bài viết này
- Định cấu hình máy khách gRPC
- Thực thi lời gọi gRPC
- Truy cập tiêu đề (header) gRPC
- Truy cập đoạn giới thiệu gRPC
- Cấu hình thời hạn
- Tài nguyên bổ sung
Thư viện máy khách .NET gRPC có sẵn trong gói Grpc.Net.Client NuGet. Bài viết này giải thích cách:
- Định cấu hình máy khách gRPC để gọi các dịch vụ gRPC.
- Thực hiện lệnh gọi gRPC tới các phương thức truyền phát đơn nhất, phát trực tuyến trên máy chủ, truyền phát ứng dụng khách và truyền phát hai chiều.
Định cấu hình máy khách gRPC
Máy khách gRPC là các loại máy khách cụ thể được tạo từ các file .proto
. Máy khách gRPC cụ thể có các phương thức chuyển sang dịch vụ gRPC trong file .proto
. Ví dụ: một dịch vụ được gọi Greeter
sẽ tạo ra một kiểu GreeterClient
có các phương thức để gọi dịch vụ.
Máy khách gRPC được tạo từ một kênh. Bắt đầu bằng cách sử dụng GrpcChannel.ForAddress
để tạo kênh, sau đó sử dụng kênh đó để tạo ứng dụng khách gRPC:
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greet.GreeterClient(channel);
Kênh thể hiện kết nối lâu dài với dịch vụ gRPC. Khi một kênh được tạo, nó sẽ được cấu hình với các tùy chọn liên quan đến việc gọi một dịch vụ. Ví dụ: HttpClient
được sử dụng để thực hiện lời gọi, kích thước message gửi và nhận tối đa cũng như tính năng ghi nhật ký có thể được chỉ định trên GrpcChannelOptions
và sử dụng với GrpcChannel.ForAddress
. Để biết danh sách đầy đủ các tùy chọn, hãy xem tùy chọn cấu hình máy khách.
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var greeterClient = new Greet.GreeterClient(channel);
var counterClient = new Count.CounterClient(channel);
// Sử dụng máy khách để gọi các dịch vụ gRPC
Định cấu hình TLS
Máy khách gRPC phải sử dụng cùng mức bảo mật cấp kết nối như dịch vụ được gọi. Máy khách gRPC TLS (Transport Layer Security) được định cấu hình khi kênh gRPC được tạo. Máy khách gRPC gặp lỗi khi gọi một dịch vụ và mức độ bảo mật cấp kết nối của kênh và dịch vụ không khớp.
Để định cấu hình kênh gRPC để sử dụng TLS, hãy đảm bảo địa chỉ máy chủ bắt đầu bằng https
. Ví dụ: GrpcChannel.ForAddress("https://localhost:5001")
sử dụng giao thức HTTPS. Kênh gRPC tự động đàm phán kết nối được bảo mật bằng TLS và sử dụng kết nối an toàn để thực hiện lời gọi gRPC.
Mẹo
gRPC hỗ trợ xác thực chứng chỉ ứng dụng khách qua TLS. Để biết thông tin về cách định cấu hình chứng chỉ ứng dụng khách với kênh gRPC, hãy xem Xác thực và ủy quyền trong gRPC cho ASP.NET Core.
Để gọi các dịch vụ gRPC không bảo mật, hãy đảm bảo địa chỉ máy chủ bắt đầu bằng http
. Ví dụ: GrpcChannel.ForAddress("http://localhost:5000")
sử dụng giao thức HTTP. Trong .NET Core 3.1, cần có cấu hình bổ sung để gọi các dịch vụ gRPC không an toàn với máy khách .NET.
Hiệu suất của máy khách
Hiệu suất và cách sử dụng kênh và máy khách:
- Tạo một kênh có thể là một hoạt động tốn kém. Việc sử dụng lại kênh cho các lời gọi gRPC sẽ mang lại lợi ích về hiệu suất.
- Máy khách gRPC được tạo bằng các kênh. Máy khách gRPC là các đối tượng nhẹ và không cần phải lưu vào bộ nhớ đệm hoặc sử dụng lại.
- Nhiều máy khách gRPC có thể được tạo từ một kênh, bao gồm các loại máy khách khác nhau.
- Một kênh và các ứng dụng khách được tạo từ kênh đó có thể được nhiều luồng sử dụng một cách an toàn.
- Máy khách được tạo từ kênh có thể thực hiện nhiều lời gọi đồng thời.
GrpcChannel.ForAddress
không phải là lựa chọn duy nhất để tạo ứng dụng khách gRPC. Nếu gọi các dịch vụ gRPC từ ứng dụng ASP.NET Core, hãy xem xét việc tích hợp factory máy khách gRPC. Tích hợp gRPC HttpClientFactory
cung cấp giải pháp thay thế tập trung cho việc tạo ứng dụng khách gRPC.
Lưu ý
Gọi gRPC qua HTTP/2 Grpc.Net.Client
hiện không được hỗ trợ trên Xamarin. Microsoft đang nỗ lực cải thiện khả năng hỗ trợ HTTP/2 trong bản phát hành Xamarin trong tương lai. Grpc.Core và gRPC-Web là những lựa chọn thay thế khả thi hiện nay.
Thực thi lời gọi gRPC
Lời gọi gRPC được bắt đầu bằng cách gọi một phương thức trên máy khách. Máy khách gRPC sẽ xử lý việc tuần tự hóa message và giải quyết lệnh gọi gRPC đến đúng dịch vụ.
gRPC có nhiều loại phương thức khác nhau. Cách sử dụng máy khách để thực hiện lệnh gọi gRPC tùy thuộc vào loại phương thức được gọi. Các loại phương thức gRPC là:
- Đơn nhất
- Truyền phát máy chủ
- Truyền phát máy khách
- Truyền phát hai chiều
Lời gọi đơn nhất
Lời gọi đơn nhất bắt đầu bằng việc máy khách gửi message yêu cầu. Một message phản hồi được trả về khi dịch vụ kết thúc.
var client = new Greet.GreeterClient(channel);
var response = await client.SayHelloAsync(new HelloRequest { Name = "World" });
Console.WriteLine("Greeting: " + response.Message);
// Greeting: Hello World
Mỗi phương thức dịch vụ đơn nhất trong file .proto
sẽ dẫn đến hai phương thức .NET trên loại máy khách gRPC cụ thể để gọi phương thức: phương thức không đồng bộ và phương thức chặn. Ví dụ: trên GreeterClient
có hai cách gọi SayHello
:
GreeterClient.SayHelloAsync
- gọi dịch vụ bất đồng bộGreeter.SayHello
. Có thể chờ đợi.GreeterClient.SayHello
- gọi dịch vụGreeter.SayHello
và chặn cho đến khi hoàn tất. Không sử dụng trong code bất đồng bộ.
Lời gọi truyền phát máy chủ
Lời gọi truyền trực tuyến của máy chủ bắt đầu bằng việc máy khách gửi message yêu cầu. ResponseStream.MoveNext()
đọc message được truyền trực tuyến từ dịch vụ. Lời gọi truyền trực tuyến của máy chủ hoàn tất khi ResponseStream.MoveNext()
trả về 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
}
Khi sử dụng C# 8 trở lên, cú pháp await foreach
có thể được sử dụng để đọc message. Phương thức mở rộng IAsyncStreamReader<T>.ReadAllAsync()
đọc tất cả tin nhắn từ luồng phản hồi:
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
}
Lời gọi truyền phát máy khách
Lời gọi trực tuyến của máy khách bắt đầu mà không cần máy khách gửi message. Máy khách có thể chọn gửi message bằng RequestStream.WriteAsync
. Khi client gửi tin nhắn xong, RequestStream.CompleteAsync()
cần được gọi để thông báo cho dịch vụ. Lời gọi kết thúc khi dịch vụ trả về một message phản hồi.
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
Lời gọi truyền phát hai chiều
Lời gọi truyền phát hai chiều bắt đầu mà không cần máy khách gửi message. Máy khách có thể chọn gửi message bằng RequestStream.WriteAsync
. Các message được truyền trực tuyến từ dịch vụ có thể truy cập được bằng ResponseStream.MoveNext()
hoặc ResponseStream.ReadAllAsync()
. Lời gọi truyền phát hai chiều hoàn tất khi ResponseStream
không còn tin nhắn nào nữa.
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;
Để có hiệu suất tốt nhất và tránh các lỗi không cần thiết trong ứng dụng khách và dịch vụ, hãy cố gắng hoàn thành các lời gọi truyền phát hai chiều một cách suôn sẻ. Lời gọi hai chiều sẽ hoàn tất một cách suôn sẻ khi máy chủ đọc xong luồng yêu cầu và máy khách đã đọc xong luồng phản hồi. Lời gọi mẫu ở trên là một ví dụ về lời gọi hai chiều kết thúc một cách suôn sẻ. Trong lời gọi, máy khách:
- Bắt đầu lời gọi truyền phát hai chiều mới bằng cách gọi
EchoClient.Echo
. - Tạo tác vụ nền để đọc message từ dịch vụ bằng
ResponseStream.ReadAllAsync()
. - Gửi message đến máy chủ bằng
RequestStream.WriteAsync
. - Thông báo cho máy chủ đã gửi xong message bằng
RequestStream.CompleteAsync()
. - Đợi cho đến khi tác vụ nền đã đọc tất cả message đến.
Trong lời gọi truyền phát hai chiều, máy khách và dịch vụ có thể gửi message cho nhau bất kỳ lúc nào. Logic máy khách tốt nhất để tương tác với lời gọi hai chiều khác nhau tùy thuộc vào logic dịch vụ.
Truy cập tiêu đề (header) gRPC
Cuộc gọi gRPC trả về tiêu đề phản hồi. Tiêu đề phản hồi HTTP chuyển siêu dữ liệu tên/giá trị về lời gọi không liên quan đến message được trả về.
Các tiêu đề có thể truy cập được bằng cách sử dụng ResponseHeadersAsync
, trả về một tập hợp siêu dữ liệu. Tiêu đề thường được trả về cùng với thông báo phản hồi; do đó, bạn phải chờ đợi chúng.
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;
Cách sử dụng ResponseHeadersAsync
:
- Phải chờ kết quả
ResponseHeadersAsync
để có được bộ sưu tập tiêu đề. - Không cần phải truy cập trước
ResponseAsync
(hoặc luồng phản hồi khi phát trực tuyến). Nếu phản hồi đã được trả về thìResponseHeadersAsync
sẽ trả về tiêu đề ngay lập tức. - Sẽ đưa ra một ngoại lệ nếu có lỗi kết nối hoặc máy chủ và các tiêu đề không được trả về cho lệnh gọi gRPC.
Truy cập đoạn giới thiệu gRPC
Các lệnh gọi gRPC có thể trả về các đoạn giới thiệu phản hồi. Đoạn giới thiệu được sử dụng để cung cấp siêu dữ liệu tên/giá trị về lời gọi. Đoạn giới thiệu cung cấp chức năng tương tự như tiêu đề HTTP nhưng được nhận vào cuối lời gọi.
Đoạn giới thiệu có thể truy cập được bằng cách sử dụng GetTrailers()
, trả về một tập hợp siêu dữ liệu. Đoạn giới thiệu được trả về sau khi phản hồi hoàn tất. Vì vậy, bạn phải đợi tất cả các message phản hồi trước khi truy cập vào đoạn giới thiệu.
Các lời gọi phát trực tuyến đơn nhất và ứng dụng khách phải chờ ResponseAsync
trước khi gọi 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");
Máy chủ và các lời gọi phát trực tuyến hai chiều phải kết thúc việc chờ luồng phản hồi trước khi gọi 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");
Đoạn giới thiệu cũng có thể truy cập được từ RpcException
. Một dịch vụ có thể trả lại các đoạn giới thiệu cùng với trạng thái gRPC không ổn. Trong tình huống này, các đoạn giới thiệu được truy xuất từ ngoại lệ do ứng dụng khách gRPC đưa ra:
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");
}
Cấu hình thời hạn (deadline)
Bạn nên định cấu hình thời hạn lời gọi gRPC vì nó cung cấp giới hạn trên về thời lượng lời gọi có thể diễn ra. Nó ngăn các dịch vụ hoạt động sai chạy mãi và làm cạn kiệt tài nguyên máy chủ. Thời hạn là một công cụ hữu ích để xây dựng các ứng dụng đáng tin cậy.
Định cấu hình CallOptions.Deadline
để đặt thời hạn cho lời gọi 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.");
}
Để biết thêm thông tin, hãy xem Dịch vụ gRPC đáng tin cậy có thời hạn và hủy.
Tài nguyên bổ sung
- Tích hợp factory máy khách gRPC trong .NET
- Dịch vụ gRPC đáng tin cậy có thời hạn và hủy bỏ
- Dịch vụ gRPC với C#