TCP Ne kadar güvenilir – 1

Bu yazımızda kısaca TCP protokolündeki bazı hususları irdeleyeceğiz.

TCP’yi okulda öğretilenden biraz daha farklı olarak tecrübelerle aktarmayı düşünüyorum. Muhtemelen TCP denince aklımıza reliable kavramı geliyordur. Ben bu detayların aksine günlük hayatta karşımıza çıkabilecek hatta çıkmış olsa bile bu problemi genellikle bir framework kullandığımız için farketmediğimiz durumları ifade etmeye çalışacağım.

Tahmin edileceği gibi bir adet sunucu var, istemci sayısı ise 3 olsun. Uygulamamız ise bir sohbet (chat) uygulaması olsun. Sohbeti gerçekleştiren kişiler biraz agresif karakterli kişiler olsunlar. Öyle kafaları kızınca sohbeti sonlandıran, sizi bloklayan hatta bilgisayarın ağ kablosunu, güç kablosunu çeken hatta bilgisayarı resetleyen cinsten karakterler. Böyle durumlara dayanıklı bir sunucu yazmak kolay değildir, inanın. Bunu her durum için küçük küçük python uygulamalarıyla göstermeye çalışacağım. Ancak ilk olarak teorik bilgi sonra bu durumun canlı uygulamasını sunmaya çalışacağım.

İki muhabbetçinin konuşması:
Muhabbetçiler sunucu aracılığıyla konuşacaklar daima. Yani ilk iletişim kurulduktan sonra sunucu aradan çekilmeyecek. Tasarımı buna göre yapalım. Yani Ali, Veli, Mustafa olsun ve sunucu yazılımının adı da “Lafçı” olsun.

Ali’nin Veli’ye mesaj gönderdiği durumda eğer Ali’nin Lafçı’ya bir TCP oturumu yoksa yeni bir bağlantı açacak ve veriyi bu soketten (bağlantı, oturum) gönderecektir. Lafçı verinin kime iletileceğini veriden çıkaracak ve veriyi eğer kendisine ilgili kullanıcıya ait bir oturum varsa o oturum üzerinden gönderecektir. Aksi halde yani ilgili kullanıcı çevrim içi değil ise ya veritabanına sonra gönderilmek üzere saklayacak veya /dev/null‘a (ne demek istediğimi anladınız*) gönderecektir.

Buraya kadar herşey yolundadır umarım. Şimdi Murphy konuşsun bakalım.

Problem 1:
a) Ali Veli’ye bir mesaj göndermek istiyor.
b) Ali mesajı yazdı ve gönder butonuna bastı.
c) Ali’nin bilgisayarından Lafçı’ya bir TCP oturum kuruldu.
d) Lafçı mesajı aldı, kime göndereceğine baktı ve Veli olduğunu gördü.
e) Veli’nin çevrim içi (online) olduğunu gördü.
f) Lafçı Veli’nin oturumuna (yani istemci soketine) veriyi yazmaya başladı.
g) Lafçı Veli’nin mesajını tam yazamadan Veli programı kapattı

Bu durumda Veli’nin programının kapanması Veli’nin oturumunun kapanmasına (soketin kapanmasına) sebep olur. Yani Lafçı tam mesajı yazamadan (mesela, 100 karakterlik bir mesaj ve 20inci karakteri henüz yazmış ve daha 80 karakter için işlem yapılması gerekirken) Veli soketi kapattı, soru: böyle bir durumda ne olur?

Eğer sunucu uygulamanız yani Lafçı bu durumu daha önceden algılayacak kodu barındırmıyorsa “broken pipe” hatası ile sonlanır. Sonlanır demek bir hafif oldu, aslına bakacak olursanız sunucu uygulamanız kırılır. C’de bu durumdan SIG_PIPE sinyalini bloklayarak kurtulabilirsiniz.

Peki bu hatanın alınması nasıl oldu?
Her iki bilgisayar (yani Lafçı’nın çalıştığı ve Veli’nin kullandığı) soket haberleşmesini TCP stack (yığın) aracılığıyla gerçekeştirmektedir. Lafçı’nın bilgisayarı soketin kapatıldığını Veli’nin bilgisayarındaki TCP stack’in söylemesi ile bilmektedir. Yani Veli’nin bilgisayarındaki program kapanınca Veli’nin bilgisayarındaki TCP stack Lafçı’nın bilgisayarındaki TCP stack’e artık bana yazamazsın demektedir. Karşı TCP stack’ten gelen bu bilgiyi alan Lafçı’nın TCP stack’i ise Lafçı uygulamasına ilgili hata sinyalini göndermektedir. Yani Lafçı arada bir Veli’nin bilgisayarına gidip arkadaş sen soketi kapattın mı dememekte tersine soketi kapatan taraf, arkadaş bak ben kapattım haberin olsun demektedir.

Özetle iki bilgisayar en alt seviyede soket haberleşmesini TCP stack ile yapmaktadır. İki bilgisayardaki soket durumları, paket akışı, window size, ACK almış paketler vb. hepsi TCP stack’lar tarafından kontrol edilmektedir. Bize ise herşeyden arındırılmış temiz bir akış (stream) sunulmaktadır.

Diğer durumlar için ilerleyen yazılarda incelememize devam edeceğiz.

