İçeriğe geç
academia.sh

Ders 06 / 11

Sunucu Yazımı

Bir sunucunun işleyicileri ile yetenek bildiriminin ayrılması ve bildirimin eksiksizliğinin çağrılabilirlik olarak ölçülmesi: aynı üç kurgu sunucu ve aynı on iki görev dört bildirim düzeyinde koşturulduğunda el sıkışmasının bedeli dört düzeyde de 3 istek, 3 yanıt, 99 belirteç ve 5 gidiş-dönüştür, değişen tek şey o üç yanıtın istemcinin eline ne bıraktığıdır. Yalnız ad bildiren düzeyde anlaşılan araç dörttür ama istemci alan dolduramadığı için 12 çağrının 12'si başarısız olur ve tamamlanan görev sıfırdır. Alan bildirilince çağrı 30'a, tamamlanan görev 9'a çıkar. Yan etkinin bildirilmesi bu izin listesinde hiçbir sayıyı oynatmaz. Sürümün bildirilmesi anlaşılan aracı 4'ten 3'e düşürür, tamamlanan görevi 6'ya indirir ve zarf belirtecini 816'dan 744'e çeker: en eksiksiz bildirim en küçük araç kümesini verir. İzin listesi kayit_yaz'ı kabul edecek biçimde genişletildiğinde yan etkisi bildirilmeyen üç yazma çağrısı sessizce geçer, bildirildiğinde aynı üç çağrı onay kapısında durdurulur.

İçindekiler

Önceki beş derste model bağlam protokolü dışarıdan anlatıldı: üç rolün sorumluluğu ayrıldı, aktarım katmanının gidiş-dönüşü sayıldı, veri katmanının mesaj türleri ölçüldü ve bir sunucunun bildirdiği beş araçtan yalnız üçünün anlaşıldığı görüldü. Bütün o ölçümlerde sunucu verilmiş bir şeydi. Hiçbiri yazılmadı.

Bu ders sunucuyu yazar. Yazılan şey iki parçadır ve aradaki ayrım kursun kalan ölçümlerini taşır. İşleyici, gelen isteği karşılayıp sonucu döndüren işlevdir; sunucunun içindedir ve istemci onu hiç görmez. Yetenek bildirimi, o işleyicinin dışarıya verilen tanımıdır ve istemcinin gördüğü tek şeydir. Araç tanımının kattığı Yapay Zekâ Ajanları kursunda ölçülmüştü: yalnız adla verilen tanımda geçerli çağrı oranı 0,2803, şemayla 0,8919. O ölçüm tanımın içeriğini sorguluyordu ve tanım elin altındaydı. Burada tanım karşı taraftadır ve sorulan şey eksiksizliğidir: işleyici yerinde durduğu hâlde, bildirilmeyen bir alan istemcinin elinde hiç yoktur.

  • GE1. Ortam, üç sunucu ve ders içindeki protokol benzeticisi kurgudur; gerçek bir sunucu, istemci kitaplığı ya da çalışma zamanı çağrılmaz. 60 kurgu abone, üç kurgu sunucu, beş bildirilen araç. Tohum 20260218, ikinci koşum 20260219.
  • GE2. Dört düzeyin hepsinde işleyiciler aynıdır, değişen yalnız bildirimdir: yalnız ad; ad ve alan; ad, alan ve yan etki; tam bildirim (sürüm eklenmiş).
  • GE3. İstemci yalnız bildirileni kullanır. Alan bildirilmemişse çağrı alansız gider.
  • GE4. Zarf her istek ve yanıtı sayar; sabit bedeli 8 belirteçtir ve uzak aktarımda gidiş-dönüş iki katıdır.
  • GE5. İzin listesi ada bakar ve dört araç taşır; kayit_yaz listede yoktur. İstemci kayit sunucusunun sürüm 3’ünü değil, sürüm 2’sini bilir.
  • GE6. Görev kümesi 12 kurgu görevdir: dört türden üçer tane, her biri bilinen bir araç zinciriyle çözülür. Bir çağrı hata döndürürse zincir orada kesilir.
  • GE7. Tamamlanan görev, zincirin bıraktığı sonucun doğru yanıta eşit olmasıdır.
  • GE8. Yan etkili bir araç, yan etkisi bildirilmişse onay ister; bildirilmemişse istemci onay kapısını kuramaz.
  • GE9. Taban çizgisi protokolsüz kurulumdur: aracın ders içinde tanımlandığı, el sıkışmasının olmadığı, zarfın sayılmadığı hâl.
  • GE10. Küme küçüktür: üç sunucu, beş bildirilen araç. Bu yüzden 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.

İşleyici ve Bildirim

