TCP Soketleri

TCP, veri güvenliğinin garanti altına alındığı, bağlantı temelli (connection-oriented) bir iletişim protokülüdür. TCP protokolü stream soketleri üzerinden sağlanmaktadır. Bu amaçla socket fonksiyonuna ilk argüman olarak SOCK_STREAM sembolik sabitini geçireceğiz.

Haberleşecek taraflardan bir tanesi gelecek istekleri dinlemekte diğer taraf ise istekte bulunmaktadır. Dinleyen taraf sunucu (server), istekte bulunan taraf ise istemci (client) olarak isimlendirilmektedir. TCP için sunucu istemci haberleşmesini kabaca telefon sistemine benzetebiliriz. Bu benzetim üzerinden gidecek olursak sırasıyla sunucu ve istemci tarafında gerçekleşen olaylar aşağıdaki gibi olacaktır.

  1. socket ile haberleşme alt yapısı oluşturulur. Bu durumu telefon cihazının kendisine sahip olmaya benzetebiliriz.
  2. bind ile soket bir IP adresi ve port ile ilişkilendirilir. Bu durumu, kendi adımıza tahsis edilmiş, bir telefon numarasına sahip olmaya benzetebiliriz.
  3. listen ile soket üzerinde dinleme işlemi yapılacağı belirtilir. Bu durumu telefonun şebekeye bağlanmasına ve başında cevap vermek üzere beklemeye benzetebiliriz.
  4. accept ile gelen bağlantı isteği kabul edilir. Bu durumu telefon çaldığında cevap verilmesine benzetebiliriz.
  5. read ve write fonksiyonlarıyla çift yönlü okuma ve yazma işlemleri yapılır. Bu durum karşılıklı konuşmaya denk gelmektedir.
  6. close ile bağlantı sonlandırılır. Bu durum telefonun kapatılmasına karşılık gelmektedir.

İstekte bulunan tarafta ise geçen olaylar kabaca şöyledir:

  1. Sunucu tarafında olduğu gibi ilk olarak socket ile haberleşme alt yapısı oluşturulur.
  2. connect ile karşı tarafa bağlanma isteği gönderilir. Bu durum birini telefon numarasını çevirerek aramaya denk gelmektedir.
  3. read ve write fonksiyonlarıyla çift yönlü okuma ve yazma işlemleri yapılır. Bu durum karşılıklı konuşmaya denk gelmektedir.
  4. close ile bağlantı sonlandırılır. Bu durum telefonun kapatılmasına karşılık gelmektedir.

Sunucu ve istemci arasında geçen olayları görsel olarak aşağıdaki gibi gösterebiliriz.

Sunucunun yaşam döngüsü boyunca birden çok istemciye yanıt verebilmek için bir döngü içerisinde yeniden accept fonksiyonunu çağırdığını görmekteyiz. Genel işleyişi gösterdiğimiz bu örnekte, sunucu aynı anda bir tek istemciye hizmet vermektedir, çoklu istemciyle eş zamanlı çalışma şekline daha sonra bakacağız.

Bu noktada port numaralarıyla ilgili birkaç şey söylemek istiyoruz. IP numarasını bir şirketin telefon numarasına benzetirsek, port numarası konuşmak istediğimiz kişinin dahili numarasına denk gelmektedir. Port numaraları sistemdeki servisleri birbirinden ayırmak için kullanılmaktadır. Port numaraları için adres alanlarında 2 byte yer ayrıldığını hatırlayınız, ilk 1024 tanesi root kullanıcısına ayrılmış 65535 port (0. port saklı durumdadır) kullanılabilmektedir. /etc/services dosyasından sisteminizdeki servislere ve ilgili port numaralarına bakabilirsiniz. İstemci tarafında herhangi bir port numarası belirtmememiz dikkatinizi çekmiş olabilir, işletim sistemi bizim için geçici bir port ayarlayacaktır. Bu tür portlar ephemeral port olarak isimlendirilmektedir.

Şimdi basit bir örnek üzerinden istemci sunucu haberleşmesine bakalım. Sunucu ve istemci kodları aşağıdaki gibidir. client uygulaması bağlanacağı tarafın IP adresini komut satırından almaktadır. Her iki tarafında bilmesi gereken port numarasını 8080 olarak seçtik. Buradaki test kendi makinamızda gerçekleştireceğimizden istemciye loopback adresini (127.0.0.1) geçirdik.

server.c

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define PORT 8080
#define BACKLOG 4

