---
title: 'İstemci Yazımı'
source: 'https://academia.sh/tr/kurslar/mcp/istemci-yazimi'
course: 'Model Bağlam Protokolü ve Araç Ekosistemi'
language: tr
updated: '2026-08-17T18:06:40+00:00'
license: 'CC BY-SA 4.0'
---

# İstemci Yazımı

Sunucu keşfinin ve oturum yönetiminin istemci tarafında yazılması ve keşfin bedeliyle sürüm sapmasının sessizliğinin sayılması: üç kurgu sunucuya yapılan keşif 3 istek, 3 yanıt, 99 belirteç ve 5 gidiş-dönüş tutar ve bu dört sayı, kayit sunucusu sürüm sapmasıyla tamamen düşerken de düşmezken de birebir aynıdır. Aynı on iki görev tek oturumda 27 istek ve 744 belirteçle koşarken keşfi her görevde yenilemek istek sayısını 60'a, belirteci 1833'e çıkarır ve bunun 1188'i keşfe gider. Sürümü öğrenmiş istemcide anlaşılan araç 3'ten 4'e, tamamlanan görev 6'dan 9'a çıkar. Bölge görevi içermeyen bir görev kümesinde ise sapmalı istemci ile güncel istemci 12 tamamlanan görev, 33 istek, 33 yanıt, 909 belirteç ve 41 gidiş-dönüşle birbirinin tıpatıp aynısını üretir: düşen araç hiçbir hata üretmez ve kaybı görünür kılan tek şey oturum açılışında bildirilen 5, anlaşılan 3 ve düşen 2 sayılarının yan yana basılmasıdır.

Önceki derste sunucu yazıldı: işleyici içeride kaldı, yetenek bildirimi dışarı çıktı ve bildirimin
her eksik düzeyi çağrılabilirlikten bir şey götürdü. O ölçümde karşı taraf hazır kabul edilmişti.
El sıkışmasının çoktan yapıldığı varsayıldı, anlaşılan kümenin nerede durduğu sorulmadı.

Bu ders istemciyi yazar. İstemcinin iki işi vardır. **Sunucu keşfi**, hangi sunucunun ne
bildirdiğini sormaktır ve bir bedeli vardır. **Oturum**, keşfin sonucunu bir yerde tutmaktır; o
yerde duran şey, sonraki bütün çağrıların dayandığı araç kümesidir. Dersin sorusu bu iki işin
birleştiği noktadadır: keşif ne tutar, oturum neyi tutar, ve tuttuğu şey eksik kaldığında bunu
kim söyler.

- **GE11.** Ortam, üç sunucu ve ders içindeki protokol benzeticisi **kurgudur**; gerçek bir sunucu,
  istemci kitaplığı ya da çalışma zamanı çağrılmaz. Tohum **20260218**, ikinci koşum **20260219**.
- **GE12.** İstemci bir **oturum** açar: keşif bir kez yapılır ve anlaşılan araç kümesi oturumun
  içinde durur. Oturum boyunca yeniden sorulmaz.
- **GE13.** Keşif, her sunucuya bir yetenek isteği ve karşılığında bir yanıttır. Zarfın sabit
  bedeli 8 belirteçtir; uzak aktarımda gidiş-dönüş iki katıdır.
- **GE14.** İzin listesi dört araç taşır; `kayit_yaz` listede yoktur.
- **GE15.** Sürüm sapması: `kayit` sunucusu sürüm 3 bildirir, istemci sürüm 2 bilir. Uyuşmazlıkta
  **sunucunun tamamı** düşer. Karşılaştırma için sürümü öğrenmiş bir istemci de koşturulur.
- **GE16.** Görev kümesi 12 kurgu görevdir. İki tür küme kullanılır: dört türlü küme (bedel,
  tüketim, bölge, kayıt) ve iki türlü küme (bedel, tüketim).
