Biraz garip olmakla birlikte bu işlevin epey faydalı olduğu
      söylenebilir. Şu durumu ele alalım: siz bir sunucu programsınız ve
      bir yandan gelen bağlantıları dinlerken öte yandan da açık olan
      bağlantılardan akmakta olan verileri okumak istiyorsunuz.
    
      Sorun değil, diyorsunuz, bir accept() ve birkaç
      tane de recv() işimizi görür. Ağır ol dostum!
      Ya çağırdığın accept() işlevi bloklayan durumda
      ise? O zaman aynı anda recv() ile nasıl oluyor
      da veri okumayı düşünebiliyorsun? "O halde bloklamayan soketleri
      kullanırım!" Hiç yakıştıramadım senin gibi bir programcıya! İşlemci
      zamanını deliler gibi harcamak mı istiyorsun? E peki nasıl yapacağız
      öyleyse?
    
      select() aynı anda birden fazla soketi gözetleme
      imkânı sunar. Bununla kalmaz aynı zamanda hangi soketin okumak için
      hazır olduğunu, hangisine yazabileceğiniz, hangisinde istisnai durumlar
      oluştuğunu da söyler.
    
      Laf kalabalığını kesip hemen işleve geçiyorum,
      select():
    
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
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 ediyorsanız
      tek yapmanız gereken 0 ve
      sockfd'yi readfds
      kümesine eklemek. numfds parametresi azami
      dosya tanımlayıcı artı bir olarak ayarlanmalıdır. Bu örnekte
      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 edebilirsiniz.
    
      Daha fazla ilerlemeden bu kümeler ile nasıl başa çıkacağınızı anlatayım.
      Her küme fd_set türündedir. Bu tür üzerinde aşağıdaki
      makroları kullanabilirsiniz:
    
      - 
          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.
         
      Son olarak, nedir bu struct timeval? Bazen birilerinin
      size veri göndermesini sonsuza dek beklemek istemezsiniz. Belki de her
      96 saniyede bir ekrana "Hala çalışıyor..." mesajı basmak istersiniz
      (program o esnada bir şey yapmıyor olsa bile). Bu zaman yapısı sizin bir
      sonlandırma süresi ("timeout period") belirlemenizi 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 böylece de siz
      de işinize devam edebilirsiniz.
    
      struct timeval yapısının elemanları aşağıdaki gibidir:
    
struct timeval {
    int tv_sec;     // seconds
    int tv_usec;    // microseconds
}; 
      Tek yapmanız gereken tv_sec'i kaç saniye
      bekleneceğine ayarlamak ve tv_usec'i de kaç
      mikrosaniye bekleneceğine ayarlamak. Evet, doğru okudunuz,
      mikrosaniye, milisaniye değil. Bir milisaniye içinde 1,000
      mikrosaniye vardır ve bir saniye içinde de 1,000 milisaniye vardır.
      Yani bir saniye içinde 1,000,000 mikrosaniye vardır. Tamam da neden
      "usec"? Buradaki "u" harfinin Yunan alfabesindeki Mü harfine benzediği
      düşünülmüştür ve bu yüzden "mikro"yu temsilen kullanılmıştır.
      Bunlara ek olarak işlev döndüğünde timeout
      ne kadar zaman kaldığını gösterecek şekilde güncellenmiş olabilir.
      Bu hangi tür UNIX kullandığınıza bağlıdır.
    
      Vay canına! Mikrosaniye hassasiyetinde bir zamanlayıcımız var! Gene de
      siz buna çok güvenmeyin. Standart Unix zamandilimi yaklaşık 100
      milisaniyedir, yani struct timeval yapısını nasıl
      ayarlarsanız ayarlayın en az bu kadar beklemek durumunda kalabilirsiniz.
    
      Meraklısına not: Eğer struct timeval yapısındaki
      değişkeninizin alanlarınız 0 yaparsanız,
      select() işlevi anında sonlanacak ve böyle kümenizin
      içindeki tüm dosya tanımlayıcıları taramış olacaktır. Eğer
      timeout parametresini NULL yaparsanız bu sefer
      de işlev asla zaman aşımına uğramayacak ve ilk dosya tanımlayıcı hazır
      olana dek bekleyecektir. Son olarak: Eğer belli bir küme için beklemek
      sorun teşkil etmiyorsa o zaman select() işlevini
      çağırırken onu NULL olarak ayarlayabilirsiniz.
    
