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.