Domain Soketleri ve Internet Soketleri



Yüklə 181,98 Kb.
Pdf görüntüsü
səhifə7/7
tarix17.10.2017
ölçüsü181,98 Kb.
#5100
1   2   3   4   5   6   7

yorumluyor ve size dosyayı ftp protokolune uygun paketler halinde gönderiyor. Artık siz okumaya

başlıyorsunuz. Paketleri alıp birleştirip yerel diske yazıyorsunuz. Bu aşamadan sonra istediğinizi

yaptırabilirsiniz.

istemci.c

Aşağıdaki bir SMTP sunucuya bağlanıp smtp komutları gönderir ve cevaplarını alır. Tam olarak bir

SMTP istemci değildir. SMTP protokolunun detaylari için RFC 821'e bakabilirsiniz.

#include

#include

#include

#include

#include

#include

#include

#define SMTP_PORT 25

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

{

  struct hostent *h_name;



  int sd;

  char cmd[512];

  struct sockaddr_in serv_addr;

  printf("checkd - SMTP sunucuyu kontrol eder.\n");

  printf("http://www.acikkod.org/\n\n");

  if(argc < 2) {

    printf("Kullanımı: %s hostname\n",argv[0]);

    exit(1);

  } else {

    h_name = gethostbyname(argv[1]);

  }

  bzero((char *) &serv_addr, sizeof(serv_addr));



  serv_addr.sin_family = AF_INET;

  serv_addr.sin_addr.s_addr = *(u_long *) h_name->h_addr;

  serv_addr.sin_port = htons(SMTP_PORT);

  printf("> Soket açılıyor... ");

  if( (sd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {

    printf("hata!\n");

    perror("socket");

    exit(1);

  } else printf("tamam\n");

  printf("> %s bağlanılıyor... ",inet_ntoa(serv_addr.sin_addr));

  if(connect(sd,(struct sockaddr*)&serv_addr,sizeof(serv_addr)) < 0) {

    printf("hata!\n");

    perror("connect");

    exit(1);

  } else printf("tamam\n");

  printf("> Karşılama mesajı alınıyor... ");

  memset(cmd, 0, 256);

  if((recv(sd, cmd, 256, 0)) <= 0) {

    printf("hata!\n");

    perror("recv");

    exit(1);

  } else printf("tamam\n");

  printf("  %s\n", cmd);



  printf("> Selam veriliyor... ");

  memset(cmd, 0, 256);

  strcpy(cmd, "helo world\n");

  if((send(sd, cmd, 256, 0)) <= 0) {

    printf("hata!\n");

    perror("send");

    exit(1);

  } else printf("tamam\n");

  printf("> Cevap alınıyor... ");

  memset(cmd, 0, 256);

  if((recv(sd, cmd, 256, 0)) <= 0) {

    printf("hata!\n");

    perror("recv");

    exit(1);

  } else printf("tamam\n");

  printf("  %s\n\n", cmd);

  printf("SMTP sunucunuz çalışıyor.\n\n");

  return 0;

}

6. Gelişmiş G/Ç

6.1. Bloksuz G/Ç

Giriş/Çıkış   (I/O);   bir   dosyaya,   pipe'a,   terminale   veya   ağ   aygıtına   yazmak   veya   bu   aygıtlardan

okumak gibi işlemleri içermektedir. 

Okunacak veri hazır değilse veya yazılacak veri o an kabul edilmiyorsa bu işlemleri yapan süreçler

bloklanacaktır.   Bloksuz   G/Ç   (Nonblocking   I/O),   verinin   veya   aygıtın   hazır   olmaması   gibi

durumlarda bloklanmayı tamamen ortadan kaldıran bir özelliktir. Bu konunun detayları dökümanın

kapsamı dışındadır. (man 2 fcntl) 

6.2. select()

Soket program aynı anda birden fazla soketten veri okumak veya yazmak durumunda kalabilir.

Bunu tek soket tanımlayıcı ile sağlayamazsınız. Çünkü soketiniz blok durumuna geçtiğinde (örneğin

accept(), bağlantı gelene kadar programın blok olmasına neden olur) kodunuzun geri kalan kısmı

çalışmaz, bloktan çıkmayı bekler.

Şöyle bir senaryoyu hayal edelim. İki adet soket tanımlayıcı var ve bu ikisi ile karşı uçtan dosya veri

alacaksınız. 1. uç henüz veriyi hazırlamadı ancak 2. uç hazırladı varsayalım. Eğer program 1. soket

tanımlayıcı ile ilk önce 1. uçtan veri çekmeye çalışırsa blok olacaktır. 2. ucun verisi hazır olduğu

halde veri alınamayacaktır. Oysa select() ile bu iki uç kontrol edilip hazır olan uçtan -senaryomuzda

2. uç- veri okunsa idi program blok olmayacak ve verisi hazır olan işlerini yapmaya devam edecekti.

Bu şekilde bloklanma riski taşımayan bir program yazılabilir.

Tek bir süreç içerisinde bloksuz G/Ç kullanarak bu sorun çözülebilir. Bütün dosya tanımlayıcılar

bloksuz G/Ç yapacak şekilde set edilir. Eğer veri hazır değilse read() hemen sonlanır. Aynı işlemi

ikinci dosya tanımlayıcısı için de yaparız. Belli bir süre sonra tekrar ilk tanımlayıcıyı kontrol ederiz.

Buna 'polling' deniliyor. Bunun dezavantajı, gereksiz yere CPU zamanı harcamaktadır.

Aynı problemi çözmek için kullanılabilecek bir diğer teknik de "asenkron G/Ç". Bu yöntemde,

dosya tanımlayıcımız G/Ç için hazır olduğunda çekirdek, G/Ç yapacak süreci haberdar edecektir.

Ancak burada standart problemleri ve sinyalleri işleme (signal handling) ile ilgili problemler vardır. 

Bunlardan daha iyi bir teknik ise G/Ç çoğullama (I/IO multiplexing) olarak adlandırılmaktadır.

İlgilendiğimiz tanımlayıcıların eklendiği bir küme vardır ve tanımlayıcılardan biri G/Ç için hazır




olmadıkça   çıkmayan   bir   sistem   çağrısı   yapılır.   Sistem   çağrısından   çıkıldığında   hangi

tanımlayıcıların G/Ç için hazır olduğu sorulabilir. 

select   birden   fazla   soket   tanımlayıcının   durumunu   takip   eden   ve   BSD4.2   ile   gelen   bir   sistem

çağrısıdır. Burada şunu belirtmeliyim ki, select() yalnızca soketler için değil genel olarak dosya ve

G/Ç işlemleri için kullanılan bir sistem çağrısıdır.

#include

#include

#include

int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct

timeval *timeout);

n: En büyük soket tanımlayıcının bir fazlası (tanımlayıcılar 0'dan başladığından) 

readfds: Okumak için izlenecek (hazır olup olmadığı bakılacak) soket tanımlayıcı

writefds: Yazmak için izlenecek soket tanımlayıcı

exceptfds: İstisnai durumlar için izlenecek soket tanımlayıcı

timeval, aşağıdaki şekilde bir veri yapısıdır:

struct timeval {

  long tv_sec; /* seconds */

  long tv_usec; /* microseconds */

};

timeout   eğer  NULL  olarak  verilirse  bir   dosya hazır   olana  kadar  blokda  bekler.  Hazır  olmazsa



sonsuza kadar bekleyecektir. Tabi sinyal(signal) gonderilirse sonlanır. Eğer sinyal ile sonlandırılırsa

select() -1 döndürecektir ve errno=EINTR olacaktır.

'0' a setlenirse hiç beklemez. Bütün belirtilen tanımlayıcılar test edilir ve çağrıdan hemen çıkılır. Bu,

select içinde blocklanmadan birden fazla tanımlayıcıyı test etmek için kullanılır. 

0'dan   büyük   bir   değere   setlenirse   o   değer   kadar   bekler.   Belirtilen   tanımlayıcılardan   biri   hazır

olduğunda veya zamanaşımına uğradığında sistem çağrısı return yapar. 

Gözetlenecek tanımlayıcılar tanımlayıcı setine (descriptor set) eklenir. Tanimlayıcı seti, fd_set veri

yapısı şeklinde kayıt edilmiş veridir. fd_set, her tanımlayıcı için bir bit tutar ve geçerli boyutu 1024

bittir (sys/types.h içinde tanımlı). Set üzerinde işlem yapmak için bazı makrolar tanımlanmıştır: 

fd_set aşağıdaki gibi tanımlanır:

fd_set dset;

Seti sıfırlamak için:

FD_ZERO(&dset);

Takip etmek istediğimiz tanımlayıcıları eklemek için:

FD_SET(fd, &dset); 

select()  seti   değiştirmektedir.   select   sistem   çağrısından  sonra   bir   tanımlayıcının   hala   sette   olup

olmadığını test etmek için:



if (FD_ISSET(fd, &dset)) {

  ...


}

Bir tanımlayıcıyı setten çıkartmak için:

FD_CLR(fd, &dset); 

Bu bilgiler ışığında bir uygulama geliştirebiliriz. Standart giriş (stdin), dosya tanımlayıcısı 0 olan bir

dosyadır.   Eğer   bir   PC   kullanıyorsanız   klavye   standart   giriştir.   Aşağıdaki   kod,   standart   girişi

izlemektedir. Eğer standart giriş okumak için hazırsa (bunun anlamı klavyeden birşey yazıp enter'a

basmışsak) hazır olduğunu ekrana basacak. FD_ISSET ile de verinin hazır olduğunu göreceğiz

(Hazır olduğunda FD_ISSET, true olacaktır. Eğer hazır değilse false olacaktır.).

/*

 * select.c - I/O multiplexing



 *

 * Baris Simsek,

 * http://www.acikkod.org

 * 07/07/2004

 *

 */


#include

#include

#include

#include

int

main(void) {



  fd_set dset;

  struct timeval tv;

  int ret;

  FD_ZERO(&dset);

  FD_SET(0, &dset); /* stdin'i gozlemeye aldik */

  tv.tv_sec = 10; /* 10 sn giris icin bekle */

  tv.tv_usec = 0;

  ret = select(1, &dset, NULL, NULL, &tv);

  if (ret == -1)

    perror("select()");

  else if (ret) {

    printf("Standart input okumak için hazir.\n");

    if(FD_ISSET(0, &dset))

      printf("Standart input icin dset true\n");

  }

  else {


    printf("10 saniye icinde standart giristen veri girilmedi.\n");

    if(!FD_ISSET(0, &dset))

      printf("Standart input icin dset false\n");

  }


  return 0;

}

 




7. Sık Sorulan Sorular 

7.1. Kitap önerebilir misiniz? 

W. Richard Stevens'ın 

"UNIX Network Programming - Volume 1" 

kitabı bu alanda başlıca referans

kitaptır. 

7.2. Soketler Nasıl Çalışır? 

Soketler (özelikle connection oriented soketler) dosyalar veya 

PIPE  

gibi çalışır. Pipe'dan farkı iki



yönlü   olmasıdır.   Dosyalardan   farkı   ise   beklediğiniz   kadar   veri   okuyamayabilir   veya  istediğiniz

kadar veri yazamayabilirsiniz. 



7.3. select() veri hazır dediği halde, 0 byte neden okunur? 

select() verinin hazır olduğunu söyledikten sonra karşı taraf bağlantıyı koparmıştır. Bu da read() 'in

0 döndürmesine neden olur. 

7.4. Soket seçeneklerini nasıl değiştiririm? 

int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen); 

int flag = 1;

int result = setsockopt(sock, /* socket affected */

                        IPPROTO_TCP, /* seçeneği TCP seviyesinde set et. */

                        TCP_NODELAY, /* seçenek */

                        (char *) &flag,

                        sizeof(int)); /* seçenek değerinin büyüklüğü */

Seçenekler hakkında detay için "man 2 setsockopt" 

7.5. SO_KEEPALIVE seçeneği neden kullanılır? 

Oturum açmış iki bilgisayar arasında belli bir süre (RFC1122 de 2 saat olarak tanımlandı) veri

alışverişi olmazsa, karşı tarafı yoklamak için (ACK isteyerek) kullanılır. Zira karşı taraf ulaşılamaz

durumda olabilir. Bu durumu algılamak için kullanılır. 



7.6. Soketi tam olarak nasıl kapatırım? 

Soket kapatıltıktan sonra "netstat -na" ile bakıldığında sisteminizde TIME_WAIT'de duran soketler

hala gözükebilir. Bu normaldir. Çünkü TCP, bütün verinin karşılıklı transfer edildiğinden emin

olmak  istiyor.  Soket   kapatıldığında  her  iki   taraf  da   başka   veri   transferi   olmayacağı  konusunda

anlaşmış demektir. Böyle bir anlaşmadan sonra socket rahatlıkla kapatılabilir. Ancak burada iki

problem sözkonusu olacak. Bunlardan biri son ACK'in ulaşıp ulaşmadığı bilinemeyecek. (Bu ACK

için tekrar ACK istense bunun da ulaşıp ulaşmadığı belli olmayacak. Kısır döngü olur.). Eğer bu

