Tüm eğitimler
TEKNİK RAPOR GRPC PYTHON 2026

Sıfırdan gRPC Servisi
Python ile Uçtan Uca

Kendi .proto sözleşmeni yaz, protoc ile kod üret, sunucu ve istemciyi kur. Unary'den bidirectional streaming'e 4 RPC tipini komut-komut, satır-satır.

00Çalışma klasörü ve kurulum

İzole bir klasör ve sanal Python ortamı. Bitince silersin, sistem Python'un kirlenmez.

bash
mkdir grpc-demo
cd grpc-demo

Sanal ortam oluştur ve aktif et

bash
python3 -m venv .venv
source .venv/bin/activate

Windows'ta: .venv\Scripts\activate

gRPC paketlerini kur

bash
pip install grpcio grpcio-tools
grpcioruntime kütüphane — sunucu/istemci kanalı, RPC çağrısı, streaming
grpcio-toolsprotoc derleyicisi ve Python kod üreteci — bu paket sayesinde ayrı protoc kurmana gerek yok

01gRPC nedir, neden Protocol Buffers

Kısaca teorik zemin. Sonra tamamen pratik.

gRPC, Google'ın geliştirdiği, HTTP/2 üzerinde çalışan bir RPC (Remote Procedure Call) çerçevesidir. Servisler arası iletişimde REST'e göre avantajları var: binary format (daha küçük mesajlar), multiplexing (tek bağlantıda paralel çağrılar), native streaming desteği, her dilde çalışan kod üretimi.

Protocol Buffers (protobuf) ise gRPC'nin mesaj formatıdır. Bir .proto dosyasında servis ve mesaj yapısını tanımlarsın; protoc bu dosyadan seçtiğin dilde (Python, Go, Java…) client ve server kodunu üretir. JSON/XML'e kıyasla hem daha küçük (binary) hem daha hızlı (parse edilmesi için).

REST vs gRPC tek cümlede

REST'te sözleşme dokümandır (OpenAPI), gRPC'de sözleşme .proto dosyasıdır ve her iki taraf o dosyadan aynı kodu üretir — yanlış tip göndermek derleme zamanında yakalanır.

Bu tutorial'da şunu yapacaksın: tek bir chat.proto dosyasında 4 farklı RPC tipi tanımlayıp hem sunucu hem istemci kodunu çalışır hale getireceksin.

02.proto dosyası — servis sözleşmesi

Her şeyin başı burası. Sunucu ne yapacak, istemci ne gönderecek, ne alacak — hepsi burada tanımlı. Kod yazmaya başlamadan önce bu dosyayı bitirirsin.

chat.proto
syntax = "proto3";

package chat;

service Chat {

  // Unary: tek istek gönder, tek yanıt al
  rpc SayHello (HelloRequest) returns (HelloReply);

  // Server streaming: tek istek gönder, birden fazla yanıt al
  rpc ListGreetings (HelloRequest) returns (stream HelloReply);

  // Client streaming: birden fazla istek gönder, tek yanıt al
  rpc SendManyGreetings (stream HelloRequest) returns (HelloReply);

  // Bidirectional streaming: her iki yönde de akış
  rpc ChatStream (stream HelloRequest) returns (stream HelloReply);

}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}
syntax = "proto3"Hangi protobuf versiyonu kullanıldığı. Proto3 varsayılan değerler ve zorunlu alan kurallarını basitleştirdi.
package chatİsim alanı. Birden fazla proto dosyası varken çakışmaları önler. Üretilen Python modülünde de görünür.
service ChatRPC metotlarını gruplar. protoc bu bloğu okuyarak ChatServicer ve ChatStub sınıflarını üretecek.
rpc ... returns (...)Bir RPC metodunun imzası. stream anahtar kelimesi o tarafın birden fazla mesaj göndereceğini belirtir.
message HelloRequestWire üzerinden gidecek veri yapısı. = 1 alan numarasıdır — binary encoding'de kullanılır, değiştirilmemelidir.
Proto sözleşme neden önemli

Bu dosya değişmeden sunucu ve istemci farklı dillerde yazılabilir. Go sunucu, Python istemci — aynı chat.proto'dan üretilen kod, farklı dillerde birbiriyle konuşabilir. Sözleşme bozmak (alan numarası, tip değiştirmek) backward-incompatible'dır.

03Kod üretme (protoc)

Bir komut — iki Python dosyası. Bunları elle yazmazsın, proto'dan üretirsin.

bash
python -m grpc_tools.protoc \
  -I. \
  --python_out=. \
  --grpc_python_out=. \
  chat.proto