/*
** select.c -- bir select() örneği
*/
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#define STDIN 0  // standart girdi için dosya tanımlayıcı
int main(void)
{
    struct timeval tv;
    fd_set readfds;
    tv.tv_sec = 2;
    tv.tv_usec = 500000;
    FD_ZERO(&readfds);
    FD_SET(STDIN, &readfds);
    // writefds ve exceptfds ile ilgilenmiyoruz:
    select(STDIN+1, &readfds, NULL, NULL, &tv);
    if (FD_ISSET(STDIN, &readfds))
        printf("Bir tusa basildi!\n");
    else
        printf("Zaman doldu.\n");
    return 0;
} 
      Üzerinde çalıştığınız uçbirim türüne bağlı olarak bir tuşa bastıktan
      sonra bunun algılanması için ENTER tuşuna basmanız gerekebilir. Aksi
      takdirde "Zaman doldu." mesajı alırsınız.
    
      Şimdi bazılarınız bir veripaketi soketi üzerinden veri beklemek için
      bu yöntemin mükemmel bir yöntem olduğunu düşünebilir -- evet haklısınız:
      bu yötem gerçekten de mükemmel olabilir . Ancak bazı UNIX türevleri
      select'i bu şekilde kullanabilirken bazıları
      kullanamaz. Bu yüzden de pratik olarak kullanmaya başlamadan önce
      sisteminizde bu konu ile ilgili man sayfalarını okuyun.
    
      Bazı UNIX türevleri de struct timeval yapısındaki
      değişkeninizi, zamanaşımına ne kadar süre kaldığını gösterecek şekilde
      güncelleyebilir. Bazıları ise bunu yapmaz. Eğer taşınabilir programlar
      yazmak istiyorsanız buna güvenmeyin. (gettimeofday()
      işlevinden faydalanın. Saçma geliyor değil mi evet ama böyle ben ne
      yapabilirim.)
    
      Peki ya okuma kümesindeki soketlerden biri bağlantıyı kesmiş ise? Bu
      durumda select() bu soket için "okumaya hazır"
      mesajı verir ve siz recv() ile okumaya
      kalktığınızda da recv() size 0
      değerini döndürür. Böylece istemcinin bağlantıyı kesmiş olduğunu
      anlarsınız.
    
      Meraklısına select() ile ilgili bir bilgi daha: eğer
      listen() ile dinlemekte olan bir soketiniz varsa
      bu soketin dosya tanımlayıcısını readfds kümesine
      yerleştirerek yeni bir bağlantı olup olmadığını öğrenebilirsiniz.
    
      İşte dostlarım, süper güçlü select() işlevinin özeti
      bu kadar.
    
      Ancak gelen yoğun istek üzerine yukarıdaki bilgileri pratiğe dökeceğimiz
      bir örnek sizi bekliyor.
    
      Bu program  basit bir çok kullanıcılı "chat" sunucu olarak
      davranır. Derledikten sonra bunu bir pencerede çalıştırın ve ardından
      