- **GE17.** Bir çağrı hata döndürürse zincir orada kesilir; tamamlanan görev, zincirin bıraktığı
  sonucun doğru yanıta eşit olmasıdır.
- **GE18.** Taban çizgisi protokolsüz kurulumdur: keşfin hiç yapılmadığı, aracın ders içinde
  tanımlandığı hâl. Orada keşif bedeli sıfırdır.
- **GE19.** Küme küçüktür: üç sunucu, beş bildirilen araç. Oran değil **sayım** basılır; bu kümenin
  ölçebildiği en küçük adım 1/5, yani 0,2000'dir.

## Keşif ve Oturumun Tuttuğu Şey

Aşağıdaki kod kurgu ortamı, üç kurgu sunucuyu, zarf sayımını ve yetenek uzlaşmasını kurar, sonra
istemcinin oturum açışını ekler.

```python
TOHUM, M32 = 20260218, 0xFFFFFFFF


def uretec(t):
    x = ((t ^ (t >> 16)) * 2246822507) & M32
    x = ((x ^ (x >> 13)) * 3266489909) & M32
    s = [(x ^ (x >> 16)) & M32]

    def sonraki():
        s[0] = (s[0] * 1664525 + 1013904223) & M32
        return s[0] / 4294967296
    return sonraki


def belirtecle(m):
    b = []
    for s in str(m).lower().replace(",", " , ").split():
        while len(s) > 5:
            b.append(s[:5])
            s = s[5:]
        b.append(s)
    return b


# ---- KURGU ortam ve araclar (K05 ile ayni kurgu, burada sunucuya tasiniyor)
BOLGE = ["dogu", "bati", "kuzey", "guney", "merkez"]
TIP = ["mesken", "isyeri"]
ABONE = {}
for i in range(60):
    r = uretec(TOHUM + 31 * i)
    onceki = 10000 + int(r() * 80000)
    ABONE[f"A{i + 1:03d}"] = {"bolge": BOLGE[int(r() * 5)], "tip": TIP[int(r() * 2)],
                              "onceki": onceki, "simdiki": onceki + int(r() * 70)}
BASAMAK, USTU, ISYERI_KAT = ((10, 4), (25, 7), (40, 11)), 16, 2


def _bedel(m3, tip):
    kalan, alt, top = m3, 0, 0
    for ust, birim in BASAMAK:
        dilim = min(kalan, ust - alt)
        if dilim <= 0:
            break
        top += dilim * birim
        kalan -= dilim
        alt = ust
    return (top + kalan * USTU) * (ISYERI_KAT if tip == "isyeri" else 1)


def t_sayac(abone=None, **_):
    a = ABONE.get(abone)
    return {"hata": "abone yok"} if a is None else {
        "onceki": a["onceki"], "simdiki": a["simdiki"], "tip": a["tip"]}


def t_tuketim(onceki=None, simdiki=None, **_):
    if onceki is None or simdiki is None:
        return {"hata": "eksik alan"}
    f = simdiki - onceki
    return {"m3": f + 100000 if f < 0 else f}


def t_tarife(m3=None, tip="mesken", **_):
    return {"hata": "eksik alan"} if m3 is None else {"tutar": _bedel(m3, tip)}


def t_bolge(bolge=None, **_):
    if bolge not in BOLGE:
        return {"hata": "bolge yok"}
    n = sum(1 for a in ABONE.values() if a["bolge"] == bolge)
    return {"bolge": bolge, "abone": n}


def t_kayit(abone=None, sutun=None, deger=None, **_):
    if not (abone and sutun and deger is not None):
        return {"hata": "eksik alan"}
    return {"yazildi": True}


# ---- KURGU sunucular: her biri yetenek bildirir
SUNUCU = {
    "olcum": {"surum": 2, "aktarim": "yerel",
              "arac": {"sayac_oku": {"islev": t_sayac, "alan": ("abone",), "yan_etki": False},
                       "tuketim": {"islev": t_tuketim, "alan": ("onceki", "simdiki"),
                                   "yan_etki": False}},
              "kaynak": ("abone_listesi",), "istem": ()},
    "tarife": {"surum": 1, "aktarim": "uzak",
               "arac": {"tarife": {"islev": t_tarife, "alan": ("m3", "tip"), "yan_etki": False}},
               "kaynak": ("basamak_tablosu",), "istem": ("bedel_aciklamasi",)},
    "kayit": {"surum": 3, "aktarim": "uzak",
              "arac": {"bolge_ozet": {"islev": t_bolge, "alan": ("bolge",), "yan_etki": False},
                       "kayit_yaz": {"islev": t_kayit, "alan": ("abone", "sutun", "deger"),
                                     "yan_etki": True}},
              "kaynak": (), "istem": ()},
}
ISTEMCI_SURUM = {"olcum": 2, "tarife": 1, "kayit": 2}      # kayit sunucusu ileride
IZIN = ("sayac_oku", "tuketim", "tarife", "bolge_ozet")     # kayit_yaz izin listesinde yok

SAYIM = {"istek": 0, "yanit": 0, "bildirim": 0, "belirtec": 0, "gidis_donus": 0}


def mesaj(tur, govde, aktarim="yerel"):
    """KURGU zarf. Gercek bir protokol uygulanmaz; sayilan sey zarfin bedelidir."""
    SAYIM[tur] += 1
    b = len(belirtecle(govde)) + 8                       # zarf sabiti
    SAYIM["belirtec"] += b
    if tur == "istek":
        SAYIM["gidis_donus"] += 2 if aktarim == "uzak" else 1
    return b


def elsikismasi(istemci_surum=None, izin=None):
    """Yetenek uzlasmasi: surum uyusmazligi ve izin listesi arac kumesini daraltir."""
    istemci_surum = ISTEMCI_SURUM if istemci_surum is None else istemci_surum
    izin = IZIN if izin is None else izin
    anlasilan, dusen = {}, []
    for ad, s in SUNUCU.items():
        mesaj("istek", f"yetenek sor {ad}", s["aktarim"])
        mesaj("yanit", f"yetenek bildir {ad} surum {s['surum']} "
                       + " ".join(s["arac"]) + " " + " ".join(s["kaynak"]))
        if istemci_surum.get(ad, 0) < s["surum"]:
            dusen.append((ad, "surum", s["surum"], istemci_surum.get(ad, 0)))
            continue
        for t, tan in s["arac"].items():
            if t not in izin:
                dusen.append((ad, "izin", t, None))
                continue
            anlasilan[t] = dict(tan, sunucu=ad, aktarim=s["aktarim"])
    return anlasilan, dusen


def cagir(anlasilan, ad, durum):
    if ad not in anlasilan:
        return {"hata": "arac bildirilmedi"}
    t = anlasilan[ad]
    kw = {a: durum[a] for a in t["alan"] if a in durum}
    mesaj("istek", f"{ad} " + " ".join(f"{k} {v}" for k, v in kw.items()), t["aktarim"])
    s = t["islev"](**kw)
    mesaj("yanit", " ".join(f"{k} {v}" for k, v in s.items()))
    return s


# ---- Istemci: sunucu kesfi ve oturum
GUNCEL = {"olcum": 2, "tarife": 1, "kayit": 3}     # kayit sunucusunun surumunu ogrenmis istemci


def oturum_ac(istemci_surum=None):
    """Sunucu kesfi: uc sunucuya yetenek sorulur, oturum anlasilan kumeyi tutar."""
    anlasilan, dusen = elsikismasi(istemci_surum)
    return {"anlasilan": anlasilan, "dusen": dusen,
            "bildirilen": sum(len(s["arac"]) for s in SUNUCU.values())}


for ad, sur in (("istemci kayit surum 2 biliyor", None), ("istemci kayit surum 3 biliyor", GUNCEL)):
    for k in SAYIM:
        SAYIM[k] = 0
    o = oturum_ac(sur)
    print(f"kesif ( {ad} ) : istek {SAYIM['istek']} , yanit {SAYIM['yanit']} ,"
          f" belirtec {SAYIM['belirtec']} , gidis-donus {SAYIM['gidis_donus']}")
    print(f"   bildirilen {o['bildirilen']} , anlasilan {len(o['anlasilan'])}"
          f" {sorted(o['anlasilan'])} , dusen {o['dusen']}")
```

