hiçbir makine host ismini kullanmaz. "cclub.ktu.edu.tr" bir host ismidir. 193.140.168.77 bu hostun
sahip olduğu IP adresidir. Host isimlerini IP adreslerine çeviren sistemlere DNS (Domain Name
Server) denir. İşletim sistemi bize DNS işlemlerini yapmak için kütüphane sunmaktadır. IP
dönüşümü yapmak için kütüphane çağrılarından gethostbyname() 'i kullanabiliriz.
#include
struct hostent* host;
host = gethostbyname("cclub.ktu.edu.tr");
Soket Oluşturma:
İki programın haberleşmesi için öncelikle her iki tarafta da soket açılması gerekir.
int socket(int domain, int type, int protocol);
socket() fonksiyonunun ikinci parametresi soketin tipidir. Buraya:
SOCK_STREAM
SOCK_DGRAM
SOCK_RAW
SOCK_SEQPACKET
SOCK_RDM
SOCK_PACKET
gibi soket türlerini yazabiliriz. İlk ikisini yukarıda anlattım. Diğerlerinin ne anlama geldiği şu
aşamada önemli değildir. Yalnızca biz SOCK_STREAM tipini kullandığımız için ikinci parametre
olarak bunu verdiğimizi bilin. Yani bu bir stream sunucu soket uygulamasıdır.
Üçüncü parametresi protocol tipini belirler. Hali hazırda 5 protokol türü vardır :
AF_UNIX (UNIX internal protocols)
AF_INET (ARPA Internet protocols)
AF_ISO (ISO protocols)
AF_NS (Xerox Network Systems protocols)
AF_IMPLINK (IMP "host at IMP" link layer)
Soket ile ilgili tanımlamaların başlık dosyaları ve dir.
Veri Alış-verişi:
Birbiri ile bağlantısı yapılmış iki soket arasında artık veri alış-verişine başlayabiliriz. Buradaki
veriler karşıya taşınması gereken gerçek veriler olabileceği gibi, karşıya yapması gereken işleri
bildiren bir komut listesi olabilir. Her şey, iki program arasında tarafınızdan tanımlanmış protokol
çerçevesinde olacaktır.
write() ve read() fonksiyonları aynı zamanda dosya işlemleri için de kullanılır. send() ve recv() ise
bu fonksiyonların soketler için özelliştirilmiş halidir.
int send(int s, const void *msg, int len, unsigned int flags);
int recv(int s, const void *msg, int len, unsigned int flags);
4. Sunucu Soket Programı
4.1. Sistem Çağrılarının Kullanımı
Bu kadar teorik bilgiden sonra bir uygulama yapmaya geçebiliriz. 2222 numaralı portta çalışan basit
bir sunucu programı üzerinde olayları inceleyelim. Bundan sonra üzerinde çalışacağımız kodlar
sunucu için yazılmaktadır. İstemci program olarak standart telnet programını kullanacağız. Mimari
olarak sunucu yazılımların, istemci yazılımlardan farklı olduğunu unutmayın. Aşağıdaki kodları gcc
C derleyicisi kullanarak yazdım. Ancak bu kodlar tamamen standart olup Windows altındaki Visual
C derleyicisi ile de çalışabilir. Değişik platformlarda sorun yatmasın diye kod içerisinde Türkçe
karakter kullanmadım.
Önce port numaramızı belirleyelim:
#define PORT 2222
Sıra soket oluşturmada:
int sockfd;
sockfd=socket(AF_INET,SOCK_STREAM,0);
if (sockfd<0) {
perror("socket");
exit(1);
}
Burada verdiğim kodlar tek başına çalışmaz. Daha kolay anlaşılsın diye programdan parçalar bir
araya getiriyorum. En sonunda çalışabilir kodun tamamını vereceğim. Bu işlemlere başlamadan
önce bazı header (.h) dosyalarının yüklenmesi gerekir. Bunları da kodun tamamında görebilirsiniz.
Yukarıdaki kodu incelersek: Soket oluşturma işlemini socket() fonksiyonu ile yapıyoruz.
socket() fonksiyonu geriye bir tamsayı değer döndürür. Hata oluşumunda bu değer -1'dir. Eğer hata
oluşmazsa geri dönen değer soketin tanımlayıcı numarasıdır. Bu numara en başta bahsettiğimiz gibi
dosya tanımlayıcısıdır. Bu numarayı kodda görüldüğü gibi sockfd değişkenine atatım. Bu işlemden
sonra soketi takip etmek için artık sockfd değişkenini kullanacağım. Oluşturulan her soket bu
şekilde ayrı bir numara alır.
Hata kontrolü yapmaya özen gösterin. Aksi halde zamanızın büyük bir kısmını programdaki hataları
ayıklamak ile geçirirsiniz.
Şimdi port numarasının atamasını yapalım:
if(bind(sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr)) ==
-1) {
perror("bind");
exit(1);
}
bind() fonksiyonu soketi isimlendirir. socket() ile oluşturulmuş bir soket için isim uzayında
(bellekte) vardır ancak bir isme sahip değildir. Bind işlemi belirtilen port numarasını ve gerekli
sistem kaynaklarını işletim sisteminden ister. Eğer bu işlemi yapmazsanız işletim sistemi port
havuzundan herhangi bir port atar. Port numarasını belirlemezseniz, diğer insanlar sizin programla
haberleşecek programlar yazamaz. bind() fonksiyonu aynı zamanda sizi bir veya daha fazla ağ
arabirim kartından (Network Interface Card) yalıtır. Ağ üzerindeki her host bir NIC'a sahiptir ve her
NIC en az bir IP adresine sahiptir. addr.sin_addr değeri olarak INADDR_ANY yazmakla işletim
sistemine host üzerinde bulunan mümkün bütün IP adreslerinden bağlantı kabul edeceğinizi
söylüyorsunuz. Bunu istemiyorsanız makinenin sahip olduğu IP numaralarından birini, hizmet
vermek üzere seçebilirsiniz. Eğer üzerinde bulunduğunuz makine bir firewall ise belirlenmiş bir
IP'yi kullanmak yararınıza olacaktır. Bu şekilde firewall'un yalnızca bir yüzü hizmet sunacaktır.
if (listen(sockfd, 5) == -1) {
perror("listen");
exit(1);
}
bind() işleminden sonra sunucumuz artık istemcileri beklemeye başlayabilir. İstemcilerin kabul
edilebilmesi için sunucunun öncelikle dinlemeye geçmesi gerekir. Bunun için listen() fonksiyonunu
kullanır. Bu çağrı, soketin özelliklerinde bazı değişiklikler yapar. Örneğin soketi dinleme moduna
geçer ve bu soketi veri taşıması için kullanamazsınız. listen(), çağrıldıktan sonra parametre olarak
belirttiğiniz kadar bir bekleme kuyruğu oluşturur. Yukarıda parametre olarak 5 verdik. Bunun
anlamı: Sunucu mevcut bağlantıya hizmet verirken gelecek ilk 5 istek bekleme kuyruğuna
konulacak. Sunucu sırayla bunlara hizmet verecek.
Artık sunucu bağlantı kabul etmek için soketi dinlemeye almıştır. Herhangi bir istemcinin bağlantı
isteğini accept() çağrısı ile yakalayacağız. Kabül fonksiyonu programı G/Ç bekleme durumuna
sokar. Dolaysıyla programımız işletim sistemi tarafından bloke edilir ve bağlantı isteği geldiğinde
tekrar uyandırılır.
int accept(int s, struct sockaddr *addr, int *addrlen);
Kabül işlemi, gelen isteği yeni bir soket ile karşılar. Çünkü mevcut soketimiz dinleme
durumundadır ve veri taşıma işlemi gerçekleştiremez.
Şimdi programımızın tamamlandığını ve cclub.ktu.edu.tr hostunda çalıştığını varsayalım.
cclub:~> telnet cclub.ktu.edu.tr 2222
Geçici olarak telnet programını istemci olarak kullandım. "telnet" i bir echo istemcisi gibi düşünün.
Sunucu ne gönderirse onu ekrana basar. 2222 portuna bağlantı isteği gönderdiğimizde sunucu
programımız bize yeni bir soket açıp bağlantımızı kabul edecek. Telnet ile sunucumuzun
konuşmaması için hiçbir neden yok.
İstemcilerin adres bilgileri, bağlantı kabülü sırasında alınır. accept() fonksiyonun son parametresi
bir girdi değil çıktıdır. Bağlantı sırasında bu yapı doldurulup programa döndürülür. Biz programa
dönen adres bilgilerini kullanarak programlarımıza biraz renk katabiliriz. Örneğin, istemcinin
adresini alıp bir yasak dosyasında var mı diye kontrol ederiz. Eğer varsa bağlantıyı red ederiz.
Bağlantıları kısıtlayan /etc/hosts.deny ve serbestlik tanıyan /etc/hosts.allow dosyalarını hatırlayın.
Sanki bir şeyler hatırlar gibi olduk değil mi?
int fd, client_size;
struct sockaddr_in client_addr;
client_size = sizeof(struct sockaddr_in);
fd = accept(sockfd, (struct sockaddr *)&client_addr, &client_size));
printf("Merhaba %s",inet_ntoa(client_addr.sin_addr));
Sunucumuza bir bağlantı yapıldığında sunucu ekrana "Merhaba 193.140.168.54" gibi bir şey
yazacak. Gördüğünüz gibi bağlantı yapanın adresini de aldık.