Aşağıdaki kod kurgu ortamı, üç kurgu sunucuyu ve zarf sayımını kurar, sonra bildirimi işleyiciden ayıran iki işlev ekler: bildir sunucunun dışarıya verdiği tanımı bir düzeyde üretir, uzlas o tanımla yetenek uzlaşmasını yapar.

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 isleyici tutar ve 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


# ---- Bildirim duzeyleri: sunucunun disariya verdigi tanim ne tasiyor
DUZEY = ("ad", "ad + alan", "ad + alan + yan etki", "tam")


def bildir(s, d):
    """Sunucunun bildirimi; duzey, isleyicinin hangi yuzunun disari ciktigini belirler."""
    b = {"arac": {}}
    for ad, t in s["arac"].items():
        tan = {}
        if d >= 1:
            tan["alan"] = t["alan"]
        if d >= 2:
            tan["yan_etki"] = t["yan_etki"]
        b["arac"][ad] = tan
    if d >= 3:
        b["surum"] = s["surum"]
    return b


def uzlas(d, izin=IZIN):
    """Yetenek uzlasmasi yalniz bildirilenle yapilir: bildirilmeyen uzlasilamaz."""
    anlasilan, dusen = {}, []
    for ad, s in SUNUCU.items():
        b = bildir(s, d)
        mesaj("istek", f"yetenek sor {ad}", s["aktarim"])
        mesaj("yanit", f"yetenek bildir {ad} surum {s['surum']} "
                       + " ".join(s["arac"]) + " " + " ".join(s["kaynak"]))
        if "surum" in b and ISTEMCI_SURUM.get(ad, 0) < b["surum"]:
            dusen += [(t, "surum") for t in b["arac"]]
            continue
        for t, tan in b["arac"].items():
            if t not in izin:
                dusen.append((t, "izin"))
                continue
            anlasilan[t] = dict(tan, islev=s["arac"][t]["islev"], aktarim=s["aktarim"])
    return anlasilan, dusen