int main() {
    /*istemcileri dinleyen soket*/
    int s;
    /*iletişim soketi*/
    int c;
    int b;
    struct sockaddr_in sa;
    FILE *client;
    int count = 0;

    if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket");
        return 1;
    }

    bzero(&sa, sizeof sa);

    sa.sin_family = AF_INET;
    sa.sin_port   = htons(PORT);

    if (INADDR_ANY)
        sa.sin_addr.s_addr = htonl(INADDR_ANY);

    if (bind(s, (struct sockaddr *)&sa, sizeof sa) < 0) {
        perror("bind");
        return 2;
    }

    listen(s, BACKLOG);

    for (;;) {
        b = sizeof sa;

        if ((c = accept(s, (struct sockaddr *)&sa, &b)) < 0) {
            perror("accept");
            return 4;
        }

        if ((client = fdopen(c, "w")) == NULL) {
            perror("fdopen");
            return 5;
        }

        fprintf(client, "%d. cevap\n", ++count);

        fclose(client);
    }
}

client.c

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define PORT 8080

int main(int argc, char **argv) {
  int s;
  int bytes;
  struct sockaddr_in sa;
  char buffer[BUFSIZ+1];

  if (argc != 2) {
      printf("kullanım:  client <IP adresi>\n");
      return 1;
  }

  if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
    perror("socket");
    return 1;
  }

  bzero(&sa, sizeof sa);

  sa.sin_family = AF_INET;
  sa.sin_port = htons(PORT);
  sa.sin_addr.s_addr = inet_addr(argv[1]);
  if (connect(s, (struct sockaddr *)&sa, sizeof sa) < 0) {
    perror("connect");
    close(s);
    return 2;
  }

  while ((bytes = read(s, buffer, BUFSIZ)) > 0)
    write(1, buffer, bytes);

  close(s);
  return 0;
}

Her iki kodu derleyelim ve sunucu uygulamasını çalıştıralım.

$ gcc -oserver server.c
$ gcc -oclient client.c
$ ./server

Bu aşamada server uygulamasının accept fonksiyonunda bloklandığını görmekteyiz. client uygulamasını çalıştırdığımızda ekrana sunucu tarafından gönderilen bir mesaj yazdığını ve ardından sonlandığını görmekteyiz.

$ ./client 127.0.0.1
1. cevap
$ ./client 127.0.0.1
2. cevap
$ ./client 127.0.0.1
3. cevap

Sunucu ve istemci kodlarında şimdiye kadar görmediğimiz bir sembolik sabit ve birer fonksiyon bulunmakta, kısaca ne olduklarına bakalım.

INADDR_ANY: Bu sembolik sabit, soketi spesifik bir IP adresine bağlamak yerine makinanın tüm internet arayüzlerinin kullanımına olanak sağlar.

fdopen: Sokete ilişkin düşük seviyeli dosya betimleyicisini almakta ve standart C kütüphanesindeki fonksiyonların tanıdığı FILE türünden adrese dönmektedir. Bu sayede soket üzerinde fprintf gibi fonksiyonları kullanmak mümkün olmaktadır.

FILE fdopen(int fildes, const char mode);

inet_addr: Sayı ve noktalarla ifade edilen IPv4 adreslerini ağ byte sıralamasına uygun sayı formuna dönüştürür.

Soketlere Özgü G/Ç Fonksiyonları

Soketler üzerinde G/Ç işlemleri için geleneksel read, write yerine ek özellikler sunan, soket spesifik recv ve send fonksiyonları da kullanılabilir.

#include <sys/socket.h>
ssize_t recv(int sockfd, void *buffer, size_t length, int flags);
ssize_t send(int sockfd, const void *buffer, size_t length, int flags);

Fonksiyonların geri dönüş değerleri ve aldıkları ilk 3 argüman read ve write ile aynıdır. Son argüman flags ise G/Ç işleminin davranışını değiştiren bitsel bir maskedir, örnek bazı değerler aşağıdaki gibidir.

Değer Özellik
MSG_DONTWAIT Soket, fcntl ile blokesiz moda geçirilmek zorunda kalınmaksızın, G/Ç işlemleri blokesiz modda yapılır.
MSG_PEEK Soketten okuma yaptıktan sonra tamponu silmez. Bu sayede soket üzerinden aynı bilgilere sonraki okumalarda da ulaşılabilir.
MSG_WAITALL Okuma işleminde, sokette length kadar bilgi birikene kadar bloklar.

results matching ""

    No results matching ""