Soket API

Bu bölümde, sonraki bölümlere alt yapı oluşturmak amacıyla, temel soket fonksiyonlarına sırasıyla değinmeye çalışacağız.

socket

Haberleşecek her iki taraf da ilk olarak birer soket oluşturmalıdır. Soketler, socket sistem çağrısı ile oluşturulmaktadır.

#include <sys/socket.h>
int socket(int domain, int type, int protocol);

socket fonksiyonu başarılı olması durumunda bir soket oluşturur ve sonraki çağrılarda kullanılmak üzere, sokete ilişkin bir dosya betimleyicisi (file descriptor) döner. Başarısızlık durumunda ise -1 değerine geri dönmektedir. Oluşan soketin gerçekte ne olduğunu ve geri dönüş değerinin neyi gösterdiğini merak edebilirsiniz.

Unix sistemlerinde tüm Giriş/Çıkış işlemleri dosya betimleyicileri üzerinden, okuma ve yazma işlemleri şeklinde yapılmaktadır. Dosya betimleyicileri açık dosyalara erişimi sağlayan birer tamsayı değeridir. Örneğin, disk tabanlı bir dosya açıldığında, işletim sistemi bellekte o dosyaya ilişkin, adına dosya nesnesi (file object) denilen, bir alan oluşturur. Dosya ile ilgili daha sonraki okuma ve yazma işlemleri bu dosya nesnesi üzerinden yapılmaktadır. Bu dosya nesnesine ulaşmak için işletim sistemi her proses özelinde ayrıca bir dosya betimleyici tablosu (file descriptor table) denilen bir alan daha tutmaktadır. Gösterici dizisi şeklindeki bu tablonun her elemanı bir dosya nesnesinin adresini tutmakta ve dosya betimleyicileri de bu tabloda bir indeks değeri göstermektedir. open fonksiyonuyla bir disk tabanlı dosya açtığımızda fonksiyonun, başarılı olması durumunda, döndüğü değer aslında dosya betimleyici tablosundaki indeks değeridir.

soket fonksiyonunun argümanlarına geçmeden bu durumu aşağıdaki gibi bir örnek üzerinden gözleyebiliriz. Örneği test.c adıyla saklayıp aşağıdaki gibi derleyip çalıştırabilirsiniz.

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main() {
    int fd;

    fd = open("./test.c", O_CREAT);
    printf("%d\n", fd);

    fd = socket(PF_INET, SOCK_STREAM, 0);
    printf("%d\n", fd);

    return 0;
}
$ gcc -otest test.c

$ ./test
3
4

Bir proses başlatıldığında dosya betimleyici tablosundaki ilk 3 giriş, standart okuma ve yazma işlemleri için, saklı durumdadır. Bu aşamadan sonra açılacak her dosya için dosya betimleyici tablodaki en düşük indeksli alan ayrılmaktadır. Örneğimizde disk tabanlı dosya için 3 numaralı girişin, soket için ise 4 numaralı girişin ayrıldığını görmekteyiz. Bu durumu görsel olarak aşağıdaki gibi gösterebiliriz.

Ortada gerçek anlamda bir dosya olmamasına karşın, soketler programcı tarafından bakıldığında gerçek birer dosyaymış gibi görünmektedir. Gerçek dosyalara ve soketlere aynı dosya betimleyici tablosu üzerinden erişildiğinden soketler üzerinde de dosya fonksiyonlarını kullanmak mümkün olmaktadır. Soketlerle çalışmak normal dosyalara göre daha karmaşık olduğundan okuma ve yazma işlemleri için ayrıca özel fonksiyonlar da bulunmaktadır.

Not: Soketlere ilişkin dosya sisteminde girişler oluşturulabilir. Bu tür soketlere (Unix Domain Socket) daha sonra değineceğiz.

socket fonksiyonunun aldığı argümanlar ve ilgilendiğimiz başlıca değerleri aşağıdaki gibidir.

  • domain: Kullanılacak olan protokol ailesini (protocol family) gösterir. bits/socket.h dosyasında sembolik sabitler şeklinde tanımlanmışlardır. Kullanacağımız başlıca değerler aşağıdaki gibidir.
Alan (Domain) İletişim Ortamı Adres Biçimi Adres Yapı Türü
AF_UNIX Aynı makina Dosya yolu sockaddr_un
AF_INET IPv4 yoluyla ağ üzerinden 32-bit IPv4 adresi + 16-bit port numarası sockaddr_in
AF_INET6 IPv6 yoluyla ağ üzerinden 128-bit IPv4 adresi + 16-bit port numarası sockaddr_in6