```
kesif ( istemci kayit surum 2 biliyor ) : istek 3 , yanit 3 , belirtec 99 , gidis-donus 5
   bildirilen 5 , anlasilan 3 ['sayac_oku', 'tarife', 'tuketim'] , dusen [('kayit', 'surum', 3, 2)]
kesif ( istemci kayit surum 3 biliyor ) : istek 3 , yanit 3 , belirtec 99 , gidis-donus 5
   bildirilen 5 , anlasilan 4 ['bolge_ozet', 'sayac_oku', 'tarife', 'tuketim'] , dusen [('kayit', 'izin', 'kayit_yaz', None)]
```

Keşfin bedeli üç sunucu için **3 istek, 3 yanıt, 99 belirteç, 5 gidiş-dönüş**. Gidiş-dönüşün beş
oluşu sunucuların dağılımından gelir: `olcum` yerel olduğu için bir, `tarife` ile `kayit` uzak
olduğu için ikişer sayılır. Bu bedel protokolsüz kurulumda **hiç yoktur**; araç ders içinde
tanımlıyken sorulacak kimse de yoktur. Karşılığında kazanılan şey oturumun tuttuğu kümedir.

Asıl bulgu iki satırın **aynılığındadır**. Üstteki istemci `kayit` sunucusunun sürüm 2'sini
biliyor ve sunucunun tamamını düşürüyor; alttaki sürüm 3'ü biliyor ve `bolge_ozet`i alıyor.
Anlaşılan araç üçe karşı dört. Buna karşılık mesaj sayıları birebir aynıdır: **3 istek, 3 yanıt,
99 belirteç, 5 gidiş-dönüş**. Sunucu bir hata yollamadı, istemci bir hata almadı, iki taraf da
keşfi başarıyla bitirmiş sayıyor. Düşmenin kaydı yalnız istemcinin kendi listesindedir ve oraya
bakan biri yoksa hiçbir yerde yazmaz.

