Soket Programlama – 1

İstemci Sunucu Kavramı ve Örnek Tasarımlar

Soket programlama IPC (interprocess communication) mimarisinde sıklıkla tercih edilen metotlardandır. Pek çok uygulama bu kavramı kullanmaktadır. En basitinden internet üzerinden oynanan bir tavla uygulaması soket programlama kullanılarak yapılabilir.

Soket programlama için temel iki kavram olan istemci ve sunucuyu açıklayalım. İstemciden çok sayıda olabilir. Sunucudan bir tane olacaktır. Aslında bu iki kavram benzetmeyle daha kolay anlaşılabilir. Sunucuyu bakkal dükkanı gibi düşünebiliriz. İstemciyi ise müşteriler. İkinci önemli mevzu ise bir iletişimi istemci başlatır, yani sunucu bekler istemci sunucuya gider. Bir programı tasarlarken iki bu kısıt dışında üst seviyede istemci ile sunucuyu ayıran başka birşey yoktur. Şimdi program örnekleriyle en sık bilinenden daha karmaşığa doğru farklı tasarımları açıklayalım.

1. Basit bir HTTP Sunucu:

HTTP sunucu nedir, bir web sayfasını size sunan uygulamadır. Örneğin google.com bir web sunucu üzerinden size sunucu olarak hizmet vermektedir. Yani bakkal dükkanıdır. İstemci ise chrome, firefox veya internet explorer’dır. Evet bu iki kısıta bakalım. Sunucudan 1 tane var (google.com), istemciden çok var (hepimiz). İkinci kısıt ise olayı kim başlatıyor? Biz yani istemci başlatıyor. Dolayısıyla bu tasarım en temel tasarımdır.

2. Tavla uygulaması:

Sunucu tavla uygulamasıdır ve 1 tanedir. Kendi içinde masalar vb. işler için farklı veri yapıları ile bu bilgiyi tutabilir. Neticede 1 tanedir. İstemciler ise oyunculardır. Hangi masaya oturacaklarsa o masaya katılım yaparlar yani birden fazla olabilir ve olayı istemciler başlatıyor. Bu tasarım da standart bir tasarımdır.

3. Web tabanlı ancak web soket kullanmadan yapılan bir sohbet uygulaması:

Bu uygulamada browser’lar istemcidir, kullanıcıların kullandığı internet sayfası ise sunucudur. Bu uygulamada kullanıcılar birbirine şöyle bir akışla mesaj gönderirler:

a) İstemci tarafı: kullanıcı metin kutusunu doldurur, gönder tuşuna basar.
b) Sunucu tarafı: sunucu mesajı alır kime gönderildiğine bakar ve ilgili kullanıcıya gönderir. Tabiki hayır. Sunucu bir bağlantı başlatamazdı hatırlayacak olursanız. Peki istemci kendisine bir mesaj gelince nasıl bu mesajı okuyacak? Tabiki ilgili sunucuya düzenli olarak kendisi (istemci olduğundan) bağlantı açarak bu durumu soracaktır.
c) İstemci web sunucusunı düzenli olarak (örneğin 10 saniyede bir) sorgulayarak yeni bir mesaj olup olmadığını kontrol eder varsa bu mesaj sayfada görüntülenir.

Bu durum tahmin edileceği gibi hiç etkin değildir. Ancak istemci-sunucu yapısı bize böyle bir çözümü tercih edersek bunu yapmamızı zorluyor. Çünkü bir tane olacak olan şey yani sunucu, bağlantı başlatamıyor.

4. Bankacılık uygulaması:

Gelelim biraz daha karmaşık tasarımlara. Bankasınız ve kullanıcılara SMS ile değil de android veya IOS uygulamasını kullandırarak internet sayfanızda giriş yaptırmak istiyorsunuz (maddi sebeplerden dolayı, SMS bedavadan daha pahalıdır). Bu işi istemci-sunucu ile tasarlayalım. Bir de olayın akışını ifade edelim de tasarımdaki zorluğu ortaya koyalım. İlk olarak kullanıcı bankanın internet sayfasına müşteri numarası ve şifresini girecektir. Bu aşamada standart olan neydi? Müşterinin cep telefonuna bir SMS ile ikinci bir şifre daha gelirdi ve bu şifreyide yeni açılan sayfaya girerek internet bankacılığına erişirdik. Ancak SMS yok, cep telefonu var, cep telefonunda da internet var ve internet bankacılığı uygulaması açık. Şimdi müşterinin cep telefonundaki internet bankacılığı uygulamasına bir mesaj gelecek ve müşteri bu mesajdaki “OK” tuşuna basınca herhangi bir ek işlem yapmadan bilgisayardaki internet sayfası asıl internet sayfasını açacak. Evet karmaşık oldu biraz daha açıklayalım.