ACK ağda kaybolmuş ise sunucu bunu bekliyor olacaktır. Bir diğer problem de eğer bir paket

router'in   birinde   herhangi   bir   nedenle   bekliyorsa   ve   alıcı   taraf   bunu   belli   bir   süre   içerisinde

alamamışsa paketi yeniden talep edecektir. Ancak diğer paket gerçekte kaybolmamıştır ve belli bir

süre   ağda   yeniden   ortaya  çıkacaktır.   Bu   kaybolma   ve   yeniden   ortaya  çıkması   süresi   içerisinde

bağlantı koparsa ve aynı host aynı porttan yeni bir bağlantı açarsa göndereceği paketin sıra numarası

ağdaki   ile   üst   üste   binecektir.   Çünkü   eski   oturumdan   kalma   bir   paket   yeni   oturumda   transfer

edilmiştir.   Bundan   kurtulmak   için   TIME_WAIT   durumu   ortadan   kalkmadan   yeni   bir   oturum

açmamalı. 

Bütün bunlar düşünüldüğünde TIME_WAIT'in programcı için bir yardımcı olduğu anlaşılır. Ancak

TIME_WAIT olduğu sürece programınız aynı soketi yeniden bind() edemeyecektir. 7.4. anlatıldığı

şekilde   SO_REUSEADDR   seçeneği   set   edilerek   bu   sorunu   çözebilirsiniz.   Öte   yandan