## Keşif Kaç Kez Ödenir

Aşağıdaki kod on iki kurgu görevi yürütür. İki değişken vardır: keşfin bir kez mi her görevde mi
yapıldığı, ve istemcinin `kayit` sunucusunun hangi sürümünü bildiği.

```python
ZINCIR = {"bedel": ("sayac_oku", "tuketim", "tarife"), "tuketim": ("sayac_oku", "tuketim"),
          "bolge": ("bolge_ozet",), "kayit": ("sayac_oku", "tuketim", "tarife", "kayit_yaz")}


def gorevler(turler, tohum=TOHUM):
    """12 KURGU gorev: verilen turlerden esit sayida, abonesi ve bolgesi tohumla secilir."""
    g = []
    for i in range(12):
        r = uretec(tohum + 9973 * i)
        g.append({"tur": turler[i % len(turler)], "abone": f"A{1 + int(r() * 60):03d}",
                  "bolge": BOLGE[int(r() * 5)]})
    return g


def dogru(g):
    a = ABONE[g["abone"]]
    f = a["simdiki"] - a["onceki"]
    m3 = f + 100000 if f < 0 else f
    if g["tur"] == "tuketim":
        return m3
    if g["tur"] == "bolge":
        return sum(1 for x in ABONE.values() if x["bolge"] == g["bolge"])
    return True if g["tur"] == "kayit" else _bedel(m3, a["tip"])


def yurut(oturum, g):
    """Bir gorevin zinciri; hata donen adimda kesilir."""
    durum, sonuc = {"abone": g["abone"], "bolge": g["bolge"], "sutun": "bedel"}, None
    for ad in ZINCIR[g["tur"]]:
        durum["deger"] = sonuc
        y = cagir(oturum["anlasilan"], ad, durum)
        if "hata" in y:
            break
        durum.update(y)
        sonuc = y.get("yazildi", y.get("tutar", y.get("m3", y.get("abone", sonuc))))
    return sonuc == dogru(g)


def kosum(turler, istemci_surum=None, gorev_basina=False, tohum=TOHUM):
    """Ayni 12 gorev; degisken, kesfin kac kez odendigi ve istemcinin bildigi surum."""
    for k in SAYIM:
        SAYIM[k] = 0
    oturum = None if gorev_basina else oturum_ac(istemci_surum)
    tamam = 0
    for g in gorevler(turler, tohum):
        o = oturum_ac(istemci_surum) if gorev_basina else oturum
        tamam += yurut(o, g)
    return dict(anlasilan=len(o["anlasilan"]), tamam=tamam, **SAYIM)


DORT, IKI = ("bedel", "tuketim", "bolge", "kayit"), ("bedel", "tuketim")
print("kurulum                                anlasilan tamamlanan istek yanit belirtec gidis")
for ad, tur, sur, gb in (("tek oturum , surum sapmasi var", DORT, None, False),
                         ("gorev basina oturum , sapma var", DORT, None, True),
                         ("tek oturum , istemci guncel", DORT, GUNCEL, False),
                         ("tek oturum , bolge gorevi yok", IKI, None, False),
                         ("tek oturum , bolge yok , guncel", IKI, GUNCEL, False)):
    a = kosum(tur, sur, gb)
    print(f"{ad:<38} {a['anlasilan']:>6} {a['tamam']:>10} {a['istek']:>6} {a['yanit']:>5}"
          f" {a['belirtec']:>8} {a['gidis_donus']:>6}")
b = kosum(DORT, None, False, TOHUM + 1)
print("ikinci kosum ( gorev tohumu 20260219 , tek oturum ) :", dict(
    tamamlanan=b["tamam"], istek=b["istek"], yanit=b["yanit"], belirtec=b["belirtec"]))
```