socket fonkiyonuna, AF_UNIX geçirilmesi durumunda bir UNIX alan soketi oluşturulurken, diğer iki argüman için Internet alan soketi oluşturulmaktadır. Tablodaki diğer bilgilere yeri geldiğince değineceğiz.

  • type: type argümanı ile soketin tipi bildirilmektedir. bits/socket_type.h bu amaçla tanımlanmış sembolik sabitler bulunmaktadır. Başlıcaları aşağıdaki gibidir.
Tip Anlamı
SOCK_STREAM Güvenli, bağlantı temelli protokol (TCP)
SOCK_DGRAM Güvensiz, mesaj temelli protokol (UDP)
SOCK_RAW IP veya Ethernet gibi alt seviye protokol

SOCK_STREAM ile tanımlanan soketler stream soketi, SOCK_DGRAM ile tanımlananlar ise datagram soketi olarak isimlendirilmektedir.

  • protocol: Bu argümanın değeri diğer ikisine göre değişmektedir, çoğunlukla 0 değeri kullanılmaktadır.

bind

bind fonksiyonu bir soketi bir adresle ilişkilendirmek için kullanılmaktadır.

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
Parametre Görevi
sockfd socket çağrısından elde edilen dosya betimleyicisi
addr Soketin bağlanacağı adres bilgilerinin tutulduğu alana ait gösterici
addrlen Adres bilgilerinin tutulduğu alanın uzunluğu

socket fonksiyonunu incelerken, soketlerin birden çok alan için (AF_UNIX, AF_INET, AF_INET6) tanımlanabildiklerini görmüştük. Bu durumda farklı uzunluklardaki adres alanlarına ihtiyaç duyulmaktadır. Örneğin IPv4 ve IPv6 için soket adresleri sırasıyla, netinet/in.h dosyasında tanımlı, aşağıdaki yapı türlerinde saklanmaktadır. IPv4 için IP adresi 32 bit ile tutulurken IPv6 için 128 bit kullanıldığını hatırlayınız.

struct sockaddr_in {
    short int           sin_family; // AF_INET
    unsigned short int  sin_port; // Port numarası
    struct in_addr      sin_addr; // IPv4 adresi
    unsigned char       sin_zero[8];
}
struct sockaddr_in6 {
    u_int16_t           sin6_family; // AF_INET6
    u_int16_t           sin6_port; // Port numarası
    u_int32_t           sin6_flowinfo;
    struct in6_addr     sin6_addr; // IPv6 adresi
    u_int32_t           sin6_scope_id;
}

bind fonksiyonunu bu türlerden bağımsız yazabilmek için ayrıca genel bir adres türü olarak sockaddr türü tanımlanmıştır.

struct sockaddr {
    sa_family_t     sa_family; // Adres ailesi (AF_*)
    char            sa_data[14]; // Soket alanına göre değişen adres
};

bind fonksiyonu çağrılırken gerçek adres türünden sockaddr türüne tür dönüşümü yapılmakta ve gerçek adres alanının uzunluğu addrlen parametresine geçirilmektedir. bind fonksiyonu ayrıca sa_family alanının değerine bakarak yapının geri kalan kısmının uzunluğunu ve adres formatını belirleyebilmektedir.

Şimdi soket ile ilişkilendireceğimiz adres alanını nasıl oluşturacağımıza bakalım. Daha basit olduğundan IPv4 adresini kullanacağız. sockaddr_in içindeki sin_addr alanına ait yapı ve tür tanımı aşağıdaki gibidir.

typedef uint32_t in_addr_t;
struct in_addr
{
    in_addr_t s_addr;
};

16 byte uzunluğundaki IPv4 için soket adres alanını (sockaddr_in) görsel olarak aşağıdaki gibi gösterebiliriz.