-m grpc_tools.protocgrpcio-tools içindeki protoc derleyicisini Python modülü olarak çalıştır — ayrıca sistem geneline protoc kurmana gerek yok
-I.Proto dosyalarının import yolu. "Mevcut dizinde ara" demek. Birden fazla -I ile birden fazla dizin verilebilir.
--python_out=.Mesaj sınıflarını mevcut dizine üret → chat_pb2.py
--grpc_python_out=.Servis stub/servicer kodunu mevcut dizine üret → chat_pb2_grpc.py

Üretilen dosyalar

Komut çalıştıktan sonra dizinde iki yeni dosya belirir:

DosyaNe İçeriyorSen Bunu Kullanacaksın
chat_pb2.py HelloRequest ve HelloReply sınıfları + binary serileştirme kodu chat_pb2.HelloRequest(name="...") ile
chat_pb2_grpc.py ChatServicer (sunucu tabanı) + ChatStub (istemci) + kayıt fonksiyonu class ChatServicer(chat_pb2_grpc.ChatServicer): ile
Üretilen dosyalara elle dokunma

Bu iki dosyayı elle düzenleme. Proto değişirse komutu tekrar çalıştır, üretilen dosyaları sil/yenile. Elle yapılan değişiklikler sonraki üretimde silinir. Genelde bu dosyalar versiyonlamaya dahil edilmez (.gitignore'a eklenir).

04Sunucu — server.py

Üretilen ChatServicer tabanını extend edip 4 metodu implement ediyorsun. Her metodun imzası farklı — normal fonksiyon, yield'layan fonksiyon veya iterator alan fonksiyon.

server.py
import grpc
from concurrent import futures
import chat_pb2
import chat_pb2_grpc


class ChatServicer(chat_pb2_grpc.ChatServicer):

    def SayHello(self, request, context):
        # Unary: tek request, tek response döndür
        return chat_pb2.HelloReply(message=f"Merhaba, {request.name}!")

    def ListGreetings(self, request, context):
        # Server streaming: yield ile birden fazla mesaj gönder
        diller = ["Türkçe: Merhaba", "İngilizce: Hello", "Japonca: Konnichiwa"]
        for dil in diller:
            yield chat_pb2.HelloReply(message=f"{dil}, {request.name}!")

    def SendManyGreetings(self, request_iterator, context):
        # Client streaming: request_iterator'dan tüm isimleri topla
        isimler = [req.name for req in request_iterator]
        return chat_pb2.HelloReply(
            message=f"Hepsine selam: {', '.join(isimler)}"
        )

    def ChatStream(self, request_iterator, context):
        # Bidi streaming: her isteğe karşılık bir yanıt yield'la
        for request in request_iterator:
            yield chat_pb2.HelloReply(message=f"Echo: {request.name}")


def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    chat_pb2_grpc.add_ChatServicer_to_server(ChatServicer(), server)
    server.add_insecure_port('[::]:50051')
    server.start()
    print("gRPC sunucu :50051 portunda başladı")
    server.wait_for_termination()


if __name__ == '__main__':
    serve()
grpc.server(...)Thread havuzunu parametre alır. gRPC her gelen RPC çağrısını bu havuzdan bir thread'e atar.
add_ChatServicer_to_serverÜretilen kayıt fonksiyonu. Servicer implementasyonunu sunucuya bağlar.
add_insecure_port('[::]:50051'TLS'siz, tüm arayüzlerde 50051 portu. Prod'da kullanma — bu tutorial'ın sonunda mTLS bağlantısı var.
server.start()Port dinlemeye başlar ama bloklamamaz. Ardından wait_for_termination ile ana thread bekler.
request_iteratorClient streaming ve bidi metodlarında request tek nesne değil, iterator'dır. for ile üzerinden geçersin.
Neden yield

Server streaming metodları normal return yerine yield kullanır çünkü gönderilecek mesaj sayısı önceden bilinmiyor. Python'un generator semantiği ile gRPC'nin akış mekanizması arasında doğal bir eşleşme var: her yield bir DATA frame'i anlamına gelir.

05İstemci — client.py

Bir kanal aç, stub oluştur, 4 farklı mod için ayrı fonksiyon yaz. Komut satırı argümanı hangi modu çalıştıracağını belirler.

client.py
import grpc
import sys
import chat_pb2
import chat_pb2_grpc


def run_unary(stub):
    # Tek istek, tek yanıt — en basit RPC tipi
    reply = stub.SayHello(chat_pb2.HelloRequest(name="Dünya"))
    print(f"[unary] {reply.message}")


def run_server_stream(stub):
    # Sunucu birden fazla yanıt akıtır, for ile tüketilir
    for reply in stub.ListGreetings(chat_pb2.HelloRequest(name="Ali")):
        print(f"[server-stream] {reply.message}")


def run_client_stream(stub):
    # Generator ile birden fazla istek gönderilir, tek yanıt alınır
    isimler = ["Ali", "Veli", "Ayşe"]
    istekler = (chat_pb2.HelloRequest(name=n) for n in isimler)
    reply = stub.SendManyGreetings(istekler)
    print(f"[client-stream] {reply.message}")


def run_bidi(stub):
    # Her iki yönde de akış: generator → iterator
    def istekler():
        for isim in ["Ali", "Veli", "Ayşe"]:
            yield chat_pb2.HelloRequest(name=isim)
    for reply in stub.ChatStream(istekler()):
        print(f"[bidi] {reply.message}")


def main():
    mod = sys.argv[1] if len(sys.argv) > 1 else "unary"
    with grpc.insecure_channel("localhost:50051") as channel:
        stub = chat_pb2_grpc.ChatStub(channel)
        if mod == "unary":
            run_unary(stub)
        elif mod == "server-stream":
            run_server_stream(stub)
        elif mod == "client-stream":
            run_client_stream(stub)
        elif mod == "bidi":
            run_bidi(stub)
        else:
            print(f"Bilinmeyen mod: {mod}")
            print("Kullanım: python client.py [unary|server-stream|client-stream|bidi]")


if __name__ == '__main__':
    main()
grpc.insecure_channel(...)TLS'siz kanal. Context manager (with) ile açılır, blok bitince kapatılır. host:port formatında.
ChatStub(channel)İstemci tarafı. RPC metodları stub üzerinden çağrılır. Sanki yerel bir fonksiyon çağırıyormuşsun gibi.
stub.SayHello(...)Unary çağrı: bloklar, yanıt gelene kadar bekler, tek bir reply nesnesi döner.
stub.ListGreetings(...)Server streaming: iterator döner, for ile tüketirsin. Her iterasyonda bir HelloReply gelir.
stub.SendManyGreetings(...)Client streaming: generator/iterator alır (istek akışı), tek reply döner. Generator tükenenende sunucu yanıt verir.
stub.ChatStream(...)Bidi streaming: generator gönderir, iterator ile alır. Tam çift yönlü — sunucu ve istemci aynı anda yazabilir.

06Dosyaların özeti

Şu an dizinde 5 dosya olmalı. Kim ne, nereye ait, hangisini commit etmeli misin?

DosyaNeDurum
chat.protoServis sözleşmesi — tek kaynak doğrusuCommit'le, versiyonla
chat_pb2.pyMesaj sınıfları — proto'dan üretildiGenelde .gitignore'a ekle
chat_pb2_grpc.pyStub + Servicer + kayıt fonksiyonu — proto'dan üretildiGenelde .gitignore'a ekle
server.pygRPC sunucu implementasyonuCommit'le
client.pygRPC istemci — 4 modCommit'le
Üretilen dosyaları commit etmeli misin

Tartışmalı. Küçük projelerde kolaylık için commit edilebilir. Büyük projelerde CI'da protoc çalıştırılır, üretilen dosyalar .gitignore'a alınır. İkinci yaklaşım daha temiz — proto değişince üretilen dosyalar eski kalma riski ortadan kalkar.

07Çalıştır ve test et

Birinci terminalde sunucuyu başlat ve açık bırak. İkinci terminalde 4 modu sırayla dene.

Terminal 1 — Sunucuyu başlat

terminal 1 — sunucu
python server.py
Beklenen çıktı

gRPC sunucu :50051 portunda başladı

Sunucu bu terminalde askıda kalacak. Durdurmak için Ctrl+C.

Test 1 — Unary

terminal 2 — istemci
python client.py unary
Beklenen çıktı

[unary] Merhaba, Dünya!

Test 2 — Server streaming

terminal 2 — istemci
python client.py server-stream
Beklenen çıktı

[server-stream] Türkçe: Merhaba, Ali!
[server-stream] İngilizce: Hello, Ali!
[server-stream] Japonca: Konnichiwa, Ali!

3 ayrı DATA frame geldi — biri hemen ardından diğeri. Sunucu liste bitene kadar akıtmaya devam etti.

Test 3 — Client streaming

terminal 2 — istemci
python client.py client-stream
Beklenen çıktı

[client-stream] Hepsine selam: Ali, Veli, Ayşe

İstemci 3 istek gönderdi, sunucu hepsini topladıktan sonra tek bir yanıt döndürdü. Sunucu istemci akışını kapatana kadar (generator bitti) bekleyebilir.

Test 4 — Bidirectional streaming

terminal 2 — istemci
python client.py bidi
Beklenen çıktı

[bidi] Echo: Ali
[bidi] Echo: Veli
[bidi] Echo: Ayşe

Her istek gönderilir gönderilmez sunucu yanıt verdi. Gerçek bir chat senaryosunda her iki taraf birbirinden bağımsız olarak mesaj gönderebilir.

08HTTP/2 üzerinde ne oluyor

Unary bir çağrı yapıldığında ağ üzerinde ne gidip geldi? gRPC'ye özgü adımlar mavi ile işaretli.

 1. İstemci TCP bağlantısı açar

 2. HTTP/2 handshake (PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n)
    Her iki taraf SETTINGS frame gönderir
    → window size, max frame size, max concurrent streams

 3. İstemci HEADERS frame gönderir:
    :method        = POST
    :path          = /chat.Chat/SayHello
    :scheme        = http
    content-type   = application/grpc
    grpc-timeout   = 5S  (isteğe bağlı)
    te             = trailers

 4. İstemci DATA frame gönderir:
    ┌─[5 byte prefix]─┬─[protobuf payload]─┐
    │ compressed flag │ message length (4B)│ HelloRequest bytes │
    └─────────────────┴────────────────────┘
    END_STREAM flag ile stream'i kapatır (unary'de)

 5. Sunucu isteği işler, ChatServicer.SayHello() çağrılır

 6. Sunucu HEADERS frame gönderir:
    :status = 200
    content-type = application/grpc

 7. Sunucu DATA frame gönderir:
    └─ Length-Prefixed HelloReply bytes

 8. Sunucu HEADERS frame gönderir (trailers):
    grpc-status  = 0  (OK)
    grpc-message = ""
    END_STREAM flag ile stream kapanır

 9. İstemci yanıtı deserialize eder → HelloReply nesnesi
Streaming'de fark ne

Server streaming'de adım 7 birden fazla kez tekrar eder — her yield için ayrı bir DATA frame. Trailers (adım 8) yalnızca sunucu akışı bitirince gelir. Client streaming'de ise adım 4 birden fazla kez tekrar eder ve adım 5 ancak istemci END_STREAM gönderince çalışır.

gRPC, HTTP/2'nin üstüne kurulu bir convention

gRPC kendi wire protokolünü icat etmedi. HTTP/2 multiplexing, flow control, header sıkıştırma (HPACK) — bunların hepsi HTTP/2'den geliyor. gRPC bunların üzerine: :path/paket.Servis/Metod biçimine sabitleyip, protobuf payload'ı 5 byte length-prefix ile sarmak gibi kurallar koyuyor.

09Sonraki adım: mTLS ile güvenliğe al

Bu tutorial boyunca insecure_channel ve add_insecure_port kullandık. Prod'da bu ikisi olmamalı.

gRPC'yi TLS ile güvenliğe almak için grpc.ssl_channel_credentials() ve grpc.ssl_server_credentials() kullanırsın. mTLS (karşılıklı TLS) ile hem sunucu istemciyi, hem istemci sunucuyu doğrular.

client.py — güvenli versiyon
# ca.crt, client.crt, client.key mTLS tutorial'ından geliyor
with open('../mtls/ca.crt', 'rb') as f: ca_cert = f.read()
with open('../mtls/client.crt', 'rb') as f: client_cert = f.read()
with open('../mtls/client.key', 'rb') as f: client_key = f.read()

creds = grpc.ssl_channel_credentials(
    root_certificates=ca_cert,
    private_key=client_key,
    certificate_chain=client_cert,
)
with grpc.secure_channel('localhost:50051', creds) as channel:
    stub = chat_pb2_grpc.ChatStub(channel)
    # ... aynı çağrılar

Sertifika nasıl üretilir, CA nasıl kurulur, handshake'te ne olur? Her adım açıklamalı olarak:

Tek cümlede gRPC

.proto dosyasında "kim ne gönderir, kim ne alır" yazan bir sözleşme yaz; protoc bunu Python koduna çevirir; gRPC bu kodu HTTP/2 üzerinden binary ile çalıştırır — unary'den bidirectional streaming'e dört farklı iletişim desenini aynı altyapıda, aynı sözleşmeyle kullanabilirsin.

→ mTLS Tutorial — Sertifika üret, güvenli kanal kur