telnet ile programa bağlanın
      ("
telnet  hostname  9034"). Farklı farklı
      pencerelerden programa aynı anda birden fazla sayıda bağlantı
      açabilirsiniz. Bir kere bağlanıp da bulunduğunuz 
telnet
      ortamından bir şeyler yazıp yolladığınızda, mesajınız diğer bağlı
      
telnet pencerelerinde de görünmeli.
    
 
/*
** selectserver.c -- keyifli bir cok kullanicili chat sunucu
*/
#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>
#define PORT 9034   // dinledigimiz port
int main(void)
{
    fd_set master;    // na dosya tanimlayici listesi
    fd_set read_fds;  // select() icin gecici dosya tanimlayici listesi
    struct sockaddr_in myaddr;     // sunucu adresi
    struct sockaddr_in remoteaddr; // istemci adresi
    int fdmax;        // azami dosya tanimlayici numarası
    int listener;     // dinlenen soket tanımlayıcı
    int newfd;        // yeni accept()lenecek soket tanımlayıcı
    char buf[256];    // istemci verisi için tampon
    int nbytes;
    int yes=1;        // setsockopt() SO_REUSEADDR için, aşağıda
    int addrlen;
    int i, j;
    FD_ZERO(&master);    // ana listeyi ve gecici listeyi temizle
    FD_ZERO(&read_fds);
    // dinleyiciyi yarat
    if ((listener = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(1);
    }
    // "adres zaten kullanımda" mesajından kurtul
    if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes,
                                                        sizeof(int)) == -1) {
        perror("setsockopt");
        exit(1);
    }
    // bind
    myaddr.sin_family = AF_INET;
    myaddr.sin_addr.s_addr = INADDR_ANY;
    myaddr.sin_port = htons(PORT);
    memset(&(myaddr.sin_zero), '\0', 8);
    if (bind(listener, (struct sockaddr *)&myaddr, sizeof(myaddr)) == -1) {
        perror("bind");
        exit(1);
    }
    // listen
    if (listen(listener, 10) == -1) {
        perror("listen");
        exit(1);
    }
    // dinleyici soketi ana listeye ekle
    FD_SET(listener, &master);
    // en büyük dosya tanimlayicisi hatirla
    fdmax = listener; // so far, it's this one
    // ana döngü
    for(;;) {
        read_fds = master; // copy it
        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!!
                if (i == listener) {
                    // handle new connections
                    addrlen = sizeof(remoteaddr);
                    if ((newfd = accept(listener, (struct sockaddr *)&remoteaddr,
                                                             &addrlen)) == -1) {
                        perror("accept");
                    } else {
                        FD_SET(newfd, &master); // ana listeye ekle
                        if (newfd > fdmax) {    // azami miktarı güncelle
                            fdmax = newfd;
                        }
                        printf("selectserver: new connection from %s on "
                            "socket %d\n", inet_ntoa(remoteaddr.sin_addr), newfd);
                    }
                } else {
                    // 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("selectserver: socket %d hung up\n", i);
                        } else {
                            perror("recv");
                        }
                        close(i); // bye!
                        FD_CLR(i, &master); // ana listeden cikar
                    } else {
                        // istemciden bir miktar veri geldi
                        for(j = 0; j <= fdmax; j++) {
                            // gelen veriyi herkese yolla!
                            if (FD_ISSET(j, &master)) {
                                // dinleyici ve kendimiz haric
                                if (j != listener && j != i) {
                                    if (send(j, buf, nbytes, 0) == -1) {
                                        perror("send");
                                    }
                                }
                            }
                        }
                    }
                } // O KADAR CIRKIN ki!
            }
        }
    }
    return 0;
} 
      Lütfen dikkat edin: Yukarıdaki kod içinde iki dosya tanımlayıcı
      listem var: master ve read_fds.
      Birincisi yani, master o esnada bağlı olan
      tüm soket tanımlayıcılarını tutuyor ve buna ek olarak bir de
      dinleme görevini üstlenmiş olan soketi de bünyesinde barındırıyor.
    
      master diye bir liste oluşturmamın sebebi
      select() işlevinin sizin ona verdiğiniz soket
      listesini değiştirmesi ve hangilerinin okunmaya hazır olduğunu
      gösterecek şekilde güncellemesi. select()
      çağrıları arasında bağlantı kümesinin bozulmamış bir kopyasına
      ihtiyacım olduğu için böyle bir şey yaptım. Yani son anda
      master kümesini read_fds
      kümesine kopyalıyor ve select() işlevini
      çağırıyorum.
    
      İyi de bu aynı zamanda her yeni bağlantı talebinde bu bağlantıyı
      master listesine eklemem gerektiği anlamına
      gelmiyor mu? Tabii ki! Ve her bağlantı kesilmesinde de kesilen
      bağlantı ile ilgili tanımlayıcıyı da master
      listesinden çıkarmam gerekmiyor mu? Elbette!.
    
      Farkında iseniz listener soketinin ne zaman
      hazır olduğunu kontrol ediyorum. Hazır olduğunda bu demek oluyor ki
      yeni bir bağlantı talebi var ve ben de bunu görünce bağlantıyı
      accept() ile kabul ediyor ve master
      listesine ekliyorum. Benzer şekilde bir istemci bağlantısı hazır
      olduğunda ve recv() işlevi 0
      döndürdüğünde anlıyorum ki istemci bağlantıyı kapatmış ve bu yüzden onu
      master listesinden çıkarmalıyım.
    
      Ancak eğer istemci recv() işlevi ile sıfırdan
      farklı bir değer döndürdüğünde de biliyorum ki okunmuş olan bir veri
      yığını var. Bu veriyi alıyorum ve sonra da master
      listesi üzerindeki elemanlar üzerinden tek tek dolaşıp almış olduğum
      veriyi diğer istemcilere yolluyorum.
    
      Ve dostlarım işte size süper güçlü select() işlevinin
      o kadar da basit olmayan açıklaması.