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_yazlistede yoktur. İstemcikayitsunucusunun 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 bir
— kayit_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.