def cagir(anlasilan, ad, durum, onay=False):
    """Istemci yalniz bildirilen alani doldurur; bildirilen yan etki onay ister."""
    if ad not in anlasilan:
        return {"hata": "arac bildirilmedi"}
    t = anlasilan[ad]
    if t.get("yan_etki") and not onay:
        return {"hata": "onay yok"}
    kw = {a: durum[a] for a in t.get("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


print("duzey                  olcum sunucusunun bildirimi ( sayac_oku )")
for d, ad in enumerate(DUZEY):
    b = bildir(SUNUCU["olcum"], d)
    print(f"{d} {ad:<20} surum {b.get('surum', '-')} , {b['arac']['sayac_oku']}")
print()
print("duzey                  anlasilan  dusen  istek  yanit  belirtec  gidis-donus")
for d, ad in enumerate(DUZEY):
    for k in SAYIM:
        SAYIM[k] = 0
    a, ds = uzlas(d)
    print(f"{d} {ad:<20} {len(a):>9} {len(ds):>6} {SAYIM['istek']:>6} {SAYIM['yanit']:>6}"
          f" {SAYIM['belirtec']:>9} {SAYIM['gidis_donus']:>12}")
print("tam duzeyde anlasilan :", sorted(uzlas(3)[0]))
duzey                  olcum sunucusunun bildirimi ( sayac_oku )
0 ad                   surum - , {}
1 ad + alan            surum - , {'alan': ('abone',)}
2 ad + alan + yan etki surum - , {'alan': ('abone',), 'yan_etki': False}
3 tam                  surum 2 , {'alan': ('abone',), 'yan_etki': False}

duzey                  anlasilan  dusen  istek  yanit  belirtec  gidis-donus
0 ad                           4      1      3      3        99            5
1 ad + alan                    4      1      3      3        99            5
2 ad + alan + yan etki         4      1      3      3        99            5
3 tam                          3      2      3      3        99            5
tam duzeyde anlasilan : ['sayac_oku', 'tarife', 'tuketim']

İkinci tablonun dört satırında el sıkışmasının bedeli değişmiyor: her düzeyde 3 istek, 3 yanıt, 99 belirteç, 5 gidiş-dönüş. Eksiksiz bildirim keşif anında bedava geldi; değişen tek şey o üç yanıtın istemcinin eline ne bıraktığı oldu. İlk üç düzeyde anlaşılan araç dört, düşen birkayit_yaz, adı izin listesinde olmadığı için. Dördüncü düzeyde sürüm bildirildiği anda istemci bir karşılaştırma yapabiliyor: kendi bildiği sürüm 2, sunucunun bildirdiği 3. kayit sunucusunun tamamı düşüyor ve bolge_ozet de onunla gidiyor, izin listesinde olduğu hâlde. Anlaşılan araç dörtten üçe iniyor. Kursun ikinci iddiasının bu dersteki karşılığı budur: en eksiksiz bildirim en küçük araç kümesini verdi. Eksik bildiren sunucu daha çok araç anlaştırıyor, çünkü karşılaştırılacak bir şey söylememiş oluyor.

Dört Düzeyin Sayısı

Aynı on iki görev dört düzeyde koşturulur. Görev kümesi dört türden üçer tanedir: bedel, tüketim, bölge ve kayıt. Tek değişken bildirim düzeyidir.

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


def gorevler(tohum=TOHUM):
    """12 KURGU gorev: dort turden ucer tane, abonesi ve bolgesi tohumla secilir."""
    g = []
    for i in range(12):
        r = uretec(tohum + 9973 * i)
        g.append({"tur": list(ZINCIR)[i % 4], "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 kosum(d, izin=IZIN, onay=False, tohum=TOHUM):
    """Ayni 12 gorev, tek degisken bildirim duzeyi."""
    for k in SAYIM:
        SAYIM[k] = 0
    anlasilan = uzlas(d, izin)[0]
    s = {"cagri": 0, "basarisiz": 0, "tamam": 0, "durdurulan": 0, "yazilan": 0}
    for g in gorevler(tohum):
        durum = {"abone": g["abone"], "bolge": g["bolge"], "sutun": "bedel"}
        sonuc = None
        for ad in ZINCIR[g["tur"]]:
            durum["deger"] = sonuc
            y = cagir(anlasilan, ad, durum, onay)
            s["cagri"] += 1
            if "hata" in y:
                s["basarisiz"] += 1
                s["durdurulan"] += y["hata"] == "onay yok"
                break
            durum.update(y)
            s["yazilan"] += y.get("yazildi") is True
            sonuc = y.get("yazildi", y.get("tutar", y.get("m3", y.get("abone", sonuc))))
        s["tamam"] += sonuc == dogru(g)
    return dict(anlasilan=len(anlasilan), **s, **SAYIM)


print("duzey                  anlasilan  cagri  basarisiz  tamamlanan  istek  yanit  belirtec")
for d, ad in enumerate(DUZEY):
    a = kosum(d)
    print(f"{d} {ad:<20} {a['anlasilan']:>9} {a['cagri']:>6} {a['basarisiz']:>10}"
          f" {a['tamam']:>11} {a['istek']:>6} {a['yanit']:>6} {a['belirtec']:>9}")
b = [kosum(d, tohum=TOHUM + 1) for d in range(4)]
print("ikinci kosum ( gorev tohumu 20260219 ) tamamlanan :", [x["tamam"] for x in b],
      ", belirtec :", [x["belirtec"] for x in b])
print()
GENIS = IZIN + ("kayit_yaz",)
print("genis izin listesi ( kayit_yaz eklendi )   anlasilan  durdurulan  yazilan  tamamlanan")
for d, onay in ((1, False), (2, False), (2, True)):
    a = kosum(d, izin=GENIS, onay=onay)
    print(f"duzey {d} , onay {str(onay).lower():<5}                      {a['anlasilan']:>9}"
          f" {a['durdurulan']:>11} {a['yazilan']:>8} {a['tamam']:>11}")
duzey                  anlasilan  cagri  basarisiz  tamamlanan  istek  yanit  belirtec
0 ad                           4     12         12           0     15     15       351
1 ad + alan                    4     30          3           9     30     30       816
2 ad + alan + yan etki         4     30          3           9     30     30       816
3 tam                          3     30          6           6     27     27       744
ikinci kosum ( gorev tohumu 20260219 ) tamamlanan : [0, 9, 9, 6] , belirtec : [351, 818, 818, 744]

genis izin listesi ( kayit_yaz eklendi )   anlasilan  durdurulan  yazilan  tamamlanan
duzey 1 , onay false                              5           0        3          12
duzey 2 , onay false                              5           3        0           9
duzey 2 , onay true                               5           0        3          12

İlk satırda anlaşılan araç dörttür, yani istemci hangi araçların var olduğunu biliyor; işleyiciler de yerinde duruyor. Buna rağmen tamamlanan görev sıfır. İstemci hangi alanı dolduracağını bilmediği için her çağrı alansız gidiyor, sayac_oku abone yok döndürüyor ve zincir ilk adımda kesiliyor. Zincirlerin toplam uzunluğu otuz adımken yalnız 12 çağrı yapılabiliyor, hepsi başarısız oluyor ve koşum 351 belirteç harcıyor. En ucuz koşum, hiçbir işi bitirmeyen koşumdur.

İkinci satırda alan bildiriliyor ve zincirler işliyor: çağrı 30’a, tamamlanan görev dokuza çıkıyor. Kalan üç başarısız çağrı kayıt görevlerinin son adımıdır; kayit_yaz izin listesinde olmadığı için arac bildirilmedi dönüyor ve bu üç çağrı hiç mesaj harcamıyor — istek 30, yani 3 el sıkışması artı 27 gönderilen çağrı. Anlaşılmamış bir araca yapılan çağrı istemcinin içinde biter, tele hiç çıkmaz.

Üçüncü satır ikinciyle birebir aynıdır. Yan etkinin bildirilmesi bu izin listesinde tek bir sayıyı bile oynatmadı, çünkü yan etkili tek araç zaten listede değildi. Sınanmayan bir bildirimin ölçülebilir değeri yoktur. Dördüncü satır ise daha ucuz ve daha kötüdür: zarf belirteci 816’dan 744’e iniyor, tamamlanan görev dokuzdan altıya. Üç bölge görevi, bolge_ozet düştüğü için tek adımda arac bildirilmedi alıyor.

İkinci koşumda görev kümesi başka bir tohumla çekiliyor, yani başka aboneler ve başka bölgeler geliyor; tamamlanan görev dört düzeyde de aynı kalıyor. Zarf belirteci yalnız iki düzeyde 816’dan 818’e oynuyor, çünkü bir gövdeye giren bölge adı bir belirteç uzun. Sayılan şey verinin değil sözleşmenin özelliğidir.

Bildirilmeyen Yan Etki

Son üç satır, üçüncü düzeyin niçin boşuna göründüğünü açıklar. İzin listesine kayit_yaz eklendiğinde anlaşılan araç beşe çıkıyor. Yan etki bildirilmemişken (birinci düzey) üç yazma çağrısı geçiyor ve on iki görevin hepsi tamamlanıyor; istemcinin elinde bu üç çağrının ötekilerden farklı olduğunu söyleyen hiçbir alan yoktur.

Yan etki bildirildiğinde (ikinci düzey) aynı üç çağrı onay kapısında duruyor: durdurulan 3, yazılan 0, tamamlanan 9. Onay verildiğinde yine üçü yazılıyor. Onayın verilip verilmeyeceği istemcinin izin modelidir ve Yapay Zekâ Ajanları kursunda ölçülmüştür; burada ölçülen şey onun önkoşuludur: bildirilmeyen bir yan etki durdurulamaz, çünkü sorulacak bir soru yoktur. Üç durdurulan çağrı on iki görevin içinde sayılır; bu küme oran basmak için fazla küçüktür.

Özet

  • Sunucunun iki yüzü ayrılır: işleyici içeride durup hiç görünmez, yetenek bildirimi istemcinin elindeki tek şeydir.
  • El sıkışmasının bedeli dört bildirim düzeyinde de aynıdır — 3 istek, 3 yanıt, 99 belirteç, 5 gidiş-dönüş; eksiksizlik keşif anında ücretsizdir.
  • Yalnız ad bildiren düzeyde anlaşılan araç dörttür ama tamamlanan görev sıfırdır: 12 çağrının 12’si alansız gittiği için başarısız olur ve koşum 351 belirteçle en ucuz koşumdur.
  • Alan bildirilince çağrı 30’a ve tamamlanan görev 9’a çıkar; anlaşılmamış bir araca yapılan üç çağrı hiç mesaj harcamaz.
  • Sürüm bildirilince anlaşılan araç 4’ten 3’e, tamamlanan görev 9’dan 6’ya iner: en eksiksiz bildirim en küçük araç kümesini verir.
  • Yan etki bildirilmemişken üç yazma çağrısı sessizce geçer; bildirildiğinde durdurulur.

Sonraki Adım

Bu derste sunucu yazıldı ve bildiriminin eksikliği çağrılabilirlik olarak sayıldı. Karşı taraf hazır kabul edildi: el sıkışmasının çoktan yapılmış olduğu, anlaşılan kümenin bir yerde durduğu ve kimin ne zaman sorduğu hiç sorulmadı. Sonraki ders istemciyi yazar. Sunucu keşfinin ne tuttuğunu, aynı keşfin her görevde yenilenmesiyle bir oturumda bir kez ödenmesi arasındaki farkı ve kayit sunucusunun düşmesinin hiçbir hata üretmediğini ölçer: keşif mesajları düşen sunucuyla düşmeyeni birbirinden ayırt etmez, aradaki farkı görünür kılan tek şey sayımdır.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat