Socket Programlama

Güven KÖKDAMAR (guven.kokdamar@students.comu.edu.tr)
Çanakkale Onsekiz Mart Üniversitesi,
Bilgisayar Mühendisliği Bölümü


06/10/2003    v.0.1  Bu belgede, socket programlamanın mantığı ve temel socket programlama sistem çağrıları anlatılmıştır.


İçindekiler


     

        1. Giriş
            1.1. TCP/IP Hakkında Genel Bilgiler
        2. Transmission Control Protokol (TCP)
        3. Port ve Soket Kavramları
        4. User Datagram Protokol (UDP)
        5. Socket Programlama
            5.1. Socket Programlama Nedir?
            5.2. Socket Türleri
            5.3. Uygulamadaki Temel Prensipler
            5.4. Socket Programlamada Kullanılan Veri Yapıları ve Veri Türleri
            5.5. Veri Türlerinin Dönüşümleri
            5.6. Socket Programlama Sistem Çağrıları
                   5.6.1. socket();
                   5.6.2. bind();
                   5.6.3. connect();
                   5.6.4. listen();
                   5.6.5. accept();
                   5.6.6. send(); ve recv();
                   5.6.7. sendto(); ve recvfrom();
                  
5.6.8. close(); ve shutdown();
                   5.6.9. getpeername();
                   5.6.10. gethostname();
                   5.6.11. gethostbyname();
       
           5.6.12. select();
            5.7. send() Sistem Çağrısı Sorunları
            5.8. RAW Socket ler
        6. Örnek Program Kodu
            
6.1.Örnek Sunucu Programı
            
6.2.Örnek İstemci Programı
        7.
Kaynaklar

        8. Yasal Açıklamalar

 


1. Giriş


1.1. TCP – IP HAKKINDA GENEL BİLGİLER

          TCP-IP (Transmission Control Protocol - Internet Protokol), internette, bilgisayarlar arası iletişimi sağlayan protokol grubudur. Bilgisayarlar arasında bilgi alışverişi IP paketleri aracılığıyla yapılır. Bilgisayar A, bilgisayar B ye göndermek istediği veriyi paketler halinde gönderir. Bu paketler içerisinde A' nın ve B' nin adresleri vardır. A'nın gönderdiği paketlere IP-Paketleri, A'nın adresine IP-Adresi denir. Gönderilen paketler, B'nin adresini taşıdığı için bilgisayar ağları arasında kullanılan Router lar aracılıyla doğru bilgisayara iletilir.

           IP paketleri birbirinden bağımsız olarak gönderilir. B' nin görevi tüm IP paketlerini alıp, A' nin gönderdiği veriyi kazanmaktır. Gönderilen bazı IP paketleri B'ye ulaşmayabilir. Her paketin içinde, gönderilecek paket sayısı hakkında bilgi olduğu için, B, kaç tane IP paketi alması gerektiğini bilir. Eksik olan paketler A'dan tekrar istenir. TCP nin görevi, gönderilen IP paketlerini denetlemektir. TCP eksik olan paketleri tespit eder ve A tarafından tekrar gönderilmesi için gerekli işlemleri yapar.

            Subnet tiplerini ve subnet içinde yer alacak bilgisayar miktarını tespit etmek amacıyla IP-numaraları A, B, C, D, E sınıflarına ayrilir. Burada A, B, C sınıfları incelenmiştir. Sınıflar arasında ayrım ilk 8 bit (byte) üzerinde yapılır. Sınıfların hangi adres alanlarına sahip olduklarını aşağıdaki tabloda yer alıyor.

Sinif

Sistem

1. byte

2. byte

3. byte

4. byte

A

desimal
dual

0-127
0xxx xxxx

0-255
xxxx xxxx

0-255
xxxx xxxx

0-255
xxxx xxxx

B

 

128-191
10xx xxxx

0-255
xxxx xxxx

0-255
xxxx xxxx

0-255
xxxx xxxx

C

 

192-223
110x xxxx

0-255
xxxx xxxx

0-255
xxxx xxxx

0-255
xxxx xxxx

Sinif A: 1.0.0.0 - 127.0.0.0
Net-id: İlk byte 0-127 rakamlarını verecek şekilde seçilir: 0000 0000 - 0111 1111
Host-id: Geri kalan 3 byte (24bit = 2^24 = 16777216) bilgisayar Host-id olarak kullanılır.
A sınıf subnet sayısı: 1. byte tan 7 bit, 2^7= 128

          A sınıfından subnetler teorik olarak 16777216 adet bilgisayar barındırabilirler. Bu kadar sayıda bilgisayarı bir subnet içinde barındırmak mümkün değildir. Ayrıca dünya çapında sadece 127 adet A sınıfı IP-numaraları verilebilir, daha doğrusu verildi ve A sınıfı IP-numaraları artık verilmiyor.

Sinif B: 128.0.0.0 - 191.255.0.0
Net-id:
İlk iki byte 128.0-191.255 rakamlarını verecek şekilde seçilir: 1000 0000.0000 0000 - 1011 1111.1111 1111
Host-id: Geri kalan 2 byte (16bit = 2^16 = 65536) bilgisayar Host-id olarak kullanılır.
B sınıf subnet sayısı: 1.byte tan 6, 2. byte tan 8, 2^14=16384

Sinif C: 192.0.0.0 - 223.255.255.0
Net-id: İlk üç byte 192.0.0-223.255.255 rakamlarını verecek şekilde seçilir: 1100 0000.0000 0000.0000 0000 - 1101 1111.1111 1111.1111 1111
Host-id: Geri kalan 1 byte (8bit = 256) bilgisayar Host-id olarak kullanılır.
C sınıf subnet sayısı: 1.byte tan 5, 2. byte tan 8, üçüncü byte tan 8, 2^21=2.097.152

           Aritmetiksel olarak dünya çapında 128 + 16384 + 2.097.152 = 2.113.664 adet subnet oluşturulabilir. Bu subnetler kendi aralarında tekrar subnetlere ayrılabilir. Bu rakam cok büyük görünse de, aslında internetin son zamanlarda çok hızlı gelişmesinden dolayı, bos net-id kalmamıştır. Bu sebepten dolayı 1995 yılından beri IPv6 isminde bir adresleme üzerinde çalışılmaktadır. IPv6 16 byten oluşur.

16 byte = 128 bit = 2^128 = 3.402823669209e+38 . IPv6 su anda kullanılan IPv4 standartına uygun şekilde tasarlanmaktadır. Bu yüzden iki standartı paralel çalıstırmak mümkün hale gelecek. Belli bir zaman sonra, tamamiyle IPv6 sistemine gecilmis olacak.

 


2.  Aktarım Kontrol  Protokolü [Transmission Control Protokol (TCP)]


TCP, IP ’nin bir üst katmanında çalışan iki aktarım katmanı protokolünden birisidir.

TCP, güvenilir ve sanal devre üzerinden çalışan bir protokoldür. Aynı ağ üzerinde ya da farklı ağlar üzerinde iki hostun birbirleriyle güvenilir bir şekilde haberleşmesini sağlar.

TCP ‘nin başlıca özellikleri şunlardır:

TCP istemci host üzerinde çalışan bazı uygulama katmanı protokolleri:                                           

TCP, IP ‘den hizmet alan bir protokoldür. IP, TCP ‘nin sağladığı olanaklarla güvenli veri dağıtımı yapar. IP ‘nin TCP ‘ye sağladığı ana hizmetler şunlardır.

                                               Bir TCP Paketi Örneği

TCP ‘nin Temel İşlevleri

TCP ‘nin üzerinde çalıştığı farklı hostlar arasında güvenilirliğe sahip, bağlantı tabanlı paket dağıtımını sağlar.

TCP, internet ortamında şu işlevleri sağlar:

 

Temel Veri Transferi (Basic Data Transfer):

Haberleşen TCP hostlar üzerinde bu katmanlar arası segment aktarımı yoluyla haberleşme sağlanır. TCP veri akışını baytları sıralandırıp segment grupları halinde iletir. Eğer bir parçalama gerekliliği ortaya çıkmadıysa her segment bir IP paketine konarak iletilir.

Güvenilirlik (Reliability):

TCP, zarara uğramış, bozulmuş, ikilenmiş verinin doğru olarak elde edilmesinden sorumludur.

TCP, her bir bayt ’a sıra numarası verir. Daha sonra ilettiği bu bayt ’lara karşılık onay (acknowledge) bekler. Eğer belirli aralıklarla beklediği onayları alamazsa onay alamadığı kısımları yeniden hedef hosta iletir. Hedef host sıra numaralarına göre segmentleri sıralarken aynı segment numarasına sahip iki segmentle karşılaşabilir. Her bir segment checksum denilen kontrol bilgileri içerir. Bu kontrol bilgilerine göre hasara uğramış segmentler anlaşılır ve atılır. Kaynak hosta onay gönderilmezse kaynak hosttaki TCP onay alamadığı segmentleri yeniden gönderir.

Uçtan Uca Akış Kontrolü (End to end flow control):

Kaynak istasyondan hedefe iletilen veri miktarını kontrol eden bir mekanizmadır. Bu mekanizma pencere yönetimi (window management) tekniği ile sağlanır.

Çoğullama (Multiplexing):

TCP üzerinde birden fazla eş zamanlı TCP bağlantı kurmakta kullanılır.