a) kullanıcının cep telefonununun interneti var ve bankanın interaktif bankacılık uygulaması yüklü ve açık durumdab) kullanıcı bilgisayardan interaktif bankacılık müşteri adı ve şifresini giriyor
c) bu girişten sonra yeni bir sayfa açılıyor ve kullanıcı bu sayfada bekletiliyor
d) kullanıcının cep telefonuna onaylaması veya red etmesi için bir mesaj geliyor.
e) onaylarsa bilgisayarda bekletildiği sayfa geçiliyor ve internet bankacılığına başarılı bir giriş yapılmış oluyor.
=> yani SMS kalktı.

Şimdi soru şu?
Banka sunucu olmalı değil mi, evet. Peki bu yapı nasıl kurulacak?

Şöyle,

banka internet sayfası = sunucu (kaçarı yok, çünkü bakkal dükkanı orası), müşterinin bilgisayarındaki browser istemci (benzer şekilde kaçarı yok)
cep telefonu uygulaması = istemci, karşısındaki uygulama sunucu (iki kısıtı hatırlayalım)

O vakit başka bir soru akla geliyor, sunucu bekleyen taraf ise müşterinin cep telefonuna “onay” veya “red” mesajını nasıl gönderiyor? (3. örnekteki durumu hatırlayalım), Cevap kendi içinde saklı, kullanıcının cep telefonundaki uygulama açılırken sunucuya istemci olarak bağlantı kuruyor. Bu durumda sunucu ile istemci arasında bir bağ kuruldu ve artık sunucu istemciye mesaj gönderebilir. Hatırlayalım ilk bağlantıyı sunucu kuramaz ama ilk bağlantıdan sonraki bütün iletişimler her iki taraf tarafından tetiklenebilir (bağlantı kapatılmadıktan sonra).

Başka bir soru, internet sayfası ile cep telefonundaki uygulama nasıl ilişkilendirildi? Cevap, internet sayfasına kullanıcı müşteri numarası ile giriş yapıyor, cep telefonundaki uygulama da müşteri numarası ile kayıt yaptırılan bir uygulama.

Dolayısıyla, akışı oynatalım.
1. Cep telefonundan uygulama açılınca sunucuyla bir istemci-sunucu bağlantısı kuruldu (istemci=cep telefonu uygulaması, sunucu=banka uygulaması).

2. İnternet sayfasına bilgisayardan giriş yapıldı. Doğru bilgiler girildiyse cep telefonu sunucu uygulamasına “onay-red” mesajı gönderilecek müşteri bilgisi geçildi (aaaa bakın bu durumda internet sayfası, cep telefonu sunucu uygulamasına istemci olarak bağlanmaktadır, web socket mesela). Tahmin edilebileceği üzere cep telefonu sunucu uygulaması hem müşterilerden hem de banka internet sayfasından bağlantı kabul ediyor olmalı.

3. Cep telefonu sunucu uygulaması kullanıcıya mesaj gönderilir.
4. Müşteri “onay” veya “red” der ve bu mesaj cep telefonu sunucu yazılımına gelir.
5. Cep telefonu sunucu yazılımı internet sayfası istemci soketine (web soket demiştik)  durumu (onay veya red) gönderir.
6. Onay ise internet sayfası gösterilir, red ise çıkış yaptırılır.

Sonuç:

Bu yazımızda istemci-sunucu mimarisine giriş yapıldı ve örnekler verildi. İfade edilen iki kısıt yazılımlarımız için temel teşkil etmektedir. Ağ (network) üzerinden çalışacak yazılımlarda mimari detaylı tasarlanmalıdır.

Soket Programlama – 2

Basit bir Sunucu Yapalım

