gRPC hizmetleri ve yöntemleri oluşturma
Uyarı
ASP.NET Core'un bu sürümü artık desteklenmiyor. Daha fazla bilgi için bkz . .NET ve .NET Core Destek İlkesi. Geçerli sürüm için bu makalenin .NET 9 sürümüne bakın.
Yayınlayan James Newton-King
Bu belgede C# dilinde gRPC hizmetlerinin ve yöntemlerinin nasıl oluşturulacağı açıklanmaktadır. Konu başlıkları şunlardır:
- Dosyalardaki
.proto
hizmetleri ve yöntemleri tanımlama. - gRPC C# araçları kullanılarak oluşturulan kod.
- gRPC hizmetlerini ve yöntemlerini uygulama.
Yeni gRPC hizmetleri oluşturma
C# ile gRPC hizmetleri, gRPC'nin API geliştirmeye yönelik sözleşme öncelikli yaklaşımını tanıttı. Hizmetler ve iletiler dosyalarda .proto
tanımlanır. C# araçları daha sonra dosyalardan .proto
kod oluşturur. Sunucu tarafı varlıklar için, her hizmet için bir soyut temel tür ve tüm iletiler için sınıflar oluşturulur.
Aşağıdaki .proto
dosya:
- Bir
Greeter
hizmeti tanımlar. - Hizmet
Greeter
birSayHello
çağrı tanımlar. SayHello
birHelloRequest
ileti gönderir ve birHelloReply
ileti alır
syntax = "proto3";
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
C# araçları C# GreeterBase
temel türünü oluşturur:
public abstract partial class GreeterBase
{
public virtual Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
throw new RpcException(new Status(StatusCode.Unimplemented, ""));
}
}
public class HelloRequest
{
public string Name { get; set; }
}
public class HelloReply
{
public string Message { get; set; }
}
Varsayılan olarak oluşturulan GreeterBase
hiçbir şey yapmaz. Sanal SayHello
yöntemi, onu çağıran tüm istemcilere hata UNIMPLEMENTED
döndürür. Hizmetin yararlı olması için bir uygulamanın somut bir uygulaması oluşturması GreeterBase
gerekir:
public class GreeterService : GreeterBase
{
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
return Task.FromResult(new HelloReply { Message = $"Hello {request.Name}" });
}
}
, ServerCallContext
sunucu tarafı çağrısının bağlamını verir.
Hizmet uygulaması uygulamaya kaydedilir. Hizmet ASP.NET Core gRPC tarafından barındırılıyorsa, yöntemiyle yönlendirme işlem hattına MapGrpcService
eklenmelidir.
app.MapGrpcService<GreeterService>();
Daha fazla bilgi için bkz . ASP.NET Core ile gRPC hizmetleri.
gRPC yöntemlerini uygulama
gRPC hizmetinin farklı yöntem türleri olabilir. İletilerin bir hizmet tarafından nasıl gönderileceği ve alındığı, tanımlanan yöntemin türüne bağlıdır. gRPC yöntem türleri şunlardır:
- Birli
- Sunucu akışı
- İstemci akışı
- çift yönlü akış
Akış çağrıları dosyasında anahtar stream
sözcüğüyle .proto
belirtilir. stream
aramanın istek iletisine, yanıt iletisine veya her ikisine de yerleştirilebilir.
syntax = "proto3";
service ExampleService {
// Unary
rpc UnaryCall (ExampleRequest) returns (ExampleResponse);
// Server streaming
rpc StreamingFromServer (ExampleRequest) returns (stream ExampleResponse);
// Client streaming
rpc StreamingFromClient (stream ExampleRequest) returns (ExampleResponse);
// Bi-directional streaming
rpc StreamingBothWays (stream ExampleRequest) returns (stream ExampleResponse);
}
Her çağrı türünün farklı bir yöntem imzası vardır. Soyut temel hizmet türünden oluşturulan yöntemleri somut bir uygulamada geçersiz kılma, doğru bağımsız değişkenlerin ve dönüş türünün kullanılmasını sağlar.
Tekli yöntem
Birli yöntem, istek iletisini parametre olarak alır ve yanıtı döndürür. Yanıt döndürülürken birli çağrı tamamlanır.
public override Task<ExampleResponse> UnaryCall(ExampleRequest request,
ServerCallContext context)
{
var response = new ExampleResponse();
return Task.FromResult(response);
}
Tekli çağrılar, web API denetleyicilerindeki eylemlere en benzer olan çağrılardır. gRPC yöntemlerinin eylemlerle arasındaki önemli farklardan biri, gRPC yöntemlerinin isteğin bölümlerini farklı yöntem bağımsız değişkenlerine bağlayamamasıdır. gRPC yöntemleri, gelen istek verileri için her zaman bir ileti bağımsız değişkenine sahiptir. İstek iletisine alanlar eklenerek gRPC hizmetine birden çok değer gönderilebilir:
message ExampleRequest {
int32 pageIndex = 1;
int32 pageSize = 2;
bool isDescending = 3;
}
Sunucu akış yöntemi
Sunucu akış yönteminde parametre olarak istek iletisi bulunur. Arayana birden çok ileti akışı gerçekleştirilebildiğinden, responseStream.WriteAsync
yanıt iletileri göndermek için kullanılır. Yöntem döndürdüğünde bir sunucu akış çağrısı tamamlanır.
public override async Task StreamingFromServer(ExampleRequest request,
IServerStreamWriter<ExampleResponse> responseStream, ServerCallContext context)
{
for (var i = 0; i < 5; i++)
{
await responseStream.WriteAsync(new ExampleResponse());
await Task.Delay(TimeSpan.FromSeconds(1));
}
}
Sunucu akış yöntemi başlatıldıktan sonra istemcinin ek ileti veya veri gönderme yolu yoktur. Bazı akış yöntemleri sonsuza kadar çalışacak şekilde tasarlanmıştır. Sürekli akış yöntemleri için, bir istemci artık gerekli olmadığında çağrıyı iptal edebilir. İptal gerçekleştiğinde istemci sunucuya bir sinyal gönderir ve ServerCallContext.CancellationToken oluşturulur. Belirteç CancellationToken
sunucuda zaman uyumsuz yöntemlerle kullanılmalıdır, böylece:
- Zaman uyumsuz çalışmalar akış çağrısıyla birlikte iptal edilir.
- yöntemi hızlı bir şekilde çıkar.
public override async Task StreamingFromServer(ExampleRequest request,
IServerStreamWriter<ExampleResponse> responseStream, ServerCallContext context)
{
while (!context.CancellationToken.IsCancellationRequested)
{
await responseStream.WriteAsync(new ExampleResponse());
await Task.Delay(TimeSpan.FromSeconds(1), context.CancellationToken);
}
}
İstemci akış yöntemi
İstemci akış yöntemi, yöntemi bir ileti almadan başlar. requestStream
parametresi istemciden gelen iletileri okumak için kullanılır. Yanıt iletisi döndürülürken istemci akış çağrısı tamamlanır:
public override async Task<ExampleResponse> StreamingFromClient(
IAsyncStreamReader<ExampleRequest> requestStream, ServerCallContext context)
{
await foreach (var message in requestStream.ReadAllAsync())
{
// ...
}
return new ExampleResponse();
}
Çift yönlü akış yöntemi
çift yönlü akış yöntemi, yöntem bir ileti almadan başlar. requestStream
parametresi istemciden gelen iletileri okumak için kullanılır. yöntemi ile responseStream.WriteAsync
ileti göndermeyi seçebilir. Yöntem döndürdüğünde çift yönlü akış çağrısı tamamlanır:
public override async Task StreamingBothWays(IAsyncStreamReader<ExampleRequest> requestStream,
IServerStreamWriter<ExampleResponse> responseStream, ServerCallContext context)
{
await foreach (var message in requestStream.ReadAllAsync())
{
await responseStream.WriteAsync(new ExampleResponse());
}
}
Yukarıdaki kod:
- Her istek için bir yanıt gönderir.
- İki yönlü akışın temel bir kullanımıdır.
İstekleri okuma ve yanıtları aynı anda gönderme gibi daha karmaşık senaryoları desteklemek mümkündür:
public override async Task StreamingBothWays(IAsyncStreamReader<ExampleRequest> requestStream,
IServerStreamWriter<ExampleResponse> responseStream, ServerCallContext context)
{
// Read requests in a background task.
var readTask = Task.Run(async () =>
{
await foreach (var message in requestStream.ReadAllAsync())
{
// Process request.
}
});
// Send responses until the client signals that it is complete.
while (!readTask.IsCompleted)
{
await responseStream.WriteAsync(new ExampleResponse());
await Task.Delay(TimeSpan.FromSeconds(1), context.CancellationToken);
}
}
İki yönlü akış yönteminde, istemci ve hizmet istedikleri zaman birbirlerine ileti gönderebilir. çift yönlü yöntemin en iyi şekilde uygulanması, gereksinimlere bağlı olarak değişir.
Access gRPC istek üst bilgileri
bir istemcinin gRPC hizmetine veri göndermesinin tek yolu istek iletisi değildir. Üst bilgi değerleri kullanılarak ServerCallContext.RequestHeaders
bir hizmette kullanılabilir.
public override Task<ExampleResponse> UnaryCall(ExampleRequest request,
ServerCallContext context)
{
var userAgent = context.RequestHeaders.GetValue("user-agent");
// ...
return Task.FromResult(new ExampleResponse());
}
gRPC akış yöntemleriyle çoklu iş parçacığı oluşturma
Birden çok iş parçacığı kullanan gRPC akış yöntemlerinin uygulanmasında dikkat edilmesi gereken önemli noktalar vardır.
Okuyucu ve yazıcı yazışma güvenliği
IAsyncStreamReader<TMessage>
ve IServerStreamWriter<TMessage>
her biri aynı anda yalnızca bir iş parçacığı tarafından kullanılabilir. Bir akış gRPC yöntemi için, birden çok iş parçacığı yeni iletileri requestStream.MoveNext()
aynı anda okuyamaz. Ayrıca birden çok iş parçacığı aynı anda yeni responseStream.WriteAsync(message)
ileti yazamaz.
Birden çok iş parçacığının gRPC yöntemiyle etkileşim kurmasını sağlamanın güvenli bir yolu, System.Threading.Channels ile üretici-tüketici desenini kullanmaktır.
public override async Task DownloadResults(DataRequest request,
IServerStreamWriter<DataResult> responseStream, ServerCallContext context)
{
var channel = Channel.CreateBounded<DataResult>(new BoundedChannelOptions(capacity: 5));
var consumerTask = Task.Run(async () =>
{
// Consume messages from channel and write to response stream.
await foreach (var message in channel.Reader.ReadAllAsync())
{
await responseStream.WriteAsync(message);
}
});
var dataChunks = request.Value.Chunk(size: 10);
// Write messages to channel from multiple threads.
await Task.WhenAll(dataChunks.Select(
async c =>
{
var message = new DataResult { BytesProcessed = c.Length };
await channel.Writer.WriteAsync(message);
}));
// Complete writing and wait for consumer to complete.
channel.Writer.Complete();
await consumerTask;
}
Yukarıdaki gRPC sunucusu akış yöntemi:
- İletileri üretmek ve tüketmek
DataResult
için sınırlanmış bir kanal oluşturur. - Kanaldan iletileri okumak ve bunları yanıt akışına yazmak için bir görev başlatır.
- Kanala birden çok iş parçacığından ileti yazar.
Not
Çift yönlü akış yöntemleri ve IServerStreamWriter<TMessage>
değerlerini bağımsız değişken olarak alırIAsyncStreamReader<TMessage>
. Bu türleri birbirinden ayrı iş parçacıklarında kullanmak güvenlidir.
Bir çağrı sona erdikten sonra gRPC yöntemiyle etkileşim kurma
gRPC yöntemi çıktıktan sonra sunucuda gRPC çağrısı sona erer. gRPC yöntemlerine geçirilen aşağıdaki bağımsız değişkenlerin, çağrı sona erdikten sonra kullanılması güvenli değildir:
ServerCallContext
IAsyncStreamReader<TMessage>
IServerStreamWriter<TMessage>
Bir gRPC yöntemi bu türleri kullanan arka plan görevlerini başlatırsa, gRPC yönteminden çıkmadan önce görevleri tamamlaması gerekir. gRPC yöntemi mevcut olduktan sonra bağlamı, akış okuyucuyu veya akış yazıcısını kullanmaya devam etmek hatalara ve öngörülemeyen davranışlara neden olur.
Aşağıdaki örnekte, çağrı tamamlandıktan sonra sunucu akış yöntemi yanıt akışına yazabilir:
public override async Task StreamingFromServer(ExampleRequest request,
IServerStreamWriter<ExampleResponse> responseStream, ServerCallContext context)
{
_ = Task.Run(async () =>
{
for (var i = 0; i < 5; i++)
{
await responseStream.WriteAsync(new ExampleResponse());
await Task.Delay(TimeSpan.FromSeconds(1));
}
});
await PerformLongRunningWorkAsync();
}
Önceki örnekte çözüm, yöntemden çıkmadan önce yazma görevini beklemektir:
public override async Task StreamingFromServer(ExampleRequest request,
IServerStreamWriter<ExampleResponse> responseStream, ServerCallContext context)
{
var writeTask = Task.Run(async () =>
{
for (var i = 0; i < 5; i++)
{
await responseStream.WriteAsync(new ExampleResponse());
await Task.Delay(TimeSpan.FromSeconds(1));
}
});
await PerformLongRunningWorkAsync();
await writeTask;
}
Ek kaynaklar
ASP.NET Core