Bağlantılar (Connections):

Bağlantı iki TCP hostun veri transferi için birbirlerinden haberdar olmaları ve gerekli hazırlıkları tamamlamış olmaları demektir. Bağlantı kurulumu için kaynak host hedef hosta bağlantı kurmak istediğini bildirir. Hedef host gelen istekteki port numarasına karşılık gelen hizmeti sağlıyorsa ve bağlantı için hazırsa bir cevap mesajı göndererek bağlantı işlemini başlatır. Bağlantı, kaynak ve hedef host arasında sanal bir devre oluşturulmasıyla kurulur. Bağlantı kurulup transfer gerçekleştirildikten sonra bağlantı koparılır.

Haberleşme sırasında, her bir host üzerindeki TCP protokolü, sürekli hata kontrolü yaparlar. Eğer herhangi bir nedenle bir iletişim problemi ortaya çıkarsa, her iki uçtaki TCP yürütücüsü bu problemden haberdar olur ve üst katman protokollerini uyarırlar.

Bağlantının kurulmasında TCP paketinin  başlığı içindeki SYN (Sync) ve ACK (onay) bitlerini kullanılır.

                                    TCP Yeniden-İletim Şeması

Bağlantı kurulumunda bağlantı talebinde bulunan ve bağlantıyı kabul eden hostlar arasında iki farklı durum oluşur.

Bağlantıyı kabul eden host pasif, bağlantı talebinde bulunan host aktif olur.

Tam çift yönlü işlem (Full Duplex Process):

TCP işlemleri hem gönderim hem de alımın aynı anda yapılmasına olanak tanır. Bu da haberleşmenin daha hızlı olmasını sağlar.

Sıra Numarası (Sequence Number)

TCP üst katmandan aldığı veriyi segmentlere böler. Bu segmentlerin herbiri genelde tek bir IP paketi içinde taşınır. Her bir segmente TCP bir numara verir. Amaç ağlar üzerinde dolaşan bu segmentlerin hedefe varış sıralarının karışması durumunda hedef hostta çalışan TCP yürütücüsünün bunları tekrar sıraya koyup üst katmana aktarmasıdır. (segment boyları sabit değildir)

TCP, karşı TCP ile bağlantıyı ilk kurduğunda, ilk gönderdiği segmente bir numara verir ve daha sonraki segment numaraları sıralı olarak artan şekilde devam ettirir. Sıra numarası 0 – 231 arasında olabilmektedir. Çünkü TCP paketi veri yapısında sıra numarası 4 byte‘lık bir alanla ifade edilir

TCP verideki bayt ’ları gruplayarak segmentleri oluşturur ve herbir segment ayrı bir numara ile numaralandırılır. Bir segment bir numara aldığında bu segment numarasını içinde barındırdığı ilk bayt ’a verir. Yine içinde barındırdığı diğer bayt ‘lara bu numaranın artanlarını verir. Bu segmentten sonra gelen segmentin alacağı numara, bir önceki segmentin içindeki en son bayt ‘ın aldığı numaranın bir fazlası olacaktır. Bu sıra numaraları TCP başlığı içinde taşınır.


3. Port ve Soket Kavramları


      Host cihazındaki bir TCP üst-katman kullanıcısı bir port numarası ile tanımlanır. Port değeri IP internet adresi ile birleşerek bir soket oluşturur. Bu değer internet boyunca tek olmalıdır. Bir soket çifti her bir uç-nokta bağlantısını tek olarak tanımlar. örneğin,

Gönderici Soket = Kaynak IP Adresi + Kaynak Port Numarası numarası
Alıcı soket = Hedef IP Adresi + Hedef Port Numarası
         Bir bilgisayarda birden çok soket bulunabilir. Örneğin aynı anda hem telnet socketi hem de ftp soketi açık olabilmektedir. Socketleri birbirinden ayırmak ve istemci-sunucu ikilisini birbiri ile buluşturmak için her soket programın PORT numarası vardır. Örneğin ftp'nin port numarası 21'dir. Bir ftp istemci, ftp sunucunun 21. portta çalıştığını bildiğinden direk onunla temasa geçer. Telnet 23.'u portta çalıştığından, telnet sunucu ile ftp sunucu karışmaz. 1-1024 arasındaki portlar standarttır ve yalnız root tarafından kullanılabilir. Eğer normal user 1024'ten küçük portları kullanmaya kalkarsa 'bind' permission hatası verir. Bu nedenle sıradan kullanıcılar soket programları için 1023'ten büyük bir port numarası seçerler.


4. User Datagram Protokol (UDP)


       UDP, TCP / IP protokol grubunun iki aktarım katmanı protokolünden birisidir.

       UDP, onay (acknowledge) gönderip alacak mekanizmalara sahip değildir. Bu yüzden veri iletiminde başarıyı garantileyemez. Yani güvenilir bir aktarım servisi sağlamaz. Hedefe ulaşan paketler üzerinde sıralama yapıp doğru veri aktarımını da sağlayacak mekanizmalara sahip değildir. Uygulamalar güvenli ve sıralı paket dağıtımı gerektiriyorsa UDP yerine TCP protokolü tercih edilmelidir.

       UDP, minimum protokol yükü (overhead) ile uygulama programları arasında TCP ye göre basit ama daha hızlı bir aktarım servisi sağlar.

Port numaralarıyla Demultiplexing :

       IP, UDP port numaraları aracılığıyla pek çok datagram paketi arasında ayrıştırma yapabilecek mekanizmaları içerir. Bu yolla bir çok uygulamanın alıcı host üzerinde aynı anda çalışmasına ve ağ üzerinden haberleşmesine olanak tanır.

        UDP paketleri de internet üzerinde IP paketleri içinde taşınır:

UDP Paket Formatı:

Kaynak Port

Hedef Port

1

2

3

4

5 6 7 8 9 10 11 12 13 14 15 16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

Uzunluk

Hata Kontrolü

1

2

3

4

5 6 7 8 9 10 11 12 13 14 15 16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

Veri

Kaynak Port (Source Port):  Opsiyonel bir alandır. Gönderilen işlemin portunu gösterir. Eğer gönderen host bir kaynak numarasına sahip değilse bu alan “0” ile doludur.

Hedef Port (Destination Port): Hedef host içerisinde, işlemlere uygun ayrımları yapmak için kullanılır.

Uzunluk (Length): UDP veri ve UDP başlığının bayt cinsinden toplam uzunluğudur.

 Hata Kontrolü (Checksum):  Opsiyonel bir alandır. Hata kontrol mekanizması sağlar. Eğer hata kontrolü yapılmayacaksa bu alan “0” ile doludur.

        UDP ‘deki hata kontrolü sadece hatasız taşımanın bir ölçüsüdür. Yeniden gönderim veya güvenilirlik sağlamaz.

       UDP ' nin bu kadar dez avantajına rağmen daha çok kullanıldığı açıktır. TCP bir veri karşıya 6x32+Veri boyu kadar bir paket olarak gitmektedir. Yani her paket fazladan 192 bit başlık (header) bilgisi taşımaktadır. Oysa UDP paketleri 64 bitlik başlık (header) bilgisine sahiptir.

       UDP kullanmanın en önemli nedeni az protokol yüküdür. Video sunucu gibi gerçek zamanlı veri akışı gerektiren bir uygulama için TCP fazla yük getirir ve görüntü gerçek zamanlı(realtime) oynamaz. Bu nedenle multicast uygulamalarında Datagram soketler kullanılır. Ayrıca video ve ses görüntülerinde genelde az bir veri kaybı sesi veya görüntüyü bozmaz. Bu nedenle sıkı paket kontrolune gerek yoktur. Eğer iyi bir fiziksel bağlantınız varsa hata oranı düşük olacaktır ve bu nedenle TCP'nin yaptığı hatalı paket kontrol işlemleri fazladan yük olacaktir.

        UDP her ne kadar paket güvenliğini denetlemese de bunu yazılımcı yapabilir. Örneğin TCP bir paketi gönderdiğinde karşı tarafın onu aldığını anlamak için acknowledgement(onay,kabul)  bekler. UDP bunu yapmaz. Fakat bunu soket yazılımcısı yapabilir. Yazılımcı, gönderilen her pakete bir cevap bekleyerek bunu sağlar.
 


5. Socket Programlama


5.1.Socket Programlama Nedir?

               Soketler, bir tür süreçler arası haberleşme(interprocessing) yöntemidir. Soket, soyut bir tanımla haberleşme uç noktalarıdır. Pratik olarak soketler dosyalara benzer. Soketten okumak ile dosyadan okumak arasında hiçbir fark yoktur. Sıradan bir programda dosya açmak istediğiniz zaman, çekirdeğin (kernel) sistem çağrıları kullanılarak, programınıza tam sayı bir değer verilir. Bu değer dosyanızı yönetmek için kullanacağınız dosya tanımlayıcısıdır (file descriptor). POSIX (Linux, BSD gibi UNIX benzeri) sistemlerde  hemen her şey bir dosyadır. /dev dizini altında cihazlar yer alır. Her cihaz için major ve minor numarası ile tanımlanan özel dosyalar bulunur. Bunlar Block Device ve Character Device diye iki kısma sınıflandırılmıştır. Terminaliniz, konsolonuz, FIFO ve pipe gibi veri yapıları; hepsi kullanıcı için birer dosyadır. Bu nedenle soketlere dosya gözüyle bakabilir ve yazmak için write(), okumak için ise read() gibi dosya fonksiyonlarını kullanabilirsiniz. Ancak sokete özel fonksiyonları kullanmak daha kullanışlı olacaktır.