Sağlıcakla kalınız

* /dev/null ile kasıt mesajın discard edileceğidir. /dev/null linux’te bir bir psudo aygıttır. Bu aygıta gönderilen herşey yok olur. Genellikle sistem yöneticileri veya bazı scriptler bazı mesajları baskılamak için mesajı bu aygıta yönlendirirler.

TCP Ne Kadar Güvenilir – 2

Bu yazımızda TCP’nin sıklıkla karşılaşılmayabilecek, ancak kritik bir problemini açıklayacağız. Bu problemi açıklamadan önce send() ve recv() metotlarının aslında TCP stack’i aracı yaparak işlevlerini gördüklerini ifade etmiştik. Bir soket kapandığı zaman kapatmayı tetikleyen yazılımın bulunduğu bilgisayardaki TCP stack’in karşı taraftak TCP stack’a durumu bildirdiğini ifade etmiştik. Bu cümleyi ne kadar tekrarlasak o kadar önemli, çünkü aslında iletişim kuranlar TCP stack’ler. Programlar TCP stack’leri aracı olarak kullanıyorlar. Paketlerdeki ACK’leri, checksum’ları, akış kontrolünü (bu terimleri bilmiyorsanız boşverin) uygulamalar değil TCP stack kontrol etmektedir.

Problemi tanımlayalım

İstemci-sunucu arasında bir bağlantı kurulmuş durumda olduğunu düşünelim. Bir mesajlaşma programı gibi ara ara sohbet iletileri gelip gidiyor.

Sunucuda istemciyle olan bağlantıyı tutan bir istemci soket vardır, (tabiki bir de sunucu soket, ancak bu konumuzla alakalı değil)
İstemcide sunucuyla iletişimi sağlayan bir istemci soket var.
İstemcinin bilgisayarının bağlandığı mekandaki elektrik gitti. Yedek güç kaynağı vb. de yok. Yani bilgisayar kapandı. Bunun doğal sonucu olarak istemcinin TCP stack’i artık ölüdür denilebilir. Kimse sunucunun TCP stack’ine bu durumu bildiremeyecek yani. TCP stack ise recv() fonksiyonu içerisinde olduğunu düşünelim.

Sizce, recv() bir hata ile veya hiç veri okumadan mı dönecek? Böyle bir durumda ne beklersiniz. İlk olarak recv()’in davranışını test edebilirsiniz. Soket Programlama – 2 yazımızdaki sunucu kodunu aşağıdaki gibi değiştirerek recv()’in davranışını gözlemleyelim:

import socket
import time

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((”, 9000))
s.listen(5)
conn, addr = s.accept()

time.sleep(100000)

conn.close()

Evet yeterince beklediğinizi düşünüyorsanız her iki programı da kapatabilirsiniz. recv()’in dönmediğini gördünüz. sleep süresini 10 saniye olarak değiştirip tekrar programı çalıştırınız ve recv()’in 10 saniye sonra sonlanacağını göreceksiniz. Bunun sebebi conn.close() metodu ile soketin kapatılması ve TCP stack’in bu durumu istemcinin TCP stack’ine iletmesidir. Yani recv()’in sonlamasının sebebi (bir okuma yapmaya çalışmasına rağmen) sunucunun soketi kapatması ve sunucu TCP stack’in durumu istemciye bildirmesidir.

Yukarıdaki ip uçlarını göz önünde bulundurursak, istemci recv() yaparken sunucu (veya tam tersi de geçerli) elektriksel veya başka bir sebeple kapanırsa ne olur? Cevap sizi şaşırtacak cinsten, recv() bunu algılayamayacak, ve ister inanın ister inanmayın oldukça uzun bir süre sonra recv() dönecektir. Eğer recv() fonksiyonu sunucuda çağrıldığı esnada bu durum gerçekleşirse, sunucu ister bu işlemi ayrı bir thread’de isterse ayrı bir process’de yapsın, kaynaklarını israf etmiş olacaktır. Hatta, bu durumun kendisi bir atak olarak kullanılabilir. Şöyle düşünelim, bir linux işletim sisteminde sunucu, her istemci için ayrı bir thread/process açılıyor olsun. Böyle bir atak ile sunucu servis dışı bırakılabilir.

Sağlıcakla kalınız

TCP Ne Kadar Güvenilir – 3

Bir önceki yazımızda (TCP Ne Kadar Güvenilir – 2) recv() metoduna kafayı takmıştık.

recv() metodu çağrılması esnasında karşı uçtaki cihazın bir sebeple tamamen erişilemez olması durumunda (örneğin elektrik kesilmesi, ağ kablosunun çekilmesi/kopması, cihazın reset alması vb.) recv()’in dönmediğini ifade etmiştik. Bu durumu bir örnekle göstermiştik. Aslında eğer sanallaştırma yapabilme imkanınız varsa (örneğin VirtualBox), kablo bağlantısını koparma gibi özellikleri VirtualBox sunmaktadır. recv()’in kablo bağlantısı koparılsa bile sonlanmadığını bu şekilde de test edebilirsiniz.

Bu yazıda, aynı durumda yani kablo çekilmesi veya reset alınması durumunda, send() durumunu irdeleyeceğiz. Bu seferki durum biraz daha karmaşık.

Continue reading