Bu yazımızda basit bir sunucu yazılımı yapacağız. Bunu temel bazı kavramları yeri geldikçe açıklayarak yapalım. Beklentilerinizin aksine malesef bu sunucu multi-threaded değil single threaded olacaktır. Bu yöntemin seçilmesinin sebebi, asıl amaç olan istemci-sunucu kavramına odaklanılması içindir. Multi-threading kavramını başka bir yazımız için planlayıp konumuza dönelim.

Soket (socket) nedir?

Soket aslında iki ucu boşta olan bir kablo olarak düşünülebilir. Bir bağlantının sağlanması için, bu analoji yapılan kablonun bir ucunun istemciye bir ucunun ise sunucuya bağlı olduğunu düşünecek olursak kablonun her iki uçtaki bağlandığı noktaları soket ile temsil edebiliriz. Yani bir laptop güç kablosu için elektrik soketi = sunucu soketi, dizüstü bilgisayarın güç girişi = istemci soketi denilebilir.

Evet iki yeni kavram ortaya çıktı: İstemci soketi ve sunucu soketi. İstemci soketi bağlantıyı başlatan taraf (bu eğitimin 1. kısmını hatırlayınız ltf), sunucu ise bağlantı gelmesini bekleyen taraf.

Bir not:

Soket programlama için hangi programlama dili ile eğitime devam edeceğimiz hususu geldi çattı. Pek çoğumuz C, Java biliyordur. Açıkçası bunlarla eğitimi sürdürmek oldukça makul gibi gelse de python bence bu kavramları öğrenmek için belki daha rahat bir programlama dili olabilir. Ayrıca harika bir IDE olan pycharm bu iş için biçilmiş kaftan. Community edition’ı indirmediyseniz lütfen durmayın indirin, eğer nesne yönelimli programlamaya (object oriented programming) aşina iseniz daha önce python ile hiç çalışmamış olsanız da bu eğitime devam edebilirsiniz. Evet yanlış ifade etmedim, dili bilmiyorsanız da devam edin! Ek olarak buradaki kavramlar C dili için de geçerlidir. Yani mantık aynıdır, burada ifade edilen kavramlar bir iskelet teşkil etmektedir.

Temel sunucu yazılımı bileşenleri

İlk ihtiyaç duyacağımız bir adet soket. Bu soketi sonradan sunucu soket olması için terfi ettireceğiz.

Şimdi soket oluşturalım

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

AF_INET internet adres ailesinin (address family) kullanılacağını, SOCK_STREAM ise bunun bir TCP soket olduğunu ifade ediyor. TCP = paket iletim garantisi, 3’lü el sıkışma, akış kontrolü… Çok kurcalamadan söylemek gerekirse paketlerimiz bu protokol kullanıldığında iletim garantisi altındadır. Bir de UDP var, UDP’de bu garanti yok. Ancak her garantinin bir maliyeti var, ağ tahmin edeceğiniz gibi sınırsız ve bedava değil. Bu sebeple TCP’nin yerine UDP’nin tercih edilebileceği durumlar (örneğin film izlemek, müzik dinlemek = bir iki paket kaybolsa da mühim değil) varken bunun kabul edilemeyeceği durumlarda (bankacılık uygulaması = para) TCP kullanılmalıdır.
Yukarıdaki kod bir adet TCP kullanan ve internet adres ailesinden bir soket ‘s’ tahsis eder.

Bu soketi şimdi terfi ettirelim

 s.bind('', 9000)

Şimdi, bu kod parçası ne yapıyor? Çoğu internet sayfası TCP 80 port numarasından hizmet vermektedir. SSL ise TCP 443. İşte bind metodu (C de metod = fonksiyon deniyor), ‘s’ soketini 9000 portuna bağlamaktadır. Yani istemci soket 9000 numaralı portun kapısını çalacaktır. Boş geçilen ” tek tırnak aç, tek tırnak kapa ise herhangi bir ağ biriminden (interface) bağlantı gelebilir demektir. Normalde sunucuların birden fazla (4-8 belki daha fazla) ağ birimleri (linuxteki eth0, eth1…) olur. Bu birimlerden hangisinden bağlantı kabul edileceğini ilk parametrede ifade edebilirdik. Evet artık soketin bir ucunu sunucu soket olarak terfi ettirdik. bu aşamadan sonra hatırlayacak olursanız bağlantıyı başlatan değil, bağlantı bekleyen (müşteri bekleyen) soket’idi sunucu soket. Artık bağlantıları bekleyebiliriz.