Aklınıza gelebilecek hemen her internet programı socket program olarak çalışır. Örneğin web sunucuları 80, telnet sunucuları 23 numaralı port üzerinde çalışan soket programlardır.

Soket programlamaya özel bazı terimler:

İstemci (Client): Hizmet isteyen soket programlara denir. İstediği zaman sunucuya (server) bağlanır, görev verir ve sonuçları alıp bağlantıyı koparır.

Sunucu (Server): Hizmet veren soket programdır. İstemci herhangi bir anda kendisine bağlanıp, ondan hizmet isteyebileceğinden sürekli çalışmak zorundadır. Linux'teki in.telnetd, wu.ftpd, nfsd, httpd gibi daemon olarak adlandırılan tüm ağ programları socket sunuculardır.


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


 

5.2. Socket Türleri

          Çeşitli soket türleri vardır. Bunlardan yalnızca en çok kullanılan üçü burada anlatılacaktır. Bunlar "Stream Soketler" , "Datagram Soketler" ve "Raw Soketler"  dir. Bunlar programlarda sırasıyla SOCK_STREAM , SOCK_DGRAM ve SOCK_RAW isimleri ile kullanılır. Bu anlatımda öncelikle  SOCK_STREAM ve SOCK_DGRAM  anlatılacaktır. SOCK_RAW ise daha sonra ele alınacaktır.

         Programda socket() açarken türü belirtilir. Stream soketlere bağlantı yönelimli (connection oriented) soketler, Datagram soketlere ise bağlantısız (connectionless) soketler denir.

        Stream soketler, TCP/IP protokolunun taşıma katmanında bulunan TCP'yi (Transmission Control Protocol) kullanır. Datagram soketler ise yine aynı katmandaki UDP'yi (User Datagram Protokol) kullanır.

        Bu iki türün özellikleri ve aralarındaki temel farkları şöyle sıralayabiliriz:

1. Stream soketler verileri sıralı gönderir, datagram soketleri sıralı göndermeyebilir.

2. Stream soketler veri bütünlüğünü garanti eder , Datagram soketler veri bütünlüğünü garanti etmez. (TCP bir paketi gönderdiği zaman, karşı taraf paketi aldığını haber vermeden, kendini  o paketi göndermiş saymaz ve tekrar gönderir. Ayrıca paketin doğru gidip gitmediğini anlamak için başlık bilgisinde checksum kontrol bilgisi tutar. UDP'de checksum kontrol bilgisi tutar ancak checksum yanlışsa aynı paketi tekrar istemez.)

3. Stream soketler, işlem bitene kadar kesintisiz bir bağlantı kurar. Datagram soketler ise bağlantı kurmaz. Sadece veri göndereceği zaman bağlantı kurar ve işi bitince bağlantıyı koparır.

 

     Örneğin 'telnet' programı Stream soket, 'tftp' programı ise Datagram sokettir.

 

Protokollerin Veriyi Paketlemesi

            Sistem çağrılarına geçmeden önce bir soket programlama örneğinde ki temel prensipler aşağıda verilmiştir.

 

5.3. Uygulamadaki Temel Prensipler

               Soketler her zaman iki uca sahiptir: Alıcı ve gönderici. Bütün mesajlar ve protokol gereği olan başlıklar nihayetinde fiziksel katmandan, mantıksal 1 ve 0'a karşılık gelen elektriksel sinyaller olarak gönderilir.

              Soket program ya istemci, yada sunucudur. Programlanmaları daha karışık olmakla beraber bazı soket programlar her iki görevi de yapmaktadır. Sunucu program ile istemci program arasında çalışma olarak bazı farklar vardır. Aşağıdaki tablo her iki tarafta olayların nasıl gittiğini göstermektedir:

İstemci

Sunucu

Soket oluştur socket()

Soket oluştur socket()

Sunucu adres bilgilerini yerleştir struct sockaddr_in server_addr

Soketi adres ve isim bilgileri ile ilişkilendir bind()

Soketi dinleme moduna geç listen()

Bağlantı yap connect()

Bağlantıyı kabul et accept()

Veri gönder send()

Veri al recv()

Veri al recv()

Veri gönder send()

...
...         Diğer işlemler
...

...
...         Diğer işlemler
...

Soketi kapat close()

Soketi kapat close()

 

 

5.4. Socket programlamada kullanılan veri yapıları ve veri türleri

 

           Burada da aşağıdakini bir ön bilgi olarak verebiliriz.

      

           İki tür byte sıralaması vardır: en önemli baytın (oktet) önce geldiği sıralama veya en önemli baytın sonra geldiği sıralama. Bu sıralamalardan birincisine "Ağ Bayt Sıralaması" (Network Byte Order) denir. Bazı makinalar içsel olarak veriyi kendi belleklerinde bu şekilde depolar, bazıları ise bu sırayı dikkate almaz .Bir şeyin Ağ Bayt Sıralaması'na göre sıralanması gerektiğini söylendiğinde htons() gibi bir işlev çağırılır ("Konak Bayt Sıralaması"ndan [Host Byte Order] "Ağ Bayt Sıralaması"na dönüştürebilmek için). Eğer "Ağ Bayt Sıralaması"ndan bahsediliyorsa o zaman ilgili veriyi olduğu gibi yani "Konak Bayt Sıralaması" düzeninde bırakılır.

 

 

struct sockaddr {

    unsigned short    sa_family;      // adres ailesi, AF_xxx

    char                     sa_data[14];  // protokol adresinin 14 byte'ı

};

 

 

 

 

 

struct sockaddr : Bu veri yapısı pek çok türde soket için soket adres bilgisini barındırır

 

sa_family pek çok değerden birini alabilir. Ama bu anlatımda sadece AF_INET (ARPA Internet protocols) 
kullanılacaktı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)

 

sa_data  ise soketle ilgili hedef adres ve port numarası bilgilerini barındırır.

struct sockaddr ile başa çıkabilmek için buna paralel bir yapı olarak struct sockaddr_in tasarlanmıştır.

struct sockaddr_in {

    short int          sin_family;  // Adres ailesi

    unsigned short int sin_port;    // Port numarası

    struct in_addr     sin_addr;    // Internet adresi

    unsigned char      sin_zero[8]; // struct sockaddr ile aynı boyda

};

 

 

 

 

 

 

 

        Bu yapı soket adresi elemanlarına erişmeyi kolaylaştırır. Buradaki : sin_zero,[68] memset() işlevi kullanılarak tamamen sıfır ile doldurulmalıdır. sin_family değişkeninin de struct sockaddr yapısındaki sa_family değişkenine karşılık gelir ve bizim örneğimizde "AF_INET" olarak ayarlanmalıdır. Son olarak sin_port ve sin_addr değişkenlerinin de Ağ Byte Sırasında bulunmaları gerekir.

 

// Internet adresi

struct in_addr {

    unsigned long s_addr; // 32-bit yani 4 bytes

};

 

 

 

 

 

     Yukarıda belirttiklerimiz zamanı geldikçe kullanılacak yapılardır.

 

5.5. Veri türlerinin dönüşümleri

          Dönüştürebileceğimiz iki tür vardır: short (iki byte) ve long (dört byte). Bu işlevler unsigned ve varyasyonları üzerinde çalışır. Örneğin bir short değişkenin bayt düzenini Konak Bayt Sıralamasından Ağ Bayt Sıralamasına çevirmek istiyorsak. "h"  ("host"),  "to" , "n" ("network") , "s" ("short"): h-to-n-s, veya htons() ("Host to Network Short" ).

"n", "h", "s" ve "l" ile her türlü birleşimi oluşturulabilir. Fakat  stolh() ("Short to Long Host") gibi bir işlev yoktur. Fakat şu işlevler vardır:

·  htons() -- "Host to Network Short" -- konaktan ağa short

·  htonl() -- "Host to Network Long" -- konaktan ağa long

·  ntohs() -- "Network to Host Short" -- ağdan konağa short

·  ntohl() -- "Network to Host Long" -- ağdan konağa long

            struct sockaddr_in yapısı içindeki sin_port ve sin_addr Ağ Bayt Sıralamasında olmak zorunda iken sin_family böyle bir özelliğe sahip olmak zorunda olmamasının nedeni  sin_addr ve sin_port sırası ile IP ve UDP katmanlarında paketlenirler. Bu yüzden Ağ Bayt Sıralamasında gönderilmeleri gerekir oysa ki sin_family sadece işletim sistemi çekirdeği tarafından veri yapısının barındırdığı adresin türünü tespit etmek için kullanılır. Bu yüzden de bu alanın Konak Bayt Sıralamasında bırakılması gerekir. sin_family ağ üzerinden bir yere yollanmadığı için Konak Bayt Sıralamasında olabilir.

inet_addr() : İp adreslerinin değişkenler içlerine koyabilmek için IP adresini sayılardan ve noktalardan oluşan bir biçemden alıp unsigned long türünde bir sayıya çevirir

 örneğin bir struct sockaddr_in türünde deneme değişkeni ve bir de “10.11.12.13” şeklinde bir IP adresi var. Bu adresi bu değişken içine yerleştirmek için kullanılır.