TIME_WAIT'te bekleyen soketler bir süre sonra (Linux'lerde bu 60 sn.dir.) close() olacaktır. sysctl

ile bu süre değiştirilebilir. 

close() doğru kapatma yöntemi ise de shutdown() daha kullanışlıdır. Çünkü tek yönlü soketi kapama

olanağı da sunar. 




int shutdown(int s, int how); 

İkinci parametre ile kapa yönünü verebilirsiniz: 

SHUT_RD: Veri alımı kesilecektir. 

SHUT_WR: Veri gönderimi kapatılacaktır. 

SHUT_RDWR: İki yönlü veri alışverişi durdurulacaktır. (close) 

close(), o süreç için soketi kapatır ancak eğer socketi başka bir süreçle paylaşıyorsa socket hala açık

duracaktır. shutdown() bütün süreçler için soketi kapatır. 

7.7. String halindeki bir adresi internet adresine nasıl çevirim? 

struct in_addr *atoaddr(char *address) {

  struct hostent *host;

  static struct in_addr saddr;

  /* Önce IP formatında deniyoruz. */

  saddr.s_addr = inet_addr(address);

  if (saddr.s_addr != -1) {

    return &saddr;

  }

  /* IP formatında değilse FQDN olarak deniyoruz. */



  host = gethostbyname(address);