```
kurulum                                anlasilan tamamlanan istek yanit belirtec gidis
tek oturum , surum sapmasi var              3          6     27    27      744     35
gorev basina oturum , sapma var             3          6     60    60     1833     90
tek oturum , istemci guncel                 4          9     30    30      816     41
tek oturum , bolge gorevi yok               3         12     33    33      909     41
tek oturum , bolge yok , guncel             4         12     33    33      909     41
ikinci kosum ( gorev tohumu 20260219 , tek oturum ) : {'tamamlanan': 6, 'istek': 27, 'yanit': 27, 'belirtec': 744}
```

İlk iki satır oturumun ne işe yaradığını sayıyla verir. Yapılan iş ikisinde de aynıdır: aynı on iki
görev, aynı zincirler, aynı altı tamamlanan görev. Tek oturumda keşif bir kez ödenir; toplam
**27 istek, 27 yanıt, 744 belirteç, 35 gidiş-dönüş**. Keşfi her görevde yenilemek aynı işi
**60 istek, 60 yanıt, 1833 belirteç ve 90 gidiş-dönüşe** çıkarır. İş belirteci iki satırda da
645'tir; değişen yalnız keşiftir ve 99'dan **1188'e** çıkar. Yani ikinci satırda harcanan
belirtecin çoğunluğu iş için değil, aynı üç soruyu on iki kez sormak için gitmiştir.

Üçüncü satır sürümü öğrenmiş istemcinindir. Anlaşılan araç dörde, tamamlanan görev **altıdan
dokuza** çıkar; bedeli üç istek, üç yanıt, 72 belirteç ve altı gidiş-dönüştür. Kalan üç görev
kayıt görevidir ve `kayit_yaz` izin listesinde olmadığı için hiçbir kurulumda tamamlanmaz.
İkinci koşumda görevler başka bir tohumla çekilir; tamamlanan görev, istek, yanıt ve belirteç
birebir aynı çıkar. Sayılan şey verinin değil sözleşmenin özelliğidir.