deneme.sin_addr.s_addr = inet_addr("10.11.12.13") ;

inet_addr() döndürdüğü değeri Ağ Bayt Sıralamasına göre dizilmiş olarak döndürdüğü için  htonl() işlevini çağırmaya gerek kalmaz.

 

5.6. Socket Programlama Sistem Çağrıları

 

5.6.1. socket() sistem çağrısı:

 

int socket(int domain, int type, int protocol);

 

 

           Bizim örneklerimizde domain argümanına AF_INET değeri verilmeli, tıpkı struct sockaddr_in yapısında olduğu gibi. Sonra type argümanı çekirdeğe ne tür bir soket söz konusu olduğunu söyler  SOCK_STREAM veya SOCK_DGRAM . Son olarak da protocol argümanına "0" değeri verelir ki socket(), type değişkenine karşılık gelen uygun protokolü seçebilsin.

         socket() işlevi  bir soket tanımlayıcı döndürür ve bu daha sonraki işlevlerde parametre olarak kullanılır. Eğer bir hata oluşursa işlev -1 değerini döndürür. Bu durumda errno isimli global değişken hata kodunu tutar.

 

 

5.6.2. bind() sistem çağrısı:

 

int bind(int sockfd, struct sockaddr *my_addr, int addrlen);

 

 

           Bir soket edindikten sonra bu, makinadaki bir "port" ile ilişkilendirilir. Eğer tek yapacağınız bir yere connect() ile bağlanmaksa o zaman buna gerek yoktur.  sockfd değeri, socket() tarafından döndürülen soket dosya tanımlayıcısıdır. my_addr değişkeni struct sockaddr türünde bir veriye işaret eder ve bu yapı da adresinizi yani port numaranızı ve IP adresinizi barındırır. addrlen'in değeri sizeof(struct sockaddr) olarak verilebilir.

my_addr.sin_port Ağ Byte Sıralamasında, ve tabii my_addr.sin_addr.s_addr de öyle.

 

my_addr.sin_port = htons(0); // kullanılmayan herhangi bir port'u seç

my_addr.sin_addr.s_addr = htonl(INADDR_ANY);  // IP adresimi kullan

 

 

 

          my_addr.sin_port değişkeninin değerini sıfır yaparak bind() işlevi uygun olan bir port sayısını bizim için seçer. Benzer şekilde my_addr.sin_addr.s_addr değişkeninin değerini INADDR_ANY yaparak üzerinde çalıştığı makinanın IP adresini almasını söylemiş oluruz.

          bind() eğer bir hata çıkarsa -1 değerini döndürür ve errno isimli hata kodu değişkenine gerekli sayıyı yerleştir. bind() işlevini çağırırken dikkat edilmesi gereken bir başka şey de şudur , port numarası olarak 1024'ün altındaki bir değer seçilmemeli. Çünkü 1024 ten küçük tüm portlar REZERVE edilmiştir . Bu sayıdan başlayarak 65535'e kadar olan sayılardan birini, seçtiğiniz numara başka bir program tarafından kullanılmıyorsa port numarası olarak kullanabilirsiniz.

 

5.6.3. connect() sistem çağrısı:

 

int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

 

 

         Uzaktaki bir bilgisayara bağlanma talebinde bulunmak için kullanılır. sockfd dediğimiz değişken socket() sistem çağrısı tarafından döndürülmüş olan soket dosya tanımlayıcısının değerini tutar. serv_addr ise struct sockaddr türünde bir değişkendir ve hedef port ile IP adres bilgilerini barındırır. addrlen değişkeni de sizeof(struct sockaddr) değerini alır.

Ayrıca bind() işlevinin çağırılmadığına da dikkat edin. Yani kısaca yerel port numarası ile ilgilenmiyoruz; bizi ilgilendiren hedef bilgisayar (uzaktaki port). İşletim sistemi çekirdeği bizim için bir yerel port bulacaktır ve bağlandığımız bilgisayar da bu bilgiyi otomatik olarak bizden öğrenecektir.

 

5.6.4.  Listen() sistem çağrısı:

 

int listen(int sockfd, int backlog);

 

 

         Bu tür bir süreç iki aşamalıdır, önce listen() ile dinler sonra da accept() ile gelen çağrıları kabul edilir.

         sockfd  , socket() sistem çağrısı tarafından döndürülmüş olan soket dosya tanımlayıcısıdır . backlog ise gelen çağrı kuyruğunda izin verilen bağlantı sayısını gösterir . Gelen bağlantı talepleri siz onları accept() ile kabul edene dek bir kuyrukta bekler ve işte bu kuyruğun ne kadar uzun olacağını başka bir deyişle sınırını ne kadar olacağının belirlenmesinde kullanılır. Buna  5 veya 10 gibi bir değerler  kullanabiliriz.

          listen() işlevi hata durumunda -1 değerini döndürür ve tabii ki errno değişkenine de ilgili hata numarasını yazar.

          listen() işlevinden önce bind() işlevi çağırılmalıdır, yoksa işletim sistemi çekirdeği bu dinleme işlemini gelişigüzel bir port üzerinden yapmaya başlar.

 

5.6.5. Accept() sistem çağrısı:

 

int accept(int sockfd, void *addr, int *addrlen);

 

 

connect() işevi ile listen() ile dinlenen bir porta bağlanmaya çalışılır. Bu bağlantı talebi accept() ile kabul edilene dek kuyrukta bekler. accept() işlevi çağırılarak ,ona beklemekte olan çağrıyı kabul etmesini söylenir. O da yeni bir soket dosya tanımlayıcısı döndürür,sadece söz konusu bağlantıya özel. Orjinal olan socket tanımlayıcısı halen port üzerinden dinleme işlemini gerçekleştirmek için kullanılıyor yeni olarak yaratılmış olan ise send() ve recv() işlevlerinde kullanılır.

sockfd dediğimiz listen() ile dinlediğiniz soket tanımlayıcıdır. addr yerel olarak kullandığınız struct sockaddr_in yapısını gösteren bir göstergedir. Gelen bağlantılarla ilgili bilgiler burada barındırılacak (bu yapı kullanılarak gelen bağlantının hangi bilgisayar ve hangi porttan geldiği öğrenilebilir). addrlen yerel bir tamsayı değişkendir ve kullanılmadan önce accept() sizeof(struct sockaddr_in) değerini almalıdır. accept() işlevi bu değerden daha fazla sayıda baytı addr içine yerleştirmeyecektir. Eğer söz konusu değerden daha azını yerleştirirse o zaman da addrlen değerini, bunu yansıtacak şekilde değiştirecektir.

accept() hata durumunda -1 değerini döndürür ve errno değişkenine ilgili hata kodunu yerleştirir.

 

 

 

5.6.6. send() ve recv() sistem çağrısı :

          

          Bu fonksiyonlar göndermeyi ve veri almayı sağlar.send() işlevi şu şekilde çağrılır:

 

int send(int sockfd, const void *msg, int len, int flags);

       

 

       sockfd üzerinden veri gönderilecek sokettir.msg gönderilmek istenen mesajı gösteren bir göstergedir ve len değişkeni bu verinin byte cinsinden uzunluğudur. flags  parametresi genelde 0 olarak kalır.

        send() değer olarak gönderilen bayt miktarını döndürür.Eğer send() işlevinin döndürdüğü değer len değişkenindeki değer kadar değilse göndermek istenen verinin geriye kalanını göndermek programcının görevidir. Eğer paket küçükse (1k civarı) bu işlev büyük ihtimalle tüm veriyi bir seferde gönderebilecektir.Hata durumunda -1 değerini döndürür ve errno hata kodunu yazar.

 

         recv() fonksiyonuda send() işlevine benzer:

 

İnt recv(int sockfd, void *buf, int len, unsigned int flags);

 

 

 

         sockfd üzerinden okuma işlemi gerçekleştirilen sokettir, buf okunan verinin yazılacağı bellek bölgesinin başlangıç adresini gösteren göstergedir, len ise verinin yazılacağı tamponun enfazla boyudur ve flags genelde 0 değeri alır.

        recv() okunduktan sonra tampona yazılan bayt miktarını döndürür  eğer bir hata oluştu ise -1 değerini döndürüp errno değişkeninini değerini belirler. recv() 0 değerini döndürürse karşı taraf bağlantıyı kesmiş demektir.

 

5.6.7. sendto() ve recvfrom() sistem çağrıları:

 

int sendto(int sockfd, const void *msg, int len, unsigned int flags,

           const struct sockaddr *to, int tolen);

       

 

 

         send() işlevinden tek farkı fazladan iki bilgi parçası var. to dediğimiz struct sockaddr türünde bir değişkeni gösteren işaretçidir ve hedef IP adresi ile port numarasını barındırır. tolen değişkeni sizeof(struct sockaddr) değerini almalıdır.

 

          recv() ve recvfrom() işlevleri birbirlerine çok benzerdir.

 

