Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
gRPC, Google tarafından geliştirilen ve özellikle mikroservis mimarilerinde kullanılan, yüksek performanslı bir iletişim protokolüdür. gRPC’nin temel amacı, farklı dillerde geliştirilmiş servisler arasında hızlı ve güvenilir veri iletişimi sağlamaktır. Bu makalede, gRPC kütüphanesinin veri iletim türlerini detaylı bir şekilde inceleyeceğiz. Bu türler, farklı senaryolar için optimize edilmiştir ve her birinin kendine özgü kullanım alanları bulunmaktadır.
gRPC Veri İletim Türleri
gRPC’de dört ana veri iletim türü vardır:
Bu veri iletim türlerinin her birini detaylı bir şekilde ele alalım.
Tanım: Unary RPC, istemcinin sunucuya tek bir istek göndermesi ve karşılığında tek bir yanıt alması modeline dayanır. Bu model, geleneksel RESTful API’lerdeki HTTP GET/POST işlemlerine benzer.
Örnek Senaryo: Bir kullanıcı kimlik doğrulama servisi düşünelim. İstemci, kullanıcı adı ve şifreyi içeren bir istek gönderir ve sunucu, doğrulama sonucunu (başarılı veya başarısız) içeren tek bir yanıt döner.
Kod Örneği:
// Proto dosyasında tanımlama
service AuthService {
rpc Login (LoginRequest) returns (LoginResponse);
}
message LoginRequest {
string username = 1;
string password = 2;
}
message LoginResponse {
string token = 1;
bool success = 2;
string message = 3;
}
Tanım: Server Streaming RPC modelinde, istemci sunucuya bir istek gönderir ve sunucu, istemciye tek bir yanıt yerine bir veri akışı gönderir. İstemci bu akışı okur ve işlemeye devam eder.
Örnek Senaryo: Bir haber servisi düşünelim. İstemci, belirli bir kategorideki haber başlıklarını talep eder ve sunucu, istemciye bu kategoriye ait tüm haber başlıklarını sıralı bir şekilde gönderir.
Kod Örneği:
// Proto dosyasında tanımlama
service NewsService {
rpc GetNewsByCategory (NewsRequest) returns (stream NewsResponse);
}
message NewsRequest {
string category = 1;
}
message NewsResponse {
string headline = 1;
string article_url = 2;
}
public override async Task GetNewsByCategory(NewsRequest request, IServerStreamWriter<NewsResponse> responseStream, ServerCallContext context) {
var news = GetNewsByCategory(request.Category); // Haber başlıklarını al
foreach (var article in news) {
await responseStream.WriteAsync(new NewsResponse {
Headline = article.Headline,
ArticleUrl = article.Url
});
}
}
Tanım: Client Streaming RPC modelinde, istemci sunucuya bir veri akışı gönderir ve sunucu bu akış tamamlandığında tek bir yanıt döner. Bu model, sunucunun istemciden toplu veri almasını sağlar.
Örnek Senaryo: Bir dosya yükleme servisi düşünelim. İstemci, büyük bir dosyayı küçük parçalara bölerek sunucuya gönderir. Sunucu, dosya tamamen yüklendiğinde bir yanıt döner.
Kod Örneği:
// Proto dosyasında tanımlama
service UploadService {
rpc UploadFile (stream FileChunk) returns (UploadResponse);
}
message FileChunk {
bytes data = 1;
}
message UploadResponse {
bool success = 1;
string message = 2;
}
public async Task UploadFile(string filePath, UploadService.UploadServiceClient client) {
using var call = client.UploadFile();
byte[] buffer = new byte[4096];
int bytesRead;
using (var fileStream = new FileStream(filePath, FileMode.Open)) {
while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) > 0) {
await call.RequestStream.WriteAsync(new FileChunk { Data = ByteString.CopyFrom(buffer, 0, bytesRead) });
}
}
await call.RequestStream.CompleteAsync();
var response = await call.ResponseAsync;
Console.WriteLine($"Upload {(response.Success ? "Succeeded" : "Failed")}: {response.Message}");
}
Tanım: Bidirectional Streaming RPC, istemci ve sunucunun birbirine veri akışı gönderebildiği en esnek veri iletim türüdür. İstemci ve sunucu bağımsız olarak veri akışı başlatabilir ve her iki taraf da verileri paralel olarak işleyebilir.
Örnek Senaryo: Bir chat uygulaması düşünelim. Hem istemci hem de sunucu, anlık olarak mesaj gönderip alabilir.
Kod Örneği:
// Proto dosyasında tanımlama
service ChatService {
rpc Chat (stream ChatMessage) returns (stream ChatMessage);
}
message ChatMessage {
string user = 1;
string message = 2;
int64 timestamp = 3;
}
public async Task Chat(ChatService.ChatServiceClient client) {
using var call = client.Chat();
var responseTask = Task.Run(async () => {
await foreach (var response in call.ResponseStream.ReadAllAsync()) {
Console.WriteLine($"{response.User} at {response.Timestamp}: {response.Message}");
}
});
while (true) {
var message = Console.ReadLine();
if (message == "exit") break;
await call.RequestStream.WriteAsync(new ChatMessage {
User = "client",
Message = message,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
});
}
await call.RequestStream.CompleteAsync();
await responseTask;
}
Projemizde bir istemci (client) ve bir sunucu (server) bulunacak. İstemci, bir dosya yüklemek veya indirmek istediğinde sunucuya bir istek gönderir. Sunucu ise bu isteği işler ve dosya transferini gerçekleştirir.
İlk Adım: gRPC Protokolü ve Protobuf Tanımları
gRPC projelerinde iletişimin sağlanabilmesi için öncelikle bir protokol (Protobuf) tanımlaması yapmamız gerekmektedir. Bu tanımlama, istemci ile sunucu arasındaki iletişim sözleşmesini belirler. Dosya yükleme ve indirme operasyonları için aşağıdaki gibi bir Protobuf dosyası (file_transfer.proto
) oluşturulabilir:
syntax = "proto3";
option csharp_namespace = "FileTransfer";
message FileInfo {
string file_name = 1;
int64 file_size = 2;
string file_extension = 3;
}
message FileChunk {
int64 chunk_size = 1;
bytes content = 2;
int32 chunk_number = 3;
}
service FileService {
rpc UploadFile(stream FileChunk) returns (google.protobuf.Empty);
rpc DownloadFile(FileInfo) returns (stream FileChunk);
}
FileInfo Mesajı: Bu mesaj, dosyanın genel bilgilerini (isim, boyut ve uzantı) içerir.
FileChunk Mesajı: Bu mesaj, dosyanın parça parça transfer edilmesi için kullanılır. Her bir parça, dosyanın belirli bir bölümünü içerir.
FileService Servisi: Bu servis, iki temel işlemi (Upload ve Download) tanımlar. UploadFile
fonksiyonu istemciden sunucuya dosya yüklemek için kullanılırken, DownloadFile
fonksiyonu sunucudan istemciye dosya indirmek için kullanılır.
Sunucu tarafında, yukarıda tanımlanan servisi implement etmemiz gerekecek. İşte sunucu tarafında FileService
servisini implement eden bir örnek:
using System.IO;
using System.Threading.Tasks;
using Grpc.Core;
using Google.Protobuf.WellKnownTypes;
public class FileServiceImpl : FileService.FileServiceBase
{
private const string UploadDirectory = "UploadedFiles";
public override async Task<Empty> UploadFile(IAsyncStreamReader<FileChunk> requestStream, ServerCallContext context)
{
Directory.CreateDirectory(UploadDirectory);
FileInfo fileInfo = null;
FileStream fileStream = null;
await foreach (var chunk in requestStream.ReadAllAsync())
{
if (fileInfo == null)
{
fileInfo = new FileInfo
{
FileName = chunk.FileName,
FileSize = chunk.FileSize,
FileExtension = chunk.FileExtension
};
string filePath = Path.Combine(UploadDirectory, fileInfo.FileName + fileInfo.FileExtension);
fileStream = new FileStream(filePath, FileMode.Create);
}
await fileStream.WriteAsync(chunk.Content.ToByteArray(), 0, chunk.Content.Length);
}
fileStream?.Close();
return new Empty();
}
public override async Task DownloadFile(FileInfo request, IServerStreamWriter<FileChunk> responseStream, ServerCallContext context)
{
string filePath = Path.Combine(UploadDirectory, request.FileName + request.FileExtension);
if (!File.Exists(filePath))
{
throw new RpcException(new Status(StatusCode.NotFound, "File not found"));
}
using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
var buffer = new byte[2048];
int bytesRead;
int chunkNumber = 0;
while ((bytesRead = await fileStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
var chunk = new FileChunk
{
Content = Google.Protobuf.ByteString.CopyFrom(buffer, 0, bytesRead),
ChunkSize = bytesRead,
ChunkNumber = chunkNumber++
};
await responseStream.WriteAsync(chunk);
}
}
}
Bu örnekte:
UploadFile
metodu, istemciden gelen dosya parçalarını alır ve sunucuya kaydeder.DownloadFile
metodu, sunucudan istemciye dosya parçalarını gönderir.Şimdi, istemci tarafında dosya yükleme ve indirme işlemlerini gerçekleştirelim. İşte istemci tarafında bu işlemleri yapan bir örnek:
using System;
using System.IO;
using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Net.Client;
public class FileTransferClient
{
private readonly FileService.FileServiceClient _client;
public FileTransferClient(FileService.FileServiceClient client)
{
_client = client;
}
public async Task UploadFileAsync(string filePath)
{
using var call = _client.UploadFile();
var fileInfo = new FileInfo(filePath);
byte[] buffer = new byte[2048];
int bytesRead;
using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
while ((bytesRead = await fileStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
var chunk = new FileChunk
{
Content = Google.Protobuf.ByteString.CopyFrom(buffer, 0, bytesRead),
ChunkSize = bytesRead
};
await call.RequestStream.WriteAsync(chunk);
}
}
await call.RequestStream.CompleteAsync();
await call.ResponseAsync;
}
public async Task DownloadFileAsync(string fileName, string destinationPath)
{
var request = new FileInfo { FileName = fileName, FileExtension = Path.GetExtension(fileName) };
using var call = _client.DownloadFile(request);
await foreach (var chunk in call.ResponseStream.ReadAllAsync())
{
using (var fileStream = new FileStream(destinationPath, FileMode.Append))
{
await fileStream.WriteAsync(chunk.Content.ToByteArray());
}
}
}
}
Bu istemci kodu:
UploadFileAsync
metodu ile istemcideki bir dosyayı sunucuya yükler.DownloadFileAsync
metodu ile sunucudaki bir dosyayı istemciye indirir.Bir sonraki yazıda görüşmek dileğiyle!”