Ders 07 / 11
İ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.
İçindekiler
Ö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_yazlistede yoktur. - GE15. Sürüm sapması:
kayitsunucusu 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.
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_ozeti 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.
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.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.