int recvfrom(int sockfd, void *buf, int len, unsigned int flags,

             struct sockaddr *from, int *fromlen);

 

 

 

 

          recv() işlevinde olduğu gibi, sadece birkaç ek değişkene ihtiyacı vardır. from yerel bir struct sockaddr türünde değişkenin adresini gösteren göstergedir ki bu değişken de mesajın geldiği ilgili makinanın IP adresini ve port numarasını barındıracaktır. fromlen yerel ve int türünde bir göstergedir ve söz konusu değişkenin alması gereken ilk değer sizeof(struct sockaddr)'dir. İşlev, çalışıp bir değer döndürünce fromlen değişkeni from değişkenindeki adresin boyunu depoluyor olacaktır. recvfrom() işlevi okuduğu bayt sayısını veya bir hata oluşması durumunda -1 değerini döndürür (ve errno değişkenine uygun hata kodunu yerleştirir). Eğer connect() ile bir UDP soketine bağlantı kurulursa send() ve recv() işlevlerini kullanmanızda bir sakınca yoktur. Soketin kendisi hala bir bağlantısız veri paketi soketidir ve gidip gelen paketler de hala UDP protokolünü kullanır. Fakat soket arayüzü otomatik olarak sizin yerinize gerekli hedef ve kaynak bilgisini pakete ekler.

 

5.6.8. close() ve shutdown()sistem çağrıları :

 

Close(sockfd);

 

 

          Soket tanımlayıcınız ile ilişkilendirilmiş olan bağlantıyı kesmek için zaman close() işlevi kullanılır.

 

          Artık bu sokete ne yazılabilir ne de buradan veri okunabilir. Karşıda bunları yapmaya çalışan kişi artık bir hata mesajı ile karşılaşacaktır.

         

int shutdown(int sockfd, int how);

 

 

         Soket kapatma işlemi üzerinde biraz daha denetim sahibi olmak istenirse o zaman shutdown() tercih edilebilir. Bu işlev kullanarak iletişim kanalını tek yönlü ya da çift yönlü olarak kapatılabilir. sockfd kapatmak istenen soket dosya tanımlayıcısıdır ve how değişkeni de şu değerlerden birini alabilir:

 

0 -- Bundan sonraki okumalara izin verme

1 -- Bundan sonraki yazmalara izin verme

2 -- Bundan sonraki okumalara ve yazmalara izin verme (close() işlevi gibi)

 

         shutdown() görevini tamamlarsa 0 döndürür ama  bir hata ile karşılaşırsa -1 döndürür  ve errno değişkenine hata kodunu yazar.

        Eğer shutdown() işlevi bağlantısız veri paketi soketleri üzerinde kullanılırsa bu soketler artık send() ve recv() işlevleri tarafından kullanılamaz hale gelirler

shutdown() aslında dosya tanımlayıcısını kapatmaz sadece kullanılabilirliğini değiştirir. Soket tanımlayıcısı gerçekten iptal etmek isteniyorsa close() kullanılmalı.

 

5.6.9. Getpeername() sistem çağrısı:

 

int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);

 

 

 

          getpeername() bağlantılı veri akış soketinin diğer tarafında kim olduğunu söyler:

         sockfd bağlantılı veri akış soketinin tanımlayıcısıdır, addr, struct sockaddr türündeki bir göstergedir ki bu da iletişimin diğer ucundaki konak ile ilgili bilgileri tutar. addrlen, int türündeki göstergedir, alması gereken ilk değer ise sizeof(struct sockaddr) olarak verilir.

Bu işlev hata durumunda -1 değerini döndürür ve errno değişkenine gerekli değeri yazar.

Bir kere karşı tarafın adres bilgisine eriştikten sonra inet_ntoa() veya gethostbyaddr() işlevleri ile daha fazla bilgi edinilebilir.

 

5.6.10. Gethostname() sistem çağrısı :

 

int gethostname(char *hostname, size_t size);

 

 

           Programınızın üzerinde çalıştığı konağın ismini döndürür. Bu isim daha sonra gethostname() tarafından makinanızın IP adresini tespit etmek için kullanılabilir.

        hostname işlev çağrıldıktan sonra bilgisayarın ismini barındıracak karakter dizisinin göstergesidir ve size değişkeni de hostname dizisinin bayt cinsinden uzunluğudur. Hata oluşursa  -1 değerini döndürüp errno değişkenini gerekli şekilde ayarlar.

 

5.6.11.  Gethostbyname() sistem çağrısı :

 

            DNS (Domain Name System yani Alan Adı Sistemi) demek. Kısaca ona kolayca hatırlanabilecek adres verilir o da buna karşılık gelen IP adresini verir (ki bu bilgiyi bind(), connect(), sendto() ve diğer işlevleri çağırırken kullanılabilsin).

 

$ telnet finaltime.com.tr

 

 

 komutunu girildiğinde telnet yazılımı connect() işlevine verilmesi gereken "192.168.0.1" bilgisine erişebilir. Bunun için gethostbyname() işlevini kullanılır.

 

struct hostent *gethostbyname(const char *name);

 

 

         Görüldüğü gibi bu işlev struct hostent türünde bir gösterge döndürür. Söz konusu türün ayrıntılı yapısı da şöyledir:

  

struct hostent {

    char    *h_name;

    char    **h_aliases;

    int     h_addrtype;

    int     h_length;

    char    **h_addr_list;

};

#define h_addr h_addr_list[0]

 

         

 

 

 

 

 

 

 

        Değişkenin içindeki alanların açıklamaları:

·  h_name -- Konağın resmi ismi.

·  h_aliases -- Söz konusu konağın alternatif isimleri, NULL ile sonlandırılmış karakter dizileri şeklinde.

·  h_addrtype -- Dönen adresin türü, genellikle AF_INET değerini alır.

·  h_length -- Byte cinsinden adresin uzunluğu.

·  h_addr_list -- Konağın ağ adresinin sıfır sonlandırmalı dizisi. Konak adresleri ağ bayt sıralamasına sahiptir.

·  h_addr -- h_addr_list listesindeki ilk adres.

     

       gethostbyname() çalıştıktan sonra içini doldurduğu struct hostent türünden bir gösterge döndürür. Eğer hata oluşursa NULL döndürür. gethostbyname() söz konusu olduğunda perror() işlevini hata mesajını basmak için kullanamazsınız.Bunun için h_errno değişkeni kullanılır.

         Buradaki tek gariplik şudur: IP adresini basarken kullanılması gereken h->h_addr, char* türündedir ama inet_ntoa() işlevi struct in_addr türünde bir değişken isteme konusunda ısrarcı olduğu için önce h->h_addr değiğkenini struct in_addr* türüne çevrilip ve ardından veriye ulaşıldı.

 

5.6.12.  select() (Eşzamanlı G/Ç Çoğullama) sistem çağrısı:

 

          Bir sunucu program ve bir yandan gelen bağlantıları dinlerken öte yandan da açık olan bağlantılardan akmakta olan verileri okumak istiyor olabilir.

 

         select() aynı anda birden fazla soketi gözetleme imkânı sunar. Aynı zamanda hangi soketin okumak için hazır olduğunu, hangisine yazabileceğiniz, hangisinde istisnai durumlar oluştuğunu da söyler.

 

int select(int numfds, fd_set *readfds, fd_set *writefds,

           fd_set *exceptfds, struct timeval *timeout);

 

           

 

          Bu işlev dosya tanımlayıcı kümelerini gözlemler. Özel olarak ilgilendikleri ise readfds, writefds ve exceptfds'dir. Mesela standart girdiden ve sockfd gibi bir soket tanımlayıcıdan veri okuyup okuyamayacağınızı merak ediliyorsa , 0 ve sockfd'yi readfds kümesine eklenir . numfds parametresi azami dosya tanımlayıcı artı bir olarak ayarlanmalıdır. Bizim örneklerimizde sockfd+1 olmalıdır. Çünkü açıktır ki yukarıdaki değer standart girdiden (0) daha büyük olacaktır.

         select() çalıştırıldıktan sonra readfds seçmiş olduğunuz dosya tanımlayıcılardan hangisinin okumak için hazır olduğunu yansıtacak şekilde güncellenir. Bunlar aşağıdaki FD_ISSET() makrosu ile test edilebilir.

Her küme fd_set türündedir. Bu tür üzerinde aşağıdaki makroları kullanılır

·  FD_ZERO(fd_set *set) -- dosya tanımlayıcı kümesini temizler.

·  FD_SET(int fd, fd_set *set) -- kümeye fd'yi ekler.

·  FD_CLR(int fd, fd_set *set) -- fd'yi kümeden çıkarır.

·  FD_ISSET(int fd, fd_set *set) -- fd'ni küme içinde olup olmadığına bakar.

        struct timeval Bazen istemcilerin size veri göndermesini sonsuza dek beklemek istemezsiniz. Bu zaman yapısı bir sonlandırma süresi ("timeout period") belirlenmesini sağlar. Eğer bu süre geçildi ise ve select() hala hazır bir dosya tanımlayıcı bulamadı ise o zaman işlev (select) döner ve program  işine devam eder.

         struct timeval yapısının elemanları aşağıdaki gibidir:

 

struct timeval {

    int tv_sec;     // seconds

    int tv_usec;    // microseconds

};

 

 

 

 

 

          Yapılması gereken tv_sec'i kaç saniye bekleneceğine ayarlamak ve tv_usec'i de kaç mikrosaniye bekleneceğine ayarlamak.