  if (host != NULL) {

    return (struct in_addr *) *host->h_addr_list;

  }


  return NULL;

}

7.8. Soketlerde dinamik buffer kullanmanın bir yolu var mı? 

Soketten okuyacağınız veri miktarı belli olmadığında böyle bir ihtiyaç doğuyor. Bu durumda malloc

() ile mümkün olan en büyük tampon belleği ayırırsınız. Okunan verinin büyüklüğüne göre tampon

bellek realloc() ile yeniden boyutlandırılır. Zaten pek çok UNIX'de malloc() fiziksel bellekten yer

ayırmaz. Sadece adres uzayını belirler. Tampona veri yazdığınızda gerçek bellek sayfaları kullanılır.

Bu nedenle büyük buffer ayırmakla gereksiz kaynak kullanımına neden olunmaz. 

7.9. "address already in use" hatasını neden alırım? 

Port kullanılıyordur veya sunucu bir programı henüz sonlandırdınız ve socket TIME_WAIT'dedir.

İkinci durum için 7.4. ve 7.5. sorularının çözümlerine bakınız. Birinci durum için aynı portta çalışan

diğer socket programı durdurmanız gerekmektedir. 



7.10. Programımı nasıl daemon yapabilirim? 

En   kolay   yolu   inetd   ile   kullanmanızdır.   Diğer   bir   yöntem   ise   fork()   ederek   isteklere   cevap