## Sessizce Düşen Araç

Son iki satır bu dersin asıl bulgusudur. Görev kümesinden bölge görevleri çıkarılmıştır; geriye
yalnız bedel ve tüketim görevleri kalır. Sapmalı istemci ile güncel istemci bu kümede
**tıpatıp aynı** sayıları üretir: 12 tamamlanan görev, 33 istek, 33 yanıt, 909 belirteç, 41
gidiş-dönüş. Biri üç araçla çalışıyor, öbürü dört araçla; hiçbir mesaj, hiçbir hata, hiçbir sayı
bu farkı taşımıyor.

Sürüm sapmasının sessizliği tam olarak budur. Kayıp bir hata değildir, çünkü hata döndüren bir
adım yoktur; el sıkışması iki tarafta da başarıyla tamamlanmıştır. Kayıp yalnızca **kullanılan bir
aracın eksilmesi** olarak, o da ancak birinin o aracı çağırmaya kalkmasıyla ortaya çıkar. Çağıran
olmazsa hiç ortaya çıkmaz ve sistem, dört araçlık bir kümeyle çalıştığını sanarak üçüyle çalışmayı
sürdürür.

Bunu görünür kılan tek şey oturum açılışındaki sayımdır: **bildirilen 5, anlaşılan 3, düşen 2**.
Üç sayı yan yana basılmadığı sürece sürüm sapması hiçbir yere yazılmaz. Kursun kuralı burada
doğrudan okunur: değiştiğinde kimin kırılacağı yazılmayan bir arayüz ölçülmemiş sayılır. Küme
beş araçlıktır ve ölçebildiği en küçük adım 0,2000'dir; bu yüzden oran değil, üç sayının kendisi
basılır.

## Özet

- İstemcinin iki işi sunucu keşfi ve oturumdur: keşif sorar, oturum anlaşılan araç kümesini tutar.
- Üç sunucuya yapılan keşif 3 istek, 3 yanıt, 99 belirteç ve 5 gidiş-dönüş tutar; gidiş-dönüşün
  beş oluşu bir yerel ve iki uzak sunucudan gelir.
- Aynı on iki görev tek oturumda 27 istek ve 744 belirteç harcar; keşif her görevde yenilenince
  60 istek ve 1833 belirtece çıkar ve bunun 1188'i keşfe gider.
- Sürümü öğrenmiş istemcide anlaşılan araç 3'ten 4'e, tamamlanan görev 6'dan 9'a çıkar; bedeli
  72 belirteç ve altı gidiş-dönüştür.
- Bölge görevi olmayan bir kümede sapmalı ve güncel istemci birebir aynı sayıları verir: düşen
  araç hiçbir hata üretmez ve kullanılmadığı sürece hiç görünmez.
- Kaybı görünür kılan tek şey oturum açılışında basılan bildirilen 5, anlaşılan 3, düşen 2
  sayımıdır.

## Sonraki Adım

Bu iki derste sunucu ve istemci yazıldı, keşfin bedeli sayıldı ve düşen aracın sessizliği görüldü.
İkisi de tek bir varsayımın üstünde duruyordu: sunucu **oradaydı**. Yerel bir sunucu ise ayrı bir
süreçtir; başlatılması gerekir, hazır olduğunu yanıt beklemeyen bir bildirim mesajıyla söyler ve
oturumun ömrü onun ömrüyle sınırlıdır. Sonraki ders bu bağlantıyı kurar: ilk çağrıya kadar kaç
mesaj geçtiğini, aynı zincirin gidiş-dönüş ve zarf belirtecini, ve süreç düştüğünde kurulumun
yeniden kaç mesaja mal olduğunu sayar.