Okuma kümesindeki soketlerden biri bağlantıyı kesmiş ise select() bu soket için "okumaya hazır" mesajı verir ve  recv() ile okumaya kalkıldığında  recv() size 0 değerini döndürür. Böylece istemcinin bağlantıyı kesmiş olduğunu anlaşılır.

 

   5.7. send() sistem çağrısı sorunları:

 

         send() ve recv()  bölümünde send() işlevinin istenilen kadar veriyi bir anda gönderemeyebileceğinden bahsedildi. Tam olarak ondan 512 byte göndermesi istenir ama o sadece 412 tanesini yollar.

            Bu veriler hala tampon bölgemizde gönderilmeyi bekliyorlardır. Kontrolünüz dışındaki bir takım sebeplerden ötürü işletim sistemi çekirdeği mevcut veriyi bir seferde göndermemeye karar verebilir.

            Sorunu halletmek için :

 

#include <sys/types.h>

#include <sys/socket.h>

 

int sendall(int s, char *buf, int *len)

{

    int total = 0;        // gönderdigimiz byte miktari

    int bytesleft = *len; // eksik kalan byte miktari

    int n;

 

    while(total < *len) {

        n = send(s, buf+total, bytesleft, 0);

        if (n == -1) { break; }

        total += n;

        bytesleft -= n;

    }

 

    *len = total; // gönderilen toplam bayt miktarı

 

    return n==-1?-1:0; // sorun varsa -1, işlem tamamsa 0 döner

}

 

 

 

          

 

 

 

 

 

          

 

 

 

 

 

 

 

 

 

 

          Bu örnekte, s üzerinden veri gönderilecek  soketi, buf veriyi barındıran bellek bölgesini (buffer) ve len de tampondaki byte sayısını gösteren sayıya işaret eden int türünde bir göstergedir.

           Hata durumunda işlev -1 değerini döndürür (ve errno hata değişkeni de send() işlevi sayesinde gerekli hata kodunu barındırır.) Ayrıca gönderilebilmiş olan byte sayısı da len parametresinden depolanır. Bu sayı sizin gönderilmek istenen sayıya eşit olacaktır (hata oluşması durumu haricinde). sendall() işlevi verinin tamamını yollamak için elinden geleni yapacaktır ancak bir hata ile karşılaşırsa size geri dönecektir.

 

char buf[10] = "merhaba";

int len;

 

len = strlen(buf);

if (sendall(s, buf, &len) == -1) {

    perror("sendall");

    printf("Hata yüzünden sadece %d byte veri gönderildi!\n", len);

}

 

 

 

 

 

 

 

 

 

5.8. Raw Socket ‘ ler

 

            Normal soketler bizleri tamamen özgür kılmıyor. Soketleri normal modunda kullandığımız zaman yapabileceğimiz çoğu şeyi yapamıyoruz.

 

            1. ICMPv4, IGMPv6 gibi IP paketlerini yazamıyor ve okuyamıyoruz.

 

            2. Bazı işletim sistemi çekirdekleri, IGMP,TCP veya UDP olmayan paketleri işlemiyorlar.

 

            İşte bunun gibi sorularımızın cevabı RAW socket'ler oluyorlar. Raw socket'ler ile normal TCP ve UDP soketlerle yapamadığımız çoğu şeyi yapabiliyoruz. Raw socket'ler "privileged"(ayrıcalıklı) kullanıcılara, kullanıcı verisinin iletişimi için kullanılan normal soketlere nazaran,  protokole direk erişim sağlamamıza olanak sağlıyor. Bunlar, mevcut protokollerin üstüne kurulan protokolleri geliştirmemizi kolaylaştırıyor veya normal arabirim ile erişemediğimiz birçok modüle erişmemizi sağlıyorlar.

           

            Bütün ağ protokolleri gibi, TCP/IP de katmanlı bir yapıdadır. Her katman iletişimin başka bir yanını yürütmekle yükümlüdür. TCP/IP'de dört katman vardır.

            ------------------------------------------------------------------

            | 4. Application  | telnet, ftp, dns etc.            |

            ------------------------------------------------------------------

            | 3. Transport    | TCP UDP                          |

            ------------------------------------------------------------------

            | 2. Network      | IP ICMP IGMP                   |

            ------------------------------------------------------------------

            | 1. Link            | device driver, network card  |

            ------------------------------------------------------------------

           

 

            TCP / IP protokol süiti katmanları

 

            Normal soket kullandığımız zaman sadece 4. katmanı kontrol edebiliyoruz. RAW

soket kullanarak 2. katmana, hatta patch'ler(yama) kullanarak 1. katmana kadar kontrol edebiliyoruz.

 

             RAW soketler aslında hemen hemen pratik hayatta hiç kullanılmamakla beraber, kişiye ağ işleyiş detaylarını öğretmesi açısından son derece faydalı. Raw socket programlamanın gerçek hayat örnekleri arasında ping, traceroute sayılabilir. Standart TCP/UDP soketlerinin yetmediği durumlarda her zaman raw soket'leri bir kurtarıcı olarak yanınızda bulacaksınız.

 

 


6. Örnek Program Kodu


 

           Buradaki sunucu programı 10 tane (daha fazla da olabilir) ayrı istemciye hizmet sunabilmektedir. Sunucu sürekli dinleyerek istemcilerden gelen ekleme, silme, ve liste alma taleblerine cevap vermektedir. Örneğin ekleme isteği geldiğinde , istemciden gelen verileri ogr.txt dosyasına kaydetmektedir.

           İstemcilerde dinlemekte olan sunucuya bağlanarak, sunucunun çalıştığı bilgisayarda bulunan kütük’e kayıt ekleme, kayıt silme ve kayıt listesi isteme gibi seçenekleri çağırır ve gerekli bilgiler sunucu tarafından bilgisayarına gönderilir . istemcinin buradaki görevi ise aldığı verileri doğru bir şekilde parçalayarak ekrana yazdırmasıdır. Çünkü gönderilen kayıtlar bir kişinin tüm bilgilerinin tutulduğu veri pakedi şeklinde gelmektedir. Dolayısıyla çözümlenmesi gerekir.

6.1. Örnek Sunucu Programı

#include "stdio.h"

 

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <errno.h>

 

#define PORT 9034

 

//#include "islem.h"

//#include "iletim.h"

 

    fd_set master;    // ana dosya tanimlayici listesi

    fd_set read_fds;  // select() icin gecici dosya tanimlayici listesi

    

    int fdmax;  //soket ler listesinde ki enson soketi bulabilmek için

    int i,k,j; //i soket no için, k da gelen verinin kac byte doldurduğunu tutuyor

    int newfd; //yani soket tanımlamak için

    int addrlen; // socket boyu nuatmak için kullanılıyor

    int nbytes; // recv ile alınan byte boyu

 

    char buf[39];

 

 

 

    int dinlesock;

 //*****************************************************

 struct sockaddr_in myadres;     // sunucu adresi

 struct sockaddr_in istemci_adres; // istemci adresi

 

 

/*############################################################################

###########################################################################*/

 

 

   

    //************************************************************

//************************ yapi.h ı buraya kopyaladım**********

struct ogryapi

{

    char adi[11];

    char soyadi[16];

    char notu[4];

    char no[10];

    char sil;

 

} ogr;

 

FILE *kp;

//ogryapi ogr;  

 

//*************************************

void dosyaac(char *ad,char *mod)

{    

      kp = fopen(ad,mod);

       if((kp = fopen(ad,mod))== NULL){

            perror("fopen");

            printf(" %d nolu hata\n", errno);

            exit(0);

       }

     

}

/*##########################################################

###########################################################*/

void ekle(char adi[11],char soyadi[16],char notu[4],char no[10])

{

 printf("su anda ekle fonksiynundasınız\n");

 

       dosyaac("ogr.txt","r+");

             

       strcpy(&ogr.adi[0],adi);

       printf("ogr.adi = %s  boyutu = %d\n",ogr.adi,sizeof(ogr.adi));

       strcpy(&ogr.soyadi[0],soyadi);

       printf("ogr.soyadi = %s  boyutu = %d\n",ogr.soyadi,sizeof(ogr.soyadi));

       strcpy(&ogr.notu[0],notu);

       printf("ogr.notu = %s  boyutu = %d\n",ogr.notu,sizeof(ogr.notu));

       strcpy(&ogr.no[0],no);

       printf("ogr.no = %s  boyutu = %d\n",ogr.no,sizeof(ogr.no));

       ogr.sil=' ';

  

       printf("kayıtlar kopyalandı\n");  

       

        fseek(kp,0,2);

       

        fwrite(&ogr,sizeof(ogr),1,kp);

        //fflush(kp);

 

        fclose(kp);

}

/*###################################################################

####################################################################*/

 

void sil(char *no)