Birden çok byte'tan oluşan tam sayılar işlemci mimarisine göre bellekte iki farklı şekilde tutulabilmektedir. Örneğin, x86 mimarisinde sayının düşük anlamlı byte'ı düşük adreste tutulurken başka bir mimaride yerleşim tam tersi olabilir. x86 gibi düşük anlamlı byte'ın düşük adreste tutulduğu mimariler little endian olarak isimlendirilirken, sayının düşük anlamlı byte'ını yüksek adreste saklayan mimariler big endian olarak isimlendirilmektedir. Haberleşen sistemlerin byte sıralamaları farklı olabilmektedir bu yüzden ağ üzerinden gönderilecek olan bilgilerin byte sıralaması önem taşımaktadır. Ağ üzerinden gönderilen bilgiler geleneksel olarak big endian'dır (network byte order). Bu durumda soket adresine ilişkin alan içerisine IP adresinin ve port numarasının yazılma biçimi önem taşımaktadır. Çalışılan sistem big endian ise, port ve IP değerleri olduğu gibi yazılırken tersi durumda byte sıralamalarının değiştirilmesi gerekmektedir. Soket arayüzü, çalışılan sistem (host) ile ağ (network) arasındaki byte sıralaması uygunluğunu sağlamak için aşağıdaki yardımcı fonksiyonları barındırmaktadır.

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

listen

Stream soketi üzerinde dinleme yapmak için listen fonksiyonu kullanılır.

#include <sys/socket.h>
int listen(int sockfd, int backlog);

Soketler bir bağlantıyı başlatmak için kullanılabildikleri gibi gelen bağlantıyı kabul etmek için de kullanılmaktadırlar. Bağlantıyı başlatmak için kullanılan soketler aktif, bağlantıyı kabul edenler ise pasif olarak isimledirilmektedir. Bir soket socket fonksiyonuyla oluşturulduğunda aktiftir, listen fonksiyonu ile soket pasif hale geçirilerek bağlantıyı kabul eden tarafta olduğu belirtilir.

Parametre Görevi
sockfd socket çağrısından elde edilen dosya betimleyicisi
backlog Cevap verilmeyi bekleyen kuyruktaki istek sayısı

backlog bekleyen bağlantı isteklerini sınırlamak için kullanılmaktadır.

accept

Gelen bağlantı istekleri accept ile kabul edilir. accept çağrıldığında bekleyen bir bağlantı isteği yoksa, normal şartlarda, yeni bir bağlantı isteği gelene kadar kod bloklanmaktadır.

Not: socket ile bir soket blokeli modda oluşturulmaktadır. Sonrasında soket fcntl ile blokesiz moda aşağıdaki gibi geçirilebilir. Bu durumda normalde bloklayan accept ve soketten okuma için kullanılan recv gibi fonksiyonlar blokesiz olarak çalışacaktır.

fcntl(sockfd, F_SETFL, O_NONBLOCK);

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr , socklen_t *addrlen);
Parametre Görevi
sockfd socket çağrısından elde edilen dosya betimleyicisi
addr Karşı taraftaki soket adresinin yerleştirileceği alanın adresi
addrlen addr uzunluğunun tutulduğu adres

accept ile karşı tarafın adres bilgilerine ulaşılabilmektedir. İstekte bulunan soketin adresiyle ilgilenmiyorsanız addr ve addrlen için NULL ve 0 değerlerini geçirebilirsiniz. addrlen parametresi accept tarafından hem okuma hem de yazma amaçlı olarak kullanılmaktadır. accept çağrıldığında addrlen, addr için ayrılan alanın uzunluğunu gösterirken, accept döndüğünde o alana yazılan gerçek byte sayısını göstermektedir.

Not: Karşı tarafın adres bilgilerine daha sonra getpeername yardımcı fonksiyonuyla da ulaşılabilir.

accept bir bağlantı isteğini kabul ettiğinde yeni bir soket ile geri dönmekte ve haberleşme bu yeni soket üzerinden yapılmaktadır.

connect

Karşı tarafa bağlanma isteği connect ile gönderilir.

#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr * addr, socklen_t addrlen);
Parametre Görevi
sockfd socket çağrısından elde edilen dosya betimleyicisi
addr Karşı taraftaki soket adresinin tutulduğu alana ait gösterici
addrlen Adres bilgilerinin tutulduğu alanın uzunluğu

close

Soket bağlantısı close ile sonlandırılır.

#include <unistd.h>
int close(int fd);

shutdown

shutdown ile, geçirilen son argümanın değerine göre iletişim kanalı tek veya çift yönlü olarak kapatılabilir.

#include <sys/socket.h>
int shutdown(int sockfd, int how);
how Durum
SHUT_RD Soket okuma işlemine kapatılır
SHUT_WR Soket yazma işlemine kapatılır
SHUT_RDWR Soket okuma ve yazmaya kapatılır

results matching ""

    No results matching ""