Kapıyı dinlemeye başlayalım

 s.listen(5)

Malum, sunucu soket olmuştuk bind ile. Sunucu sokete aynı anda birden fazla bağlantı gelebilir. Kaç tane gelmesini arzu ediyorsak (kaynaklarla ilgili bir mevzu) bu sayı listen metod paremetresine yazılır (bu durumda 5). Bu fonksiyonu çağırdıktan hemen sonra linux kullanıcıları netstat -tl komutu ile 9000 portundan bağlantı beklendiğini görebilirler.
Şimdi 9000 portuna gelen bir bağlantı (istemci soketin başlattığı bağlantı) aşağıdaki fonksiyon ile kabul edilir. accept metodu kapıyı çalan müşteriye kapıyı açar. Yani accept çağrılmaz ise bağlantı kabul edilmez.

İçeri buyur edelim

 conn, addr = s.accept()

Bu fonksiyon artık iletişim sağlayabileceğimiz bir kanal açar. Evet konu başında analojisini yaptığımız elektrik kablosunun iki ucu bağlandı. Bu hattın sunucu tarafındaki ucu conn nesnesidir. Aslında conn nesnesi tam olarak bir istemci soketidir. Sunucu soketi şöyle düşünebiliriz. Bir giyim mağazasına girdiniz ve patronun 5 tane (yukarıdaki 5 sayısını hatırlayınız) müşteriyle ilgilenecek müşteri temsilcisi var. accept eder etmez müşteriye bir tane müşteri temsilcisi atıyor. Yani sunucu soket accept ile bir tane istemci soket (conn) üretiyor. Artık sunucu soket devreden çıkıyor ve iki istemci soket yani bağlantıyı başlatan ve sunucu soketten üretilen iki soket konuşmaya (veri alış verişine) başlıyor.
Şimdi soketten müşteri temsilcisi konuşşsun

 conn.send('Merhaba, ben sunucudan tureyen istemci soketim!!!')

Evet, send ile veri gönderebilir, recv ile veri alabiliriz.

Bağlantıyı kapatalım

 conn.close()

Bu TCP bağlantısını koparır. Bu durum kablonun çekilmesi gibidir. Karşı taraftaki istemci soketin de bağlantısı kopar. Burda dikkat edilmesi gereken bir mesele var: conn nesnesi bizim istemci ile olan iletişimimizi sağlayan soket nesnesidir. “s” soketi ise sunucu sokettir. Yani “s” soketine yazma/okuma yapılmamalıdır. İstemci ile olan bütün muhabbetimiz “conn” nesnesi ile yapılmalıdır.

Test edelim ve programın bütün halini görelim

Sunucu yazılımının bütün hali aşağıdadır:

 import socket

 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 s.bind(('', 9000))
 s.listen(5)
 conn, addr = s.accept()
 conn.send('Merhaba, ben sunucudan tureyen istemci soketim!!!')
 conn.close()

Linux kullanıcılarına özel:

netcat (nc) programı ile kodumuzu kolaylıkla test edebiliriz. “nc” bir istemci soket ile belirtilen bir porta aşağıdaki gibi bağlanabilir:

$ nc localhost 9000

nc komutunu çalıştırdığınız ekranda

$ Merhaba, ben sunucudan tureyen istemci soketim!!!

yazısını göreceksiniz.

Windows kullanıcılarına özel:

telnet programı netcat benzeri bir kolaylık sunmaktadır. Telnet ile aynı çıktıyı alacaksınız.

Dikkat:

Netcat’i ikinci kez çalıştırdığınızda bağlanamadığınızı farkettiniz muhtemelen. Bunun sebebi bağlantıyı sonsuz döngü içinde kabul etmememiz. Yani program conn.send(), conn.close() dan sonra bitiyor. Yeni bağlantı için sunucu yazılımının bitmemesi gerekiyor. Peki kod nasıl olmalı? Bir sonraki konuda bu konudaki çeşitli tasarım olasılıklarını ve her bir olasılıktaki sıkıntıları tartışmaya başlayacağız.
Sağlıcakla kalınız.

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.