{

 int son;

 dosyaac("ogr.txt","r+");

 printf("sil() fonksiyonundasın ; gelen veri = %s \n",no);

 

 fseek(kp,0,2);//sona git

 

 son=ftell(kp);//gittii yerin adresini al ve son değişkenine at

 

 fseek(kp,0,0);//başa konumlan    // kp = fopen("ogr.txt","a+"); //fread(&ogr,sizeof(ogryapi),0,kp);

 

 while(ftell(kp)!=son)//gittiği adres sona eitmi deilmi diye bakyor.son kayt deilse i�ri giriyor

 {

  fread(&ogr,sizeof(ogr),1,kp);

 

  printf("ogr.no = %s\n",ogr.no);

     /*if(strncmp(no,ogr.no,9)==0)printf("eşit\n");

     if(strncmp(no,ogr.no,9)==1)printf("no buyuk\n");

     if(strncmp(no,ogr.no,9)==-1)printf("ogr.no buyuk\n");*/

       

  if(strncmp(no,ogr.no,9)==0)

   {

    printf("eşit diye if e girdi\n");

    ogr.sil='*';

    fseek(kp,-sizeof(ogr),1);//okuyup sonuna geldii i�n bir geri gidip yazmas gerekir

    fwrite(&ogr,sizeof(ogr),1,kp); printf("kayıt silindi\n\n");

   // fseek(kp,sizeof(ogryapi),1);

   }else printf("esit degil \n");

 }

 fclose(kp);

 

}

/*####################################################################

######################################################################*/

 

 

 

/*###################################################################

#####################################################################*/

 

   

main()