vermekdir.   Detay   icin  

http://www.enderunix.org/docs/daemontr.html  

Programin   temel   iskeleti

asagidaki yapiya cevirilmeli: 

ret = fork ();

if (ret == -1) { /* fork hata verdi */

  perror ("fork()");

  exit (3);

}

if (ret > 0) exit(0); /* Ana süreç çıkar */



if (ret == 0) { /* Alt süreç devam eder */

  close (STDIN_FILENO);

  close (STDOUT_FILENO);

  close (STDERR_FILENO);

  if (setsid () == -1)  exit(1);

  /* Alt sürece ait işler */

}



7.11. Aynı anda birden fazla soketi nasıl dinlerim? 

select() kullanın. Hangi socket veri için hazır ise onun, kullanmanıza olanak sağlar.  

6.2.  

select()


bölümüne bakınız. 

7.12. 1024 ten küçük portları neden bind edemiyorum? 

Güvenlik nedenleri ile 1024'ten küçük portları yalnızca yetkili kullanıcı (root) açabilir. 



9. Belge Tarihçesi

VI.sürümde "7. Sık Sorulan Sorular" bölümü eklendi. (1 Ağustos 2004)

V.sürümde Soket Türleri ve Veri Dönüşümleri başlıkları eklendi. Sarmalama(Encapsulation) tanımı

eklendi. (8 Temmuz 2004)

IV.sürümde Gelişmiş G/Ç başlığı eklendi. (28 Haziran 2004)

III.sürümde   RFC'ler   gözden   geçirilerek   yeniden   düzenlenmiştir.   İlk  sürümlerdeki   bazı   yanlışlar

giderildi. (5 Haziran 2004)

II.sürüm, Linux Kullanıcıları Derneği Liste üyeleri için yeniden gözden geçirildi. (2001)

İlk sürüm: 10 Kasım 1998 (KTÜ Bilgisayar Klubü Dergisi için yazıldı.)

Hataları çekinmeden bana bildirebilirsiniz. b$



Yüklə 181,98 Kb.

Dostları ilə paylaş:
1   2   3   4   5   6   7




Verilənlər bazası müəlliflik hüququ ilə müdafiə olunur ©genderi.org 2024
rəhbərliyinə müraciət

    Ana səhifə