{

 

 

//**************************************************************************

//********************hazirlanı buraya kopyaladım*****************************

 

    myadres.sin_family = AF_INET;

    myadres.sin_addr.s_addr = INADDR_ANY;

    myadres.sin_port = htons(PORT);

    memset(&(myadres.sin_zero), '\0', 8);

 

 

 

//socket dosya tanımlayıcısı

 

    if ((dinlesock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {

        perror("socket"); printf("\n soket tanimlama hatası <hazirlan.h> a bkz");

        exit(1);

    }

    printf("socket oluşturldu\n");

   

// "adres zaten kullanımda" mesajından kurtul

    int yes=1;        // setsockopt() SO_REUSEADDR için, aşağıda

    if (setsockopt(dinlesock, SOL_SOCKET, SO_REUSEADDR, &yes,sizeof(int)) == -1) {

        perror("setsockopt"); printf("\n adres kullanımda hatası <hazirlan.h> a bkz");

        exit(1);

    }

//bind işlevi ile adres bilgilerini (ip ve port no) socket ile ilişkilendiriyoruz

    if (bind(dinlesock, (struct sockaddr *)&myadres, sizeof(myadres)) == -1) {

        perror("bind"); printf("\n bind hatası <hazirlan.h> a bkz");

        exit(1);

    }

    printf("bind calistirildi\n");

   

 

  FD_ZERO(&master);    // ana listeyi ve gecici listeyi temizle

    FD_ZERO(&read_fds);

 

// listen

    if (listen(dinlesock, 10) == -1) {

        perror("listen");printf("\nlisten işlevinde hata <iletim.h> a bkz");

        exit(1);

    }

    printf("dinliyor\n");

   

    // dinleyici soketi ana listeye ekle

    FD_SET(dinlesock, &master);

 

    // en büyük dosya tanimlayicisi hatirla

    fdmax = dinlesock;

 

   

   

   

    // ana döngü

    for(;;) {    

            printf("istek varmı bakıyor , select işlemini calistirdi\n");

         

          read_fds = master; // master ı read_fds ye kopyala

        if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) {

            perror("select");

            exit(1);

        }

       

     

        // mevcut baglantilari tarayip okumaya hazir olanlari tespit et

        for(i = 0; i <= fdmax; i++) {

            if (FD_ISSET(i, &read_fds)) { // bir tane yakaladik!!

 

 

                //**********************************************************

                //gelen veri  yeni bir istemci çağrısı

               if (i == dinlesock) { //yani dinlediğimiz soketten biri bağlanma isteğinde bulunuyorsa aşağı geç ve accept i çalıştır

                    // handle new connections

                    addrlen = sizeof(istemci_adres);

                    printf("yeni istemci yakalandı\n");

                    if ((newfd = accept(dinlesock, (struct sockaddr *)&istemci_adres,&addrlen)) == -1) {

                        perror("accept");

                    } else {

                        FD_SET(newfd, &master); // ana listeye ekle

                        if (newfd > fdmax) {    // azami miktarı güncelle

                            fdmax = newfd;

                        }

                        printf("%s IP li istemci socket %d üzerinden bağlandı\n", inet_ntoa(istemci_adres.sin_addr), newfd);

                    }

                  }

                //******************************************************************

               

 

                //******************************************************************

                // gelen veri daha önceden socket oluşturulmuş bir itemci verisi

                 else {    printf("önceki istemcilerden yeni istek\n");

                    // istemciden gelen veri icin gerekeni yap

                    if ((nbytes = recv(i, buf, sizeof(buf), 0)) <= 0) {

                        // bir hata var ya da istemci baglantiyi kesti

                        if (nbytes == 0) {

                            // baglanti kesilmis

                            printf(" socket %d bağlantıyı kesmiş \n", i);

                        } else {

                            perror("recv");

                        }

                        close(i); //güle güle  (kesilen bağlantının soketi kapatıldı )

                        FD_CLR(i, &master); // ana listeden cikar

                    } else {

               

                  printf("onceki soketten veri geldi\n");

                  printf("%d boyutlu veri geldi\n",sizeof(buf));     

                        // hata yok ve bağlantıda kesik değil. yani gelen 0 veya -1 değil, gelen  veri.

                        k=0;

                  while(buf[k]!='\0'){

                  k++;

                  } 

                 

                  printf("\n39 byte lık bilginin dolu kısmı %d byte\n",i);

                   

                    if(k==9){        // yani silinmek için gelenler

                            printf("\n silinecek veri numarası geldi \n");

                           sil(buf);                            

 

                     }

                     if(k>9){ // yani eklenmek için gelenler

                            printf("\n eklenecek veri geldi \n");

                      printf("gelen veri = %s\n",buf);

                       

  char adi[11]="";

  char soyadi[16]="";

  char notu[4]="";

  char no[10]="";

 

  char a[37]="";

  char b[27]="";

  char c[12]="";

  char d[9]="";

 

  

   //*******************************   

  strcpy(a,buf);

  strcpy(b,&buf[10]);

  strcpy(c,&buf[25]);

  strcpy(d,&buf[28]);

     

     printf("a      = %s\n",a);

     printf("b      =           %s\n",b);

     printf("c      =                          %s\n",c);

     printf("d      =                             %s\n\n",d);

 //******************************        

     

     strncpy(adi,a,10); 

     printf("adi    = %s\n",adi);

                     

     //************************************

     

     strncpy(soyadi,b,15);

     printf("soyadi =           %s\n",soyadi);

    

     //************************************

   

     strncpy(notu,c,3);

     printf("notu   =                          %s\n",notu);

      

     //************************************

         

     strncpy(no,d,9);

     printf("no     =                             %s\n\n",no);

       

 //*****************************

                           ekle(adi,soyadi,notu,no);

 

                     }  

                     //****************************************************************

                     if(k==1){              // yani kayılılar liistesini istiyor

                        printf("kayıt listesi isteniyor\n");

                            //listele(i);  // i nolu soketten kayıtları gönder

 

 

char bitti[6]="bitti"; 

 int son;

 char satir[38]="";

 char bosalt[38]="";

 char silik[2]="*";

 char ok1[2]="";

 char ok[2]="o";

 

 

 dosyaac("ogr.txt","r");

//******************

 fseek(kp,0,2);//sona git

 son=ftell(kp);//gittii yerin adresini al ve son deikenine at

 fseek(kp,0,0);//basa konumlan   // kp = fopen("ogr.txt","a+"); //fread(&ogr,sizeof(ogryapi),0,kp);

 

 while (ftell(kp)!=son){//gittii adres sona eitmi deilmi diye bakyor.son kayt deilse i�ri giriyor

 

   fread(&ogr,sizeof(ogr),1,kp);

   printf("\nogr.adi = %s\n",ogr.adi);

  

                  if(strcmp(silik,&ogr.sil)!=0){  // yani ogr.sil * ile aynı değilse ( yani 0 'a eşit ise aynı demek)

     

                       strncat(satir,ogr.adi,10);printf("gidecek satir = %s\n",satir);

                           strncat(satir,ogr.soyadi,15);printf("gidecek satir = %s\n",satir);

                       strncat(satir,ogr.notu,3);printf("gidecek satir = %s\n",satir);

                       strncat(satir,ogr.no,9);printf("gidecek satir = %s\n",satir);

           

              

                       int total = 0;        // gönderdigimiz byte miktari

                       int bytesleft = sizeof(satir); // eksik kalan byte miktari

                       int n;

 

                            send_restart:

                      while(total < sizeof(satir)) {

                                 n = send(i, satir+total, bytesleft, 0);

                                 if (n == -1) { break; }

                                 total += n;

                                 bytesleft -= n;

                            }

                                 printf("%d byte gonderildi\n",total);

                               strcpy(satir,bosalt);

                                 

                         if(recv(i,ok1,sizeof(ok1),0)==-1){

                           perror("recv");printf("ok : aldım verisi alınamadı\n");

                           exit(0);

                         }

                         if(strcmp(ok,ok1)==0){

                                    printf("veri ulaşmış");                   

                         }else goto send_restart;

                   }

 

 }         

        if(send(i,bitti,sizeof(bitti),0) == -1){

              perror("send");printf("bitti mesajı gonderilemedi <islem.h> a bkz");

            exit(0);

          }else printf("bitti mesajı gonderildi\n");

 

//******************

 

  fclose(kp);

 

               

                     

                     

                     

                     }

                    //*****************************************************************

                    }

                   }

                //******************************************************************

 

             }

         }

    }

 

 

/*##########################################################################*/

 

   

/*#############################################################################

##############################################################################*/

 

 

 

 

 /*  char c;

    while(1){

            printf("1 - SERVER ÇALIŞTIR\n\n2 - BILGI AL\n\n3 - HIZMETI DURDUR");

          scanf("%s",&c);

             switch (c){

                      case '1':{basla() break;} // yani dinleme konumuna geç ve gelenleri kabul et

                  case '2':{ break;} // kimler baglı ve ne istiyorlar

                case '3':{ break;} // dinlemeyi bırak ve gelen istekleri kabul etme

                case '4': exit(0); // çıkış

              }

  }     */

 return 0;

 

}

 

 

6.2. Örnek İstemci Programı

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <errno.h>

#include <string.h>

#include <netdb.h>

#include <sys/types.h>

#include <netinet/in.h>

#include <sys/socket.h>

 

#define PORT 9034 // istemcinin bağlanacağı port

 

#define MAX 100 // bir seferde kabul edebileceğimizazami byte miktarı

 

int main(int argc, char *argv[])

{

    int sock;

    char listebitti[6]="";

    char secim;

   

  char adi2[37]="";

  char soyadi2[27]="";

  char notu2[12]="";

  char no2[9]="";

 

  char a[37]="";

  char b[27]="";

  char c[12]="";

  char d[9]="";

 

   

    char adi[11];

    char soyadi[16];

    char notu[4];

    char no[10];

   

    char sil[10];

   

    char giden[38]="";

    char gelen[38]="";

    char bosalt[38]="";

 

    int gelboy,gidboy;

 

    struct hostent *he;

    struct sockaddr_in their_adres; // bağlananın adres bilgisi

 

    if (argc != 2) {

        fprintf(stderr,"client argümanı girmediniz\n");

        exit(1);

    }

 

    if ((he=gethostbyname(argv[1])) == NULL) {  // konak bilgisini al

        perror("gethostbyname");

        exit(1);

    }

 

    their_adres.sin_family = AF_INET;    // konak bayt sıralaması

    their_adres.sin_port = htons(PORT);  // short, ağ bayt sıralaması

    their_adres.sin_addr = *((struct in_addr *)he->h_addr);

    memset(&(their_adres.sin_zero), '\0', 8);  // geriye kalanı sıfırla

 

                               if((sock = socket(AF_INET, SOCK_STREAM, 0))==-1){

                          perror("socket");printf("soket hatası");

                          exit(0);

                        }else printf("soket oluşturuldu\n");

 

                         if(connect(sock , (struct sockaddr *)&their_adres , sizeof(struct sockaddr))==-1){

                             perror("connect");

                             exit(0);

                        }else printf("connect işlevi tamamlandı\n");

                          

            

      for(;;) {

           

               printf("\n1 - ekle\n2 - sil\n3 - listele\n");

               printf("seciminiz : ");scanf("%s",&secim);

             

             

                      

              //******************************************** 

              if(secim=='1'){

                          strcpy(giden,"");

                        printf("adı    : ");scanf("%s",&adi);

                          int k=0;

                          while(adi[k]!='\0'){

                            k++;

                          }   

                        if(k!=10){

                                   for(k;k<11;k++){

                            strcat(adi,".");

                           }

                        }

                            strncat(giden,adi,10);

                         printf("gidecek = %s\n",giden);

                        //****************************************************

                        printf("soyadı : ");scanf("%s",&soyadi);

                              k=0;

                          while(soyadi[k]!='\0'){

                            k++;

                          }   

                        if(k!=15){

                                   for(k;k<16;k++){

                            strcat(soyadi,".");

                           }

                        }

                        strncat(giden,soyadi,15);

                             printf("gidecek = %s\n",giden);

                        //*********************************************************

                        printf("notu   : ");scanf("%s",&notu);

                              k=0;

                          while(notu[k]!='\0'){

                            k++;

                          }   

                        if(k!=3){

                                   for(k;k<4;k++){

                            strcat(notu,".");

                           }

                        }

                        strncat(giden,notu,3);

                             printf("gidecek = %s\n",giden);

                        //******************************************************

                        printf("no     : ");scanf("%s",&no);

                              k=0;

                          while(no[k]!='\0'){

                            k++;

                          }   

                        if(k!=9){

                                   for(k;k<10;k++){

                            strcat(no,".");

                           }

                        }

                        strncat(giden,no,9);

                               printf("gidecek = %s\n",giden);  

                             

                                  

                       

                        gidboy=sizeof giden;

                         

                           if( send(sock,giden,gidboy+1,0)==-1 ){

                         perror("send");printf("send hatası\n");

                         exit(0);

                        }

                        else printf("%d byte veri gönderildi ve eklendi \n",gidboy);          

                       

              }

            

          

             //************************************************

       

             if(secim=='2'){

                         printf("sileceğiniz kayıtın numarasını giriniz : ");scanf("%s",&sil);

                           gidboy=sizeof(sil);

                          

                         if(send(sock,sil,gidboy,0)==-1){

                             perror("send");

                             exit(0);

                       }

                       else

                           printf("%d byte veri gönderildi ve kayıt silindi \n",gidboy);

               }

             //**************************************************

           

             if(secim=='3'){

                             char ok[2]="o";

                                              

                       if(send(sock,"3",2,0)==-1){

                          perror("send");

                          exit(0);

                       }

                      

                           if((gelboy=recv(sock,gelen,100,0))==-1){//bir tane al

                               perror("recv");

                         exit(0);

                       }

                      

                       strcpy(listebitti,"bitti");

                      // printf("listebitti = %s\n",listebitti);

                      

                       printf("adı                soyadı                       not      no\n");

                       printf("..........         ...............              ...      .........   \n");

                          

                       while(strncmp(gelen,listebitti,5)!=0){  // yani ilk 5 karakteri "liste bitti" ile aynı değilse gelenleri al

                                                  //*******************************   

                                                  if(send(sock,ok,2,0)==-1){

                                        perror("send");printf("ok : geldi verisi gönderilemdi\n");

                                        exit(0);

                                     }

                                     strcpy(a,gelen);

                                                  strcpy(b,&gelen[10]);

                                                  strcpy(c,&gelen[25]);

                                                  strcpy(d,&gelen[28]);

                                                  //******************************

                                     //int k;   k=0; while(a[k]!='.'){ k++;}

                                                  strncpy(adi2,a,10); 

                                      //***************

                                     strncpy(soyadi2,b,15);

                                     //******************

                                     strncpy(notu2,c,3);

                                     //*****************

                                     strncpy(no2,d,9);

                                    

                                                  printf("%s         %s              %s      %s\n",adi2,soyadi2,notu2,no2);

                                    

                                    

                                          

                                     strcpy(gelen,"");//printf("gelen = %s",gelen);

                                     strcpy(a,"");//printf("a = %s",a);

                                     strcpy(b,"");//printf("b = %s",b);

                                     strcpy(c,"");//printf("c = %s",c);

                                     strcpy(d,"");//printf("d = %s",d);

                                     strcpy(adi2,"");//printf("adi2 = %s",adi2);

                                     strcpy(soyadi2,"");//printf("soyadi2 = %s",soyadi2);

                                     strcpy(notu2,"");//printf("notu2 = %s",notu2);

                                     strcpy(no2,"");//printf("no2 = %s",no2);

                                    

                                                                      

                                     if((gelboy=recv(sock,gelen,100,0))==-1){

                                     perror("recv");

                                     exit(0);

                                     }

                                    

                           }

                       printf("kayıtlar bitti\n");

             }

            //*************************************************************************************************

         

          }//ana döngünün ayracı

      

       close(sock);

 

    return 0;

}

 

 


7. Kaynaklar


 

http://www.turkcgi.com

http://www.enderunix.org (Barış Şimşek )

http://www.ecst.csuchico.edu/~beej/guide/net

http://www.belgeler.org  (beej in ağ klavuzu)

               http://www.enderunix.org/docs/rawsocket.txt (Murat Balaban)

bilgisayar işletim sistemeleri (Ali Saatçi)

 


8. Yasal Açıklamalar


Telif Hakları

Bu belgenin, Socket Programlama  0.1 sürümünün telif hakkı © 2003 Güven KÖKDAMAR 'a aittir. Bu belgeyi, Free Software Foundation tarafından yayınlanmış bulunan GNU Özgür Belgeleme Lisansının 1.1 ya da daha sonraki sürümünün koşullarına bağlı kalarak kopyalayabilir, dağıtabilir ve/veya değiştirebilirsiniz. Bu Lisansın bir kopyasını http://www.gnu.org/copyleft/fdl.html adresinde bulabilirsiniz.

Feragatname

Bu belgedeki bilgilerin kullanımından doğacak sorumluluklar, ve olası zararlardan belge yazarı sorumlu tutulamaz. Bu belgedeki bilgileri uygulama sorumluluğu uygulayana aittir.

Tüm telif hakları aksi özellikle belirtilmediği sürece sahibine aittir. Belge içinde geçen herhangi bir terim bir ticarî isim ya da kuruma itibar kazandırma olarak algılanmamalıdır. Bir ürün ya da markanın kullanılmış olması ona onay verildiği anlamında görülmemelidir.