> "IP" Ailesi: Açılımı "Internet Protocol" biçimindedir. Protokol ailelerinden en meşhur olan "IP" ailesidir. Bu protokol ailesinin kısa bir tarihi ise şu şekildedir; -> IP ailesi 70'li yıllarda Vint Cerf ve Bob Kahn tarafından geliştirilmiştir. Cerf ve Kahn 1974 yılında önce TCP üzerinde sonra da bu protokol üzerinde çalışmışlar ve o tarihlerde ilk versiyonlarını oluşturmuşlardır. 1980'li yıllar itibariyle hepimizin katıldığı Internet'in (I'nin büyük yazıldığına dikkat ediniz) bu aileyi kullanmaya başlamasıyla populer olmuştur. Böylece IP ailesini kullanarak yazdığımız programlar hem aynı bilgisayarda hem yerel ağımızdaki bilgisayarlarda hem de Internet'te çalışabilmektedir. Aynı zamanda açık bir (yani bir şirketin malı değil) protokol ailesi olması da cazibeyi çok artırmıştır. Pekiyi "Internet" denilen kavram da nedir? Bugün hepimizin bağlandığı büyük ağa "Internet" denilmektedir. Bu ağ ilk kez 1969 yılında Amerika'da Amerikan Savunma Bakanlığı'nın bir soğuk savaş projesi biçiminde başlatıldı. O zamana kadar yalnızca kısıtlı ölçüde yerel ağlar vardı. 1969 yılında ilk kez bir "WAN (Wide Area Network)" oluşturuldu. Bu proje Amerikan Savunma Bakanlığı'nın DARPA isimli araştırma kurumu tarafından başlatılmıştır ve projeye "ARPA.NET" ismi verilmiştir. Daha sonra bu ağa Amerika'daki çeşitli devlet kurumları ve üniversiteler katıldı. Sonra ağ Avrupa'ya sıçradı. 1983 yılında bu ağ "NCP" protokolünden "IP" ailesine geçiş yaptı. Bundan sonra artık "APRA.NET" ismi yerine "Internet" ismi kullanılmaya başlandı. (Internet sözcüğü I herfi küçük harfle yazılırsa "internetworking" anlamında büyük harfle yazılırsa bugün katıldığımız dev ağ anlamında kullanılmaktadır.) Biz de "IP" ailesini kullanarak kendi "internetworking" ortamımızı oluşturabiliriz. Örneğin bir şirket hiç Internet'e bağlanmadan kendi internet'ini oluşturabilir. Buna eskiden "intranet" denirdi. "IP" ailesi herkesin kendi internet'ini oluşturabilmesi için bütün gerekli protokolleri barındırmaktadır. Tabii sinerji bakımından herkes zaten var olan ve Internet denilen bu dev ağa bağlanmayı tercih etmektedir. Diğer yandan bu "IP" ailesi ise dört katmandan oluşmaktadır. Bu katmanlar, yüksek seviyeden aşağı seviyeye doğru, +---------------------+-------------------------------+ | Application Layer | HTTP, SSH, POP3, IMAP, ... | +---------------------+---------------+---------------+ | Transport Layer | TCP | UDP | +---------------------+---------------+---------------+ | Network Layer | IP | +---------------------+-------------------------------+ | Physical/Data Link | Ethernet | | Layer | Wireless | +---------------------+-------------------------------+ biçiminde gösterilebilir. Görüleceği üzere bu ailede Fiziksel Katman ve Veri Bağlantı Katmanı birarada bulunmaktadır. Yani üst üste değil. Bu katmanlardan, -> Fiziksel Katman ve Veri Bağlantı Katmanı için "Ethernet" ve "Wireless" protokolleri kullanılmaktadır. -> Ağ Katmanı için, aileye ismini veren, "IP" isimli protokol kullanılmaktadır. -> Aktarım Katmanı için "TCP" ve "UDP" protokolleri kullanılmaktadır. -> Uygulama Katmanı için, "TCP" protokolünün üzerine gelen, HTTP, TELNET, SSH, POP3, IMAP gibi pek çok protokol kullanılmaktadır. Tabii IP protokol ailesinde bu hiyerarşik yapıyla ilgili olmayan irili ufaklı pek çok protokol de bulunmaktadır. Şimdi de katmanlardaki protokolleri inceleyelim. >> "IP" : Ailenin en önemli ve taban protokolülüdür. Temel protokol olduğundan, tek başına kullanıldığında, sadece bir paket gönderme ve alma işini gerçekleştirir, ki bu paketler aslında "IP Package" olarak da adlandırılır. Bundan dolayıdır ki bu protokolün tek başına kullanılması seyrek rastlanılan durumdur. Uygulamalarda "Transport Layer" da bulunan "TCP" & "UDP" protokolleri kullanılır ki bunlar da esasında "IP" nin üzerine oturtulmuştur. "TCP" protokolü daha sık kullanıldığından, genellikle "TCP/IP" ismiyle geçer. "IP" protokolüne göre ağa katılan her bir cihaza "host" denilir. Her "host" cihazının da bir mantıksal adresi bulunur ki buna "IP Address" denilir. "IP" protokolü ise temelde ikiye ayrılır. Bunlar "IPv4" ve "IPv6" şeklindedir. Bu ikisi arasındaki temel fark "IPv4" olanlarda "IP Address" uzunluğu 4 bayt iken, "IPv6" olanlarda ise 16 bayt uzunluğundadır. >> "TCP" : Bu protokol bağlantılı ("connection-oriented") bir protokoldür. Yani buradaki bağlantılı olma ile kastedilen, "IP Package" ile yapılan mantıksal bir bağlantı olmasıdır. Bir diğer deyişle gönderen taraf ile alan taraf birbiriyle tanışır ve haberleşmenin güvenliği açısından işlemler sırasında birbirleriyle konuşabilirler. Dolayısıyla akla "Client-Server" tarzını getirtir. Bu yönüyle "TCP" protokolü güvenilir("reliable") bir protokoldür. Çünkü alıcı ile gönderen arasında mantıksal bir bağlantı olduğundan, yolda kaybolan paketlerin telafisi mümkündür. Böylece alıcı taraf, gönderenin gönderdiklerini, eksiksiz ve bozulmadan aldığını bilir. Aynı zamanda "TCP" de bir Akış Kontrol("Flow Control") mekanizması da vardır. Bu sayede alıcı taraf, kendi tamponunun taşması durumunda, göndericiyi durdurabilmektedir. Diğer yandan bu protokol "stream" tabanlıdır. Yani, tıpkı boru haberleşmesinde olduğu gibi, gönderen tarafın bilgilerinin bir kuyruk sistemi eşliğinde oluşturulması ve alıcının istediği kadar byte'ı parça parça okuyabilmesi demektir. "stream" tabanlı haberleşmenin oluşturulması için gönderilecek bilginin "IP Package" haline getirilmesi ve numaralandırılması, daha sonra hedefte tekrar birleştirilmesi gerekmektedir. Örneğin, biz bir "host" cihazından başka bir "host" cihazına 10000 bayt büyüklüğünde bir bilgi göndermek isteyelim. İlk önce bu 10000 bayt paketlere ayrılır ve her birisine bir numara verilir. Daha sonra hedefe gönderilir ve orada tekrar birleştirilir. Öte yandan gönderen tarafın gönderdiği sıranın alıcı tarafça korunması da bir zorunluluk değildir. Yani ilk gönderdiğimiz, karşı tarafa en son ulaşan paket olabilir. >> "UDP" : Bu protokol bağlantısız ("connectionless") bir protokoldür. Yani "TCP" deki gibi alıcı ile gönderici arasında bir haberleşme, el sıkışma durumu yoktur. Bu yönüyle akla televizyon yayınlarını getirtir. Verici görüntüyü yollar ancak alıcının alıp almadığıyla ilgilenmez. Vericinin görüntüyü yollaması için alıcıyla bağlantı kurması gerekmemektedir. Bu yönüyle "UDP" protokolü güvenilir olmayan("unreliable") bir protokoldür. Alıcı ile gönderici arasında bir haberleşme olmadığından, bilginin iletilip iletilmediği meçhuldür. Benzer şekilde, bu protokolde, bir Akış Kontrol("Flow Control") mekanizması da bulunmamaktadır. Diğer yandan bu protokol "datagram" tabanlıdır. Yani, tıpkı mesaj kuyruklarında olduğu gibi, bilginin paket paket iletilmesi demektir. Bu protokolde alıcı taraf, gönderen tarafın tüm paketini, tek hamlede almak zorundadır. Dolayısıyla, "stream" tabanlıda olduğu gibi, gönderilecek verinin paketlere ayrılması ve hedefte tekrar birleştirilmesi gibi bir şey söz konusu değildir. Yine bu protokolde de gönderim sırasının korunacağı garanti değildir. Bütün bunları göz önüne aldığımız neden en çok kullanılan protokolün "TCP" protokolü olduğunu anlayabiliriz. Pekiyi "IP" protokol ailesine bağlı "host" cihazları arasındaki haberleşme için gönderilen paketin hedef "host" cihazında hangi programa ilişkin olduğu nasıl belirlenmektedir? Çünkü bir "host" cihazında farklı programlar farklı "host" cihazları ile haberleşiyor olabilir. İşte gönderilen paketlerin o "host" cihazında ayrıştırılması için "Protocol Port Number" isminde bir içsel numara daha uydurulmuştur. Şimdi de bu içsel numarayı inceleyelim. >> "Protocol Port Number" : Başlıkta da belirtildiği gibi gelen paketlerin o "host" içerisindeki hangi programa ilişkin olduğunu belirten numaradır. Bu numarayı şirketlerin içsel dahili numarası olarak da görebiliriz. Bu numaralar "IPv4" ve "IPv6" da 2 baytle ifade edilmektedir. İlk 1024 numara "IP" protokol ailesinin uygulama katmanındaki protokoller için ayrılmış olup, bunlara "Well Known Ports" denilmektedir. Bu nedenle programcılar numara belirlerken 1024'ten büyük olacak biçimde belirleme yapmaları gerekir. Bu numara "TCP" ve "UDP" protokollerinde olup, "IP" protokolünde bulunmamaktadır. Dolayısıyla bir "host", "TCP" ya da "UDP" kullanarak bir bilgi gönderecekse, bilginin gönderileceği "host" un "IP" numarasını ve bilginin orada kime gönderileceğini anlatan port numarasını belirtmek zorundadır. Aynı şekilde bilgiyi almak isteyen program da kendisinin hangi port numarasıyla ilgilendiğini de belirtmek durumundadır. Kullanım kolaylığı sağlaması açısından, "IP" numarası ve port numarası çiftine, "IP End Point" de denilmektedir. Öte yandan yukarıda "IP Package" kavramından bahsetmiştik. Şimdi de onun detaylarına değinelim. >> "IP Package" : Anımsanacağı üzere "TCP" ve "UDP" protokollerinin "IP" protokolü üzerine oturdulduğunu belirtmiştik. Dolayısıyla biz "TCP" kullanarak bir veri göndermek istediğimizde, aslında önce "TCP Package" oluşturulur. Ancak alt seviyedeki protokol "IP" olduğundan, gönderimin "IP Package" olarak yapılması gerekmektedir. Bir "IP Package" ise iki kısımdan oluşur. Bunlar "IP Header" ve "IP Data" şeklinde olup, gösterimi aşağıdaki gibidir. +-------------------------+ | IP Header | +-------------------------+ | IP Data | +-------------------------+ Bu kısımlardan, -> "IP Header" : Söz konusu paketin hedef "host" cihazına ulaştırılabilmesi için gerekli bilgiler bulunur. -> "IP Data" : Gönderilecek asıl bilginin bulunduğu kısımdır. İşte "TCP Package" / "UDP Package" ise bu kısımda bulunur. Bu paketin(IPv4 için olanı) detaylı gösterimi ise aşağıdaki gibidir. <------- Byte 1 -------><------- Byte 2 -------><------- Byte 3 -------><------- Byte 4 -------> +-----------+-----------+----------------------+-----------------------------------------------+ ^ | Version | IHL | Type of Service | Total Length | (4 bytes) | | (4 bits) | (4 bits) | (8 bits) | (16 bits) | | +-----------+-----------+----------------------+-----------+-----------------------------------+ | | Identification | Flags | Fragment Offset | (4 bytes) | | (16 bits) | (3 bits) | (13 bits) | | +-----------------------+----------------------+-----------+-----------------------------------+ | | Time to Live (TTL) | Protocol | Header Checksum | (4 bytes) | 20 bytes | (8 bits) | (8 bits) | (16 bits) | | +-----------------------+----------------------+-----------------------------------------------+ | | Source IP Address (32 bits) | (4 bytes) | +----------------------------------------------------------------------------------------------+ | | Destination IP Address (32 bits) | (4 bytes) | +----------------------------------------------------------------------------------------------+ v | Segment (L4 protocol (TCP/UDP) + Data) | +----------------------------------------------------------------------------------------------+ Pekiyi "TCP Package" / "UDP Package" kavramları da nedir? >>> "TCP Package" / "UDP Package": Bu paketlerin görünümleri ise aşağıdaki gibidir. -> "TCP Package" : Aşağıdaki gibi gösterilebilir. +-------------------------+ | IP Header | +-------------------------+ <---+ | TCP Header | | +-------------------------+ IP Data | TCP Data | | +-------------------------+ <---+ -> "UDP Package" : Aşağıdaki gibi gösterilebilir. +-------------------------+ | IP Header | +-------------------------+ <---+ | UDP Header | | +-------------------------+ IP Data | UDP Data | | +-------------------------+ <---+ Şekillerden de görüleceği üzere "TCP Package" / "UDP Package" ın "header" ve "data" kısımları aslında "IP Package" ın "data" kısmı gibi oluşturulmaktadır. Böylece yolculuk eden paket aslında bir "TCP Package" / "UDP Package" değil "IP Package" tır. Bu paketlerin detaylı gösterimleri ise aşağıdaki gibidir. >>> "TCP Package" : <------- Byte 1 -------><------- Byte 2 -------><------- Byte 3 -------><------- Byte 4 -------> +----------------------------------------------+-----------------------------------------------+ ^ | Source Port | Destination Port | (4 bytes) | | (16 bits) | (16 bits) | | +----------------------------------------------+-----------------------------------------------+ | | Sequence Number | (4 bytes) | | (32 bits) | | +----------------------------------------------------------------------------------------------+ | | Acknowledgement Number | (4 bytes) | | (32 bits) | | 20 bytes +-----------+----------------+-----------------+-----------------------------------------------+ | |Header Len.| Reserved | Control Bits | Window Size | (4 bytes) | | (4 bits) | (6 bits) | (6 bits) | (16 bits) | | +-----------+----------------+-----------------+-----------------------------------------------+ | | Checksum | Urgent | (4 bytes) | | (16 bits) | (16 bits) | | +----------------------------------------------+-----------------------------------------------+ v | Options | | (0 or 32 bits) | +----------------------------------------------------------------------------------------------+ | Application Layer Data | | (Size Varies) | +----------------------------------------------------------------------------------------------+ >>> "UDP Package" : <------- Byte 1 -------><------- Byte 2 -------><------- Byte 3 -------><------- Byte 4 -------> +----------------------------------------------+-----------------------------------------------+ ^ | Source Port | Destination Port | (4 bytes) | | (16 bits) | (16 bits) | | +----------------------------------------------+-----------------------------------------------+ | 8 bytes | Header Length | Checksum | (4 bytes) | | (16 bits) | (16 bits) | | +----------------------------------------------+-----------------------------------------------+ v | Application Layer Data | | (Size Varies) | +----------------------------------------------------------------------------------------------+ Öte yandan protokollerde işlemcinin "endian" özelliği de önemlidir. "IP" ailesi "Big Endian" formata göre tasarlanmıştır. Bu formata, protokol aileleri arasında, "Network Byte Ordering" de denmektedir. Dolayısıyla bizlerin kullanacağımız değerleri ilk önce "Big Endian" formatına dönüştürmesi gerekmektedir. Pekiyi bu protokoller arka planda hangi kütüphaneyi kullanmaktadır? Esasında bu tip haberleşme işletim sisteminin çekirdekleri tarafından yapılmaktadır. Tabii "user-mode" programlar için sistem çağrılarını yapan, bir takım gereksinimleri de karşılayan fonksiyonlara ihtiyaç vardır. Bu ihtiyacı karşılayan ve en çok başvurulan kütüphane ise "socket" kütüphanesidir. >> "socket" Kütüphanesi: 1983 yılında BSD işletim sisteminin 4.2 sürümünde geliştirilmiştir ve pek çok UNIX türevi sistem bu kütüphaneyi aynı biçimde benimsemiştir. Microsoft'un Windows sistemleri de yine bu API kütüphanesini, "Winsock" ya da kısaca "WSA (Windows Socket API)" adıyla, desteklemektedir. Windows tarafındaki bu kütüphane hem klasik BSD soket API fonksiyonlarını hem de başı "WSAXXX" ile başlayan Windows'a özgü API fonksiyonlarını barındırmaktadır. Yani UNIX/Linux sistemlerinde yazdığımız soket programlarını küçük değişikliklerle Windows sistemlerine de "port" edebiliriz. Diğer yandan "socket" kütüphanesi yalnızca "IP" protokol ailesi için tasarlanmış bir kütüphane değildir. Bütün protokollerin ortak kütüphanesidir. Bu nedenle kütüphanedeki fonksiyonlar daha genel biçimde tasarlanmıştır. Dolayısıyla "socket" kütüphanesindeki fonksiyonları kullanırken, aslında arka planda işlemler "TCP/IP" ve "UDP/IP" protokollerine uygun bir biçimde gerçekleştirilmektedir. Örneğin biz "send" isimli "socket" fonksiyonu ile bir bilgiyi göndermek istediğimizde, aslında bu fonksiyon arka planda bir TCP paketi dolayısıyla da bir IP paketi oluşturarak protokole uygun bir biçimde, bu bilgiyi göndermektedir. "socket" kütüphanesinin yalnızca bir API arayüzü olduğuna dikkat ediniz. Son olarak "socket" kütüphanesi POSIX tarafından da desteklenmektedir. Bu kütüphanedeki fonksiyonların önemli bir bölümü ise "" içerisinde bulunur. Fakat bu başlık dosyasına ek olarak, kullanacağımız diğer fonksiyonlar için, başka başlık dosyalarının da eklenmesi gerekebilir. Kursun devamındaki konuların işleniş sırası şu şekilde olacaktır: -> "TCP/IP", "Client-Server" programların oluşturulması konusunu ele alacağız. -> Sonra "TCP/IP" haberleşmesinin bazı protokol detaylarından bahsedeceğiz. -> Sonra da "UDP/IP" haberleşme üzerinde duracağız. -> Son olarak da "Client-Server" arasındaki haberleşmenin detayları üzerinde duracağız. Bu konulardan, >>> "TCP/IP" : Bir "TCP/IP" uygulamasında "server" ve "client" olmak üzere iki ayrı program yazılır; "TCP Server Program" ve "TCP Client Program". Biz önce "TCP Server Program" ın daha sonra da "TCP Client Program" ın yazımı üzerinde duracağız. >>>> "TCP Server Program" : Bu program tipik olarak aşağıdaki fonksiyonları sırayla çağırarak gerçekleştirilir. "socket" > "bind" > "listen" > "accept" > "recv/read" Bu fonksiyonlar birer POSIX fonksiyonu olup, fonksiyonlardan, -> "socket" : Haberleşme için gerekli "socket" nesnesini oluşturur. Bu fonksiyon ilgili "socket" nesnesini oluşturur ve ona ilişkin bir "File Description" verir. Biz de diğer fonksiyonlarda bu dosya betimleyicisini kullanırız. Fonksiyonun prototipi aşağıdaki gibidir. #include int socket(int domain, int type, int protocol); Fonksiyonun birinci parametresi, kullanılacak protokol ailesini belirtir. Bu parametre "AF_XXX(Address Family)" biçimindeki sembolik sabitlerden biri olarak girilir. "IPv4" için bu parametreye "AF_INET", "IPv6" için "AF_INET6" girilmelidir. Pekala başka sembolik sabitler de mevcuttur. Fonksiyonun ikinci parametresi kullanılacak protokolün "stream" tabanlı mı yoksa "datagram" tabanlı mı olacağını belirtmektedir. "stream" tabanlı olanlar için "SOCK_STREAM", "datagram" olanlar için "SOCK_DGRAM" sembolik sabitleri kullanılmalıdır. Ancak başka sembolik sabitler de mevcuttur. Bu parametreye "TCP" protokolü için "SOCK_STREAM", "UDP" protokolü için "SOCK_DGRAM" sembolik sabitleri geçilmelidir. Fonksiyonun üçüncü parametresi ise "Transport Layer" daki protokolü belirtmektedir. Ancak zaten ikinci parametreden "Transport Layer" daki protokol anlaşılıyorsa, üçüncü parametre "0" olarak geçilebilmektedir. Yani ikinci parametreye "TCP" için "SOCK_STREAM" ve "UDP" için de "SOCK_DGRAM" geçmişken, üçüncü parametreye direkt "0" geçebiliriz. Pekala bu parametreye "TCP" için "IPPROTO_TCP", "UDP" için "IPPROTO_UDP" sembolik sabitlerini geçebiliriz. Fakat bu sembolik sabitler "" içerisindedir. Fonksiyonun geri dönüş değeri başarı durumunda "fd" değerine, hata durumunda "-1" değerine geri döner ve "errno" değişkeni uygun değişkene çekilir. Bu fonksiyonun, tıpkı "open" gibi, Dosya Betimleyici Tablosundaki en düşük indeks değerini, yani "fd" yi, döndürdüğüne dikkat ediniz. -> "bind" : "socket" nesnesini oluşturduktan sonra bu nesneyi bir "port" ile bağlamalıyız. Bu işlem "server" ın hangi "port" a bakacağı ve hangi "network" arayüzünden (kartından) gelen bağlantı isteklerini kabul edeceği belirlenir. Bu fonksiyon dinleme işlevini gerçekleştirmez, sadece "socket" nesnesine bu bilgileri yerleştirir. Fonksiyonun prototipi aşağıdaki gibidir. #include int bind(int socket, const struct sockaddr *addr, socklen_t addrlen); Fonksiyonun birinci parametresi, oluşturduğumuz "socket" nesnesine ilişkin "fd" değeridir. İkinci parametre ise, her ne kadar "sockaddr" yapı türünden bir adres değeri de olsa, kullandığımız protokol ailesine ait olan bir yapı türündendir. Bu yüzdendir ki protokolümüze ait olan yapı türünden nesneyi bu fonksiyona geçerken türünü "sockaddr" yapı türüne dönüştürmemiz gerekmektedir. Burada "IPv4" için "sockaddr_in" yapısı, "IPv6" için "sockaddr_in6" yapısı kullanılır. Programcı ilgili yapıyı doldurduktan sonra fonksiyona geçmelidir. Üçüncü parametre ise ikinci parametredeki yapının büyüklük bilgisidir. "sockaddr_in" yapısı aşağıdaki gibi tanımlıdır. #include struct sockaddr_in { sa_family_t sin_family; in_port_t sin_port; // 'uint16_t' struct in_addr sin_addr; }; Yapının "sin_family" isimli elemanı, kullandığımız protokol ailesini belirtir. Yani "socket" fonksiyonunun birinci parametresine geçtiğimiz sembolik sabiti kullanmalıyız. Yapının "sin_port" isimli eleman ise dinleyeceğimiz "port" numarasını belirtir. Yapının "sin_addr" isimli elemanı ise dinleyeceğimiz "IP" numarasını belirtir. Bu eleman ise "in_addr" yapı türünden olup, aşağıdaki gibi tanımlıdır. #include struct in_addr { in_addr_t s_addr; // 'uint32_t' }; "in_addr" yapısının "s_addr" isimli elemanı İşaretsiz Tam Sayı türündendir. Bu elemana "INADDR_ANY" sembolik sabitini atarsak, bütün "network" kartlarından gelen istekleri kabul etmiş oluruz. Gerek "sockaddr_in" yapısının gerek "in_addr" yapısının "netinet/in.h" içerisinde tanımlandığına dikkat ediniz. Fonksiyon başarı durumunda "0", hata durumunda "-1" değerine geri döner ve "errno" değişkenini uygun değere çeker. İşte, yukarıda açıkladığımız "endian" kavramı burada devreye girmektedir. Artık "sockaddr_in" yapısının "sin_port" ve "in_addr" yapısının "sin_addr" isimli elemanları için kullandığımız değerlerin "Big Endian" olduğuna dikkat etmeliyiz. Fakat bunun için fonksiyonlar da bulundurulmuştur. Elimizdeki makinanın "endian" durumundan bağımsız, kullanacağımız parametreyi "Big Endian" formatına dönüştüren bu fonksiyonlar ise şunlardır; "htons" ve "htonl". Bu fonksiyonlar elimizdeki değeri "Big Endian" formatına dönüştürürler. Fonksiyonların prototipi aşağıdaki gibidir. #include uint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort); Fonksiyonlar sırasıyla "long" türünden ve "short" türünden değerleri argüman olarak alırlar ve "Big Endian" formatta geri döndürürler. Pekala bu fonksiyonların tersi işlevini yapan, "ntohl" ve "ntohs" isimli fonksiyonlar da vardır. Bunların prototipi aşağıdaki gibidir. #include uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort); -> "listen" : Aktif bir şekilde o "port" üzerinden dinleme yapmak için bu fonksiyon çağrılmalıdır. Fonksiyonun prototipi aşağıdaki gibidir. #include int listen(int socket, int backlog); Fonksiyonun birinci parametresi, o "socket" nesnesine ilişkin "fd" değeridir. İkinci parametre ise bir kuyruk uzunluğu belirtmektedir. Bu kuyruk, işletim sistemi tarafından "listen" çağrısından sonra ilgili "port" a gelen bağlantı isteklerinin yerleştirildiği kuyruktur. Bu kuyruğun uzun tutulması, bağlantı isteklerinin kaçırılmamasına olanak tanır. Linux sistemlerinde varsayılan değer "128" biçiminde olup, "/proc/sys/net/core/somaxconn" dosyasındaki değeri değiştirerek, bu varsayılan değeri de değiştirebiliriz. Fonksiyon başarı durumunda "0" değerine, hata durumunda ise "-1" değerine geri döner ve "errno" değişkenini uygun değere çeker. Bu fonksiyon blokeye yol açmamaktadır. Son olarak bu fonksiyon işletim sisteminin "firewall" koruma mekanizmasına takılabilir, işletim sistemi konuyla ilişkili olarak uyarı verebilir. Dolayısıyla dinediğimiz "port" u açık hale getirmemiz gerekebilir. -> "accept" : Bu fonksiyon ise ilgili kuyruktaki bağlantı isteklerini "get" eden fonksiyondur. Çünkü bu fonksiyon bağlantı kuyruğuna bakar, orada bir bağlantı isteği varsa onu alır ve hemen geri döner. Eğer orada bir bağlantı isteği yoksa, default durumda, blokeye yol açar. Fonksiyonun prototipi şöyledir. #include int accept(int socket, struct sockaddr *address, socklen_t *address_len); Fonksiyonun birinci parametresi, o "socket" nesnesine ilişkin "fd" değeridir. İkinci parametre ise bağlantı kuyruğundaki istek bilgilerinin yerleştirileceği "sockaddr" yapı türünden bir adres bilgisidir. Yine, "bind" fonksiyonunda olduğu gibi, "sockaddr" yapısı genel bir tür belirtir. Dolayısıyla Burada "IPv4" için "sockaddr_in" yapısı, "IPv6" için "sockaddr_in6" yapısı türünden değişkenin adresini geçeceğiz. Tabii yine değişkenimizin türünü "sockaddr" türüne dönüştürdükten sonra fonksiyona geçmeliyiz. Bu parametreye geçtiğimiz yapı türünden nesnenin içi artık fonksiyon tarafından DOLDURULACAKTIR. Çünkü "client" program "server" programa bağlanırken de bir "IP" ve "port" numarası belirtir. İşte bağlantı kuyruğundaki istekler, "client" programın bu değerlerini içermektedir. "accept" ile bunları temin etmiş olacağız. Örneğin, "client" programın "IP" adresinin "178.231.152.127", kullandığı "port" numarasının da "52310" olduğunu varsayalım. "server" programınkiler ise sırasıyla "176.234.135.196" ve "55555" biçiminde olsun. Dolayısıyla "Server-Client" arasındaki bağlantı şeması takribi olarak aşağıdaki gibi olacaktır. Client Server (178.231.152.127:52310) ---> (176.234.135.196:55555) "accept" fonksiyonunun üçüncü parametresi, tıpkı "bind" fonksiyonundaki gibi, ikinci parametresindeki yapının büyüklük bilgisidir. Ancak bu parametrenin adres olduğuna dikkat ediniz. "accept" fonksiyonu yine bu adrese yazma yapacaktır. Böylelikle "client" programın kullandığı protokolün ne olduğunu anlayabileceğiz. Çünkü her protokolde farklı olan "sockaddr" türünün de büyüklük bilgisi farklıdır. Fonksiyon başarısızlık durumunda "-1" değerine geri döner ve "errno" değişkenini uygun değere çeker. Ancak başarı durumunda, o "client" a özel yeni bir "socket" betimleyicisine geri döner. Artık o "client" ile haberleşmek için fonksiyonun geri döndürdüğü betimleyiciyi kullanmalıyız. Buraya kadar, "server" program içerisinde, iki farklı "socket" nesnesi oluşturuldu. Bunlardan birisi ilk başta "socket" fonksiyonu ile elde ettiğimiz, diğeri de "accept" ile elde ettiğimiz. İşte "socket" fonksiyonu ile elde ettiğimiz ve "bind", "listen", "accept" fonksiyonlarına argüman olarak geçtiğimiz "socket" nesnesi için "TCP/IP" terminolojisinde "Passive Socket", "accept" ile ettiğmize ise "Active Socket / Listening Socket" denir. Bu soket nesnelerinden "Passive Socket" yalnızca bir kez oluşturulurken, "Active Socket" ise bağlantı isteği yollayan "client" adedince oluşturulur. Dolayısıyla "socket", "bind" ve "listen" fonksiyonlarını bir kez çağırmamız gerekirken, "accept" fonksiyonunu bir döngü içerisinde çağırmalıyız. Böylece, döngünün her turunda, her "client" için farklı "socket" betimleyicisi elde edeceğiz. Pekiyi "accept" ile elde ettiğimiz bağlantı isteklerindeki "client" programların "IP" adreslerini ve kullandıkları "port" numaralarınıı nasıl yazdıracağız? Aslında bu konu için şu noktalara dikkat etmemiz gerekmektedir: -> "accept" ile elde ettiğimiz bu bilgiler "Big Endian" formatında, yani Network Byte Ordering" formatında olduğundan, bunları kullanmak için "ntohl" ve "ntohs" fonksiyonlarını kullanmamız gerekmektedir. -> "IP" adresleri genellikle "Noktalı Desimal Format(Dotted Decimal Format)" denilen bir formatta yazıya dökülmektedir. Bütün bunlar ışığında kullanacağımız iki fonksiyon vardır. Bunlar, "inet_ntoa" ve "inet_addr" isimli POSIX fonksiyonlarıdır. Bu fonksiyonları sırasıyla ilgili "IP" adresini yazı formatına dönüştürür ve aldığı yazıyı "IP" adresi formatına dönüştürür. Fonksiyonların prototipleri şöyledir. #include char *inet_ntoa(struct in_addr in); in_addr_t inet_addr(const char *cp); Buradaki "inet_ntoa" fonksiyonu argüman olarak bir "IP" adresi alır ve geriye statik ömürlü bir yazı döndürür. Bu yazı "Dotted Decimal Format" biçimindedir. Fonksiyon başarısız olamamaktadır. FONKSİYONUN ARGÜMAN OLARAK ADRES BİLGİSİ ALMADIĞINA DİKKAT EDİNİZ. Fonksiyonun geri döndürdüğü yazı statik ömürlü olduğundan "thread-safe" OLMADIĞINA ve "free" işlemine gerek kalmadığına da DİKKAT EDİNİZ. Buradaki "inet_addr" ise "inet_ntoa" fonksiyonunun tersi işlemi yapmaktadır. "Dotted Decimal Format" biçimindeki bir yazıyı argüman olarak alır ve "IP" adresine dönüştürür eğer başarılı olursa. Başarısızlık durumunda "(in_addr_t)(-1)" değerine geri döner. Ancak karşılaştırma yaparken direkt "-1" ile de karşılaştırma yapsak olur çünkü "in_addr_t" türü işaretsiz olduğundan, "-1" ile karşılaştırırken de "-1" değeri de en büyük pozitif değere dönüştüreceğinden, sorun olmayacaktır. Fakat başarısızlığa ilişkin herhangi bir "errno" değeri tanımlı olmadığından, "errno" değişkeni uygun değere çekilmeyecektir. Son olarak "inet_ntoa" fonksiyonunun aldığı ve "inet_addr" fonksiyonunun geri döndürdüğü "IP" adresinin formatının "Big Endian" olduğuna dikkat ediniz. -> "recv" : Artık "client" programlar tarafından gönderilen bilgileri "get" edebiliriz. Fonksiyonun prototipi aşağıdaki gibidir. #include ssize_t recv(int socket, void *buffer, size_t length, int flags); Fonksiyonun ilk üç parametresi "read" fonksiyonunki gibidir. Yani sırasıyla ilgili betimleyici, okunan bilgilerin yerleştirileceği o tampon alanın başlangıç adresi ve okunmak istenen toplam bayt sayısıdır. Fonksiyonun dördüncü parametresi ise "MSG_PEEK", "MSG_OOB" ve "MSG_WAIALL" sembolik sabitlerinin "bitwise-OR" işlemine sokularak elde edilebilecek bir bayrak değeridir. Bu sembolik sabitlerin anlamlarına daha sonra değineceğiz. Sadece "MSG_PEEK" için şunu söyleyebiliriz; bilginin alındıktan sonra hala orada tutulacağını belirtir, yani silinmez. Pekala bu son parametreye "0" da geçilebilir. Bu durumda "recv" fonksiyonu "read" fonksiyonu gibi işlev görecektir. Diğer taraftan "recv" fonksiyonu varsayılan durumda blokeli çalışmaktadır, tıpkı borularda olduğu gibi. Yine, tıpkı borulardaki gibi, eğer en az bir bayt varsa okuyabildiği kadarını okur ve hemen okuduğu kadarına geri döner. Eğer hiç bayt yoksa, en az bir bayt gelene kadar blokeye yol açar. Fonksiyon başarı durumunda okuduğu bayt sayısına, hata durumunda ise "-1" değerine geri döner ve "errno" değişkenini uygun değere çeker. Eğer karşı taraf soketi kapatmışsa, yine borularda olduğu gibi, fonksiyon "0" ile geri döner. Dolayısıyla karşı tarafın soketi kapattığını bu fonksiyon ile anlayabiliriz. Bu yüzdendir ki "recv" fonksiyonunun başarısını hem "0" hem de "-1" e karşı kontrol etmeliyiz. "-1" olması ortada beklenmeyen bir hata olduğuna, "0" olması ise soketin karşı tarafça kapatıldığı anlamındadır. Eğer fonksiyon blokesiz çalıştırılırsa, bloke bekleme yerine başarısızlıkla geri döner. >>>> "TCP Client Program" : Bu program tipik olarak aşağıdaki fonksiyonları sırayla çağırarak gerçekleştirilir. "socket" > "bind" (isteğe bağlı) > "gethostbyname" (isteğe bağlı) > "connect" > "send/write" Bu fonksiyonlardan, -> "socket" : Bu fonksiyonun kullanım biçimi yine "server" programda olduğu gibidir. -> "bind" : Artık bir "socket" nesnesi oluşturduktan sonra onu bir "port" ile ilişkilendirmek zorunda değiliz. Genellikle "client" programlar "socket" nesnesini bir "port" ile ilişkilendirmezler. Ancak belli bir "port" kullanmak gerekiyorsa ilişkilendirir. Çünkü bir "bind" işlemi yapmazsa, işletim sistemi "connect" fonksiyonu sırasında boş bir "port" numarası atayacaktır. İşte işletim sistemi tarafından atanmış böyle "port" lara ise "Kısa Ömürü Port (Ephemeral Port)" denir. Ancak "client" program bağlantı sağlayabilmesi için "server" programın "IP" adresini ve "port" numarasını bilmek zorundadır. "IP" adreslerinin akılda tutulması zor olduğundan, o adreslerle eşleşen "host" isimleri oluşturulmuştur. Ancak "IP" ailesi bu "host" isimleriyle değil, yine "IP" numaralarıyla çalışmaktadır. İşte bu "host" isimlerle "IP" numaraları eşleştiren özel "server" programlara ise "Domain Name Server(DNS)" ismi verilmiştir. Bu "DNS" ler ise yine "IP" protokol ailesinin mensubudurlar, çünkü o ailedeki "DNS" isimli protokolü kullanırlar. Dolayısıyla "client" programın elinde "IP" adresi yerine host ismi varsa, "DNS" işlemi yaparak, o "host" ismine karşı gelen "IP" numarasını elde etmesi gerekir. Öte yandan iş bu "DNS" lerde "host" isimleri ile "IP" numaraları birebir karşılık gelmemektedir. Yani bir isim birden fazla numarayla ilişkili olabilirken, bir numara ise birden fazla isimle ilişkili olabilir. Dolayısıyla bizler bir takım fonksiyonlar kullanmak durumundayız. Bu fonksiyonlar, "gethostbyname" ve "gethostbyaddr" isimli geleneksel POSIX fonksiyonlarıdır. Ancak "deprecated" EDİLMİŞLERDİR. Dolayısıyla artık POSIX standartlarında mevcut DEĞİLDİRLER. Fakat bu fonksiyonlar yerine, "getnameinfo" ve "getaddrinfo" isimli POSIX fonksiyonu standarda eklenmiştir. Bu iki yeni fonksiyonun detaylarına aşağıda değinilecektir. -> "connect" : Artık "client" program bağlantıyı sağlayabilir. Fonksiyonunun prototipi aşağıdaki gibidir. #include int connect(int socket, const struct sockaddr *address, socklen_t address_len); Fonksiyonun birinci parametresi, "socket" fonksiyonu ile elde ettiğimiz soket betimleyicisini belirtir. İkinci parametre ise bağlanılacak "server" a ilişkin, yine "sockaddr" yapı türünden, adres belirtir fakat "IPv4" için "sockaddr_in" yapısını kullanmalıyız. Tabii "sockaddr_in" yapısının içini biz doldurduktan sonra, "const struct sockaddr" türüne dönüştürerek "connect" fonksiyonuna geçmeliyiz. Fonksiyonun üçüncü parametresi ise yine ikinci parametredeki yapı adresinin uzunluk bilgisidir. Fonksiyon başarı durumunda "0", hata durumunda "-1" değerine geri döner ve "errno" değişkenini uygun değere çeker. "sockaddr_in" içini doldururken "IP" adresi kısmına "127.0.0.1" adresini yazarsak, o anda çalışılan makinenin "IP" adresini kullanmış oluruz. Bu standart bir "IP" adresidir. Yani o anda çalışılan makinenin "IPv4" adresi "127.0.0.1" ile temsil edilmektedir. Bu adrese "loopback address" de denilmektedir. Bazı işletim sistemlerinde (Windows, Linux ve macOS) "localhost" ismi de o anda çalışılan makinenin "host" ismi olarak kullanılabilmektedir. Fakat bu isim standart DEĞİLDİR. Eğer "client" program bağlanmaya çalışırken aktif bir "server" yoksa ya da "server" programın bağlantı kuyruğu doluysa, "connect" fonksiyonu belli bir süre bekler ve başarısız olur. Bu durumda "errno" değişkeninin değeri ise "ECONNREFUSED(Connection Refused)" değerine çekilir. -> "send" : Artık "server" programa veri gönderebiliriz. Fonksiyonun prototipi aşağıdaki gibidir. #include ssize_t send(int socket, const void *buffer, size_t length, int flags); Fonksiyonun ilk üç parametresi "write" fonksiyonunki gibidir. Yani sırasıyla ilgili betimleyici, gönderilecek bilgilerin yerleştirileceği o tampon alanın başlangıç adresi ve gönderilmek istenen toplam bayt sayısıdır. Fonksiyonun dördündü parametresi ise "MSG_EOR", "MSG_OOB" ve "MSG_NOSIGNAL" sembolik sabitlerinin "bitwise-OR" işlemine sokularak elde edilebilecek bir bayrak değeridir. Bu sembolik sabitlerin anlamlarına daha sonra değineceğiz. Eğer bu son parametreye "0" geçilirse, fonksiyonun davranışı "write" gibi olacaktır. Fonksiyon tampona yazdığı bayt miktarına geri döner, başarı durumunda. Hata durumunda ise "-1" e geri döner ve "errno" değişkeni uygun değere çekilir. Aslında programın akışının "send" fonksiyonundan geri dönmesi, bilginin karşı tarafa iletildiği anlamına GELMEMEKTEDİR. Çünkü bu fonksiyon sadece bir tampona yazmaktadır. İşletim sistemiyse o tampondan, "TCP/IP" kullandığımız, için "IP" paketleri oluşturarak göndermektedir. Bir diğer deyişle biz tampona yazarız, sonrasında işlemci bir döngü içerisinde paket paket gönderme işlemine başlar. Pekiyi o anda iş bu tampon doluysa ne olacaktır? İşte "send" fonksiyonu gönderilecek bilginin tamamı tampona aktarılan kadar blokeye yol açar, POSIX standartlarınca. Eğer fonksiyon blokesiz çalıştırılırsa, bloke bekleme yerine başarısızlıkla geri döner. Öte yandan karşı taraf "socket" i kapatmışsa, bu fonksiyon varsayılan senaryoda "SIGPIPE" sinyalinin oluşmasına yol açar. Anımsanacağı üzere borularda okuma yapan program boruyu kapatmışsa ve yazma yapan taraf yazma yaptığında da yine bu sinyal oluşmaktaydı. Eğer bu sinyalin oluşması istenmiyorsa, "send" fonksiyonunun son parametresine "MSG_NOSIGNAL" bayrağı geçilmelidir. Bu durumda "send" fonksiyonu başarısız olur ve "errno" değişkeni "EPIPE" değerini alır. Buraya kadarki aşamalar doğrultusunda birbiriyle haberleşen iki program yazabiliriz. Pekiyi haberleşmenin sonlandırılma süreci nasıl olacaktır? Anımsanacağı üzere UNIX ve türevi sistemlerde "socket" nesnelerine ilişkin betimleyiciler birer "fd" olarak ele alınır. Dolayısıyla "close" fonksiyonları ile bu betimleyicileri kapatabiliriz. Pekala bu "close" çağrısını yapmasak bile, prosesin herhangi bir şekilde sonlanması üzerine işletim sistemi açık dosya betimleyicilerini kapatacağı için, bizim "fd" de yine kapatılacaktır. Ancak "Active Socket" nesnelerinin bu şekilde "close" ile kapatılması tavsiye edilmez. Çünkü, -> Direkt "close" ile betimleyiciyi kapatırsak, o "socket" nesnesine ilişkin veri yapıları ve bağlantı bilgileri de silineceği için, "send" / "recv" fonksiyonları ile bilgi gönderip/alamayabiliriz. Örneğin, "send" / "recv" işleminden hemen sonra "close" işlemi yaparsak, daha pakek karşıya gönderilmeden/tampondakilerin alması bitmeden silme işlemi gerçekleşebilir. Yani paketin karşıya gönderilmesi/alınması GARANTİ DEĞİLDİR. Çünkü "send" / "recv" sadece tampona yazmakta/tampondan okuma yapmaktadır. "close" ile bu tampon da silineceğinden, bilgiyi göndermiş olmamız / bilgiyi tampondan çekmiş olmamız kesin değildir. Bir diğer deyişle "close" çağrısını bilgisayarın kapatırken "power" tuşuna basmak gibi de düşünebiliriz. Halbuki "shutdown" fonksiyon çağrısı, bilgisayarı "shutdown" işlevi ile kapatmak, gibidir. Yani, işletim sistemini "shutdown" ettiğimizde tüm prosesler uygun biçimde sonlandırılıp sistem stabil olarak kapatılmaktadır. Dolayısıyla önce kontrollü bir şekilde haberleşme sonlandırılmalı, daha sonra "fd" ler kapatılmalıdır. Dolayısıyla bizler sırasıyla "shutdown" ve "close" fonksiyonlarını çağırmalıyız. İşte kapatma işleminin bu fonksiyonlar ile yapılması durumuna ise "Graceful Close (Zarif Kapatma)" denir. Bu fonksiyonlardan, -> "shutdown" : Bu fonksiyonun temelde üç işlevi vardır. Bunlar haberleşmeyi TCP çerçevesinde el sıkışarak sonlandırmak ki bu konu daha sonra işlenecektir, "send" ile tampona yazılanların gönderildiğinden emin olmak ve "read" / "write" işlemini sonlandırıp diğer işlemlere devam edebilmek ki bu duruma da aslında "Half Close" işlemi denir. "shutdown" fonksiyonunun prototipi aşağıdaki gibidir. #include int shutdown(int socket, int how); Fonksiyonunun birinci parametresi o "socket" nesnesin ilişkin betimleyici, ikinci parametresi ise sonlandırma biçimini belirtir. Bu ikinci parametre ise "SHUT_RD", "SHUR_WR" ve "SHUT_RDWR" sembolik sabitlerden birisini alır. Bu sabitler ise sırasıyla soketten artık okuma yapılmayacağına ki sokete yazma yapabiliriz, sokete artık yazma yapılmayacağına ki soketten okuma yapılabilir ve soketten ne okuma ne yazma yapılmayacağını belirtir. Bu sembolik sabitlerden "SHUR_WR" ve "SHUT_RDWR" kullanılması halinde "shutdown" fonksiyonu, "send" ile tampona yazdıklarımızın karşıya gönderilmesinden emin olur. Yani bu iki sembolik sabit kullanılırsa, bilgiler karşıya gönderilene kadar blokeye yol açar. "shutdown" fonksiyonu başarı durumunda "0", hata durumunda ise "-1" değerine geri döner. Bu fonksiyon sadece "Active Socket / Listening Socket" için çağrılmalıdır. -> "close" : Borularıdaki "close" fonksiyonudur. "TCP/IP" konusundaki şu noktalar da önemlidir: -> "socket" nesnelerine ilişkin "fd" ler "dup" işlemi ile çoğaltılabilmektedir. Bu durumda "close" sonrasında "socket" nesnesi henüz yok edilmez eğer ona ilişkin başka "fd" değişkenler de varsa. Benzer biçimde "fork" işlemi ile yine o "socket" nesnesine ait "fd" nin çoğaltılabileceğini unutmayınız. -> "send" ve "recv" fonksiyonlarında kullanılan tampona "Network Buffer" ismi verilmiştir. İşletim sistemi "send" için ayrı bir tampon, "recv" için ayrı bir tampon tutmaktadır. Anımsanacağı üzere "send" fonksiyonu kendi tamponuna yazıyor, "recv" ise kendi tamponundan okuma yapmaktadır. Bu iki tampon arasındaki bilgi aktarımı ise "IP" paketi olarak, işletim sisteminin görevidir. Dolayısıyla akışın "send" fonksiyonundan geri dönmesi demek tampona yazılanların karşı tarafa iletildiği ANLAMINA GELMEMEKTEDİR. -> "socket" programlamada bir tarafın tek bir "send" / "write" çağrısı ile tampona yazdıklarını diğer taraf birden fazla "read" / "recv" çağrısı ile alabilir. Benzer şekilde birden fazla kez çağrılan "send" / "write" ile tampona yazma yapıldığında, bu tek bir "read" / "recv" çağrısı ile alınmayabilir. Dolayısıyla bizler bir döngü içerisinde okuma ya da yazma yapmak durumunda kalabiliriz. Aşağıda garantili okumaya ilişkin bir örnek fonksiyon verilmiştir: ssize_t read_socket(int sock, char *buf, size_t len) { size_t left, index; left = len; index = 0; while (left > 0) { if ((result = recv(sock, buf + index, left, 0)) == -1) return -1; if (result == 0) break; index += result; left -= result; } return (ssize_t)index; } Bu fonksiyonun çalışması oldukça basittir. "recv" ile her defasında "left" kadar bayt okunmak istenmiştir. Ancak "left" kadar değil, "result" kadar bayt okunmuş da olabilir. Bu durumda "left", okunan miktar kadar azaltılmış ve "index" ise o miktar kadar artırılmıştır. Programın akışı bu fonksiyondan şu durumlarda çıkacaktır; Bağlantı kopması ve "recv" in başarısız olması, karşı tarafın soketi kapatması ve "recv" in "0" ile geri dönmesi, istenen miktar kadar okunmuş olması. Özetle; bir soketten "n-byte" okuma işlemi tek bir "recv" ile olmak zorunda değildir. Soket programlamaya yeni başlayanlar, sanki bir disk dosyasından ya da borudan bilgi okunuyor gibi, tek bir okuma çağrısı ile bunu yapma eğilimindedirler. Halbuki bu işlem yukarıdaki gibi bir döngüyle ya da "recv" fonksiyonuna "MSG_WAITALL" bayrağı girilerek yapılmak zorundadır. Fakat "MSG_WAITALL" bayrağı, alma tamponundan daha yüksek miktarda verilerin okunması için uygun olmayabilmektedir. Bu konu ileride ele alınacaktır. -> Bilindiği gibi "inet_ntoa" fonksiyonu dört baytlık "IPv4" adresini noktalı desimal formata, "inet_addr" fonksiyonu da bu işin tersini yapmaktaydı. İşte bu iki fonksiyonun "IPv6" adresini de kapsayan halleri de geliştirilmiştir. Bu fonksiyonlar ise sırasıyla "inet_ntop" ve "inet_pton" isimli fonksiyonlardır. Fonksiyonların prototipleri aşağıdaki gibidir. #include const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); int inet_pton(int af, const char *src, void *dst); Bu iki fonksiyonun birinci parametresi "IPv4" için "AF_INET", "IPv6" için "AF_INET6" olarak girilmelidir. Fonksiyonların ikinci parametresine; "inet_ntop" için dönüştürülecek nümerik "IPv4" ya da "IPv6" türündeki "IP" adresini temsil eden nesnenin adresini, "inet_pton" için noktalı desimal formatın bulunduğu yazının adresini alır. Fonksiyonların üçüncü parametresine; "inet_ntop" için dönüştürme sonucu elde edilen noktalı desimal formattaki yazının yazılacağı alanın başlangıç adresini, "inet_pton" için nümerik adresin yerleştirileceği adresi almakta ve bu adrese "IPv4" için 4 baytlık "IPv6" için 16 baytlık yerleştirme yapılır. Fonksiyonlar bu parametredeki adres alanlarına yazma YAPARLAR. "inet_ntop" fonksiyonunun son parametresi ise üçüncü parametresindeki dizinin uzunluğunu belirtir. Bu parametreye "IPv4" için "INET_ADDRSTRLEN", "IPv6" için "INET6_ADDRSTRLEN" sembolik sabitler girilebilir. Fonksiyonların geri dönüş değeri; "inet_ntop" için başarı durumunda üçüncü parametresine geçtiğimiz adres bilgisine ve başarısızlık durumunda "NULL" değerine, "inet_pton" için başarı durumunda "1" değerine ve başarısızlık durumunda "0" ya da "-1" değerine geri döner ki başarıszlığın kaynağı birinci parametre ise "-1", ikinci parametre ise "0" değeridir. Fonksiyonların kullanımı: "inet_ntop" için, char ntopbuf[INET_ADDRSTRLEN]; ... printf( "connected client ===> %s:%u\n", inet_ntop(AF_INET, &sin_client.sin_addr, ntopbuf, INET_ADDRSTRLEN), (unsigned)ntohs(sin_client.sin_port) ); "inet_pton" için, if (inet_pton(AF_INET, server_name, &sin_server.sin_addr.s_addr) == 0) { ... } biçimindedir. -> Anımsanacağı üzere "gethostbyname" fonksiyonu "deprecated" edildiği için yerine "getaddrinfo" isimli fonksiyon eklenmiştir. Bu fonksiyon da yine "IPv6" yı destekler niteliktedir. Fonksiyonunun prototipi aşağıdaki gibidir. #include int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res); Fonksiyonun birinci parametresi noktalı desimal formatındaki "IP" adresi veya "host" ismini alır. İkinci parametre ise port numarasını yazı olarak alır. Fakat "NULL" değeri geçilebilir. Bu durumda port numarası "0" olarak belirlenecektir. Fakat biz programcılar eğer port numarasının atıyorsak, bu parametreye "NULL" geçeriz. Diğer yandan bu ikinci parametreye "IP" ailesinin "Applicatino Layer" a ilişkin spesifik bir protokolün ismi de girilebilmektedir. Örneğin "http", "ftp" gibi. Bu durumda bu protokollere ilişikin port numaraları zaten bilindiğinden, o port numaraları girilmiş gibi işlem görülür. Fonksiyonun üçüncü parametresi ise nasıl bir "IP" adresi istediğimizi belirten filtreleme parametresidir. Bu parametre "addrinfo" yapı türünden "const" bir nesnenin adresini aldığından, ilgili nesnenin içini doldurduktan sonra fonksiyona geçmeliyiz. "addrinfo" yapı türü ise aşağıdaki gibi tanımlanmıştır. struct addrinfo { int ai_flags; int ai_family; int ai_socktype; int ai_protocol; socklen_t ai_addrlen; struct sockaddr *ai_addr; char *ai_canonname; struct addrinfo *ai_next; }; Bu yapının "ai_flags" elemanı pek çok bayrak değeri alabilmektedir. Bu değer 0 olarak da geçilebilir. Yapının "ai_family" elemanı ise "IPv4" için "AF_INET", "IPv6" için "AF_INET6", her ikisi için "AF_UNSPEC" sembolik sabitlerinden birisini alır. Yapının "ai_socktype" elemanı "0", "SOCK_STREAM" ya da "SOCK_DGRAM" sembolik sabitlerinden birisini alır. Yapının diğer elemanlarına ilişkin açıklamayı ilgili dökümandan temin edebiliriz. Ancak POSIX standartları yapının sadece ilk dört elemanını programcının doldurmasını, geri kalanlarını da sıfırlamasını tavsiye etmektedir. Pekala fonksiyonun bu üçüncü parametresine "NULL" değerini de geçebiliriz. Bu durumda ilgili "it referred to a structure containing the value zero for the ai_flags, ai_socktype, and ai_protocol fields, and AF_UNSPEC for the ai_family field" biçiminde olacaktır. Fonksiyonun son parametresi ise bir bağlı listenin ilk düğümünün adresini alır. Buradaki bağlı listedeki düğümleri birbirine bağlayan gösterici ise "addrinfo" yapısının "ai_next" isimli elemanıdır. Bu bağlı listede ise "host" a ilişkin birden fazla "IP" adresi olması durumunda doldurulur. Genellikle sadece bağlı listenin başındaki düğüm kontrol edilir fakat zaman zaman bağlı listenin baştan sonra gezinilmesi de gerekebilir. "getaddrinfo" fonksiyonu başarı durumunda "0", hata durumunda ise hata kodunun kendisine döner. Bu hata kodları "strerror" ile değil, "gai_strerror" ile yazıya dökülmelidir. "gai_strerror" fonksiyonunun prototipi aşağıdaki gibidir. #include const char *gai_strerror(int ecode); Öte yandan yukarıda bahsedilen bağlı listenin de günün sonunda boşaltılması gerekmektedir. Bunun için "freeaddrinfo" fonksiyonu çağrılır. Fonksiyonun prototipi şöyledir. #include void freeaddrinfo(struct addrinfo *ai); Fonksiyon yukarıda açıklanan bağlı listenin en başındaki düğümün adresini alır ve tüm bağlı listeyi boşaltır. "getaddrinfo" fonksiyonunun tipik kullanım biçimi ise aşağıdaki gibidir. struct addrinfo hints = {0, AF_INET, SOCK_STREAM}; ... if ((gai_result = getaddrinfo(server_name, server_port, &hints, &res)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(gai_result)); exit(EXIT_FAILURE); } for (ri = res; ri != NULL; ri = ri->ai_next) if (connect(client_sock, ri->ai_addr, ri->ai_addrlen) != -1) break; if (ri == NULL) exit_sys("connect"); freeaddrinfo(res); ... Bu fonksiyon sayesinde artık "inet_addr" ya da "inet_pton" ile "server address" bilgisinin noktalı desimal formatta olup olmadığını sorgulamaya ve duruma göre "gethostbyname" ile "DNS" işlemi yapmaya ARTIK GEREK KALMAMIŞTIR. Çünkü bu iki işlem artık "getaddrinfo" fonksiyonu ile yapılmaktadır. -> Anımsanacağı üzere "gethostbyaddr" fonksiyonu "deprecated" edildiği için yerine "getnameinfo" isimli fonksiyon eklenmiştir. Bu fonksiyon da yine "IPv6" yı destekler niteliktedir. Fonksiyonunun prototipi aşağıdaki gibidir. #include int getnameinfo(const struct sockaddr *addr, socklen_t addrlen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags); Fonksiyonun birinci parametresi "sockaddr_in" ya da "sockaddr_in6" yapısını almaktadır. İkinci parametre birinci parametredeki yapının uzunluğudur. Fonksiyonun sonraki dört parametresi sırasıyla noktalı hostun yazısal temsilin yerleştirileği dizinin adresi ve uzunluğu, port numarasına ilişkin yazının (servis ismi) yerleştirileceği dizinin adresi ve uzunluğudur. Son parametre "0" geçilebilir. Maksimum "host" ismi "NI_MAXHOST" ile maksimum servis ismi ise "NI_MAXSERV" ile belirtilmiştir. -> Bir taraf "IPv4" kullanırken diğer taraf "IPv6" kullanabilir. -> "socket" haberleşmesinde kullanılan iki program daha vardır; "getpeername" ve "getsockname" fonksiyonları. Bu fonksiyonlardan "getpeername" fonksiyonu, bağlı bir soketi parametre olarak alır ve karşı tarafın "IP" adresini ve "port" numarasını bize "sockaddr_in" ya da "sockaddr_in6" biçiminde verir. Tabii aslında "server", bağlantıyı yaptığında karşı tarafın bilgisini zaten "accept" fonksiyonunda almaktadır. Dolayısıyla bu bilgiyi saklayarak daha sonra kullanabiliriz. Eğer saklamamışsak, "getpeername" ile istediğimiz zaman "get" edebiliriz. Fonksiyonun prototipi aşağıdaki gibidir. #include int getpeername(int sock, struct sockaddr *addr, socklen_t *addrlen); Fonksiyonun birinci parametresi soket betimleyicisidir. İkinci parametre duruma göre karşı tarafın bilgilerinin yerleştirileceği "sockaddr_in" ya da "sockaddr_in6" yapı nesnesinin adresini alır. Son parametre ikinci parametredeki yapının uzunluğunu belirtmektedir. Eğer buraya az bir uzunluk girilirse kırpma yapılır ve gerçek uzunluk verdiğimiz adresteki nesneye yerleştirilir. Fonksiyon başarı durumunda 0 değerine, başarısızlık durumunda -1 değerine geri dönmektedir. Diğer fonksiyonumuz olan "getsockname" ise "getpeername" fonksiyonunun tersi işlemi yapmaktadır. Bu fonksiyon kendi bağlı soketimizin "IP" adresini ve "port" numarasını elde etmek için kullanılır. Genellikle bu fonksiyona gereksinim duyulmamaktadır. Burada bağlantı sağlandıktan sonra "client" programın bilgilerinin alınması ve gerektiğinde o bilgilerin kullanılması tavsiye edilir. Sık sık "gerpeername" çağrısı pek tavsiye edilmez. Fonksiyonun prototipi aşağıdaki gibidir. #include int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen); Fonksiyonun parametrik yapısı ve geri dönüş değeri getpeername fonksiyonundaki gibidir. -> "socket" haberleşmesinde "peer" kelimesi karşı tarafı betimlemektedir. Şimdi de "Client-Server" programlara ilişkin örnekleri inceleyelim: * Örnek 1, "Server-Client" program. /* client.c */ #include #include #include #include #include #include #include #define SERVER_NAME "127.0.0.1" #define SERVER_PORT 55555 #define BUFFER_SIZE 1024 void exit_sys(const char *msg); int main(void) { /* Creation of a 'socket' */ int client_sock; if ((client_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); /* Binding the 'socket' to a port */ /* // Optional { struct sockaddr_in sin_client; sin_client.sin_family = AF_INET; sin_client.sin_port = htons(50000); sin_client.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(client_sock, (struct sockaddr *)&sin_client, sizeof(sin_client)) == -1) exit_sys("bind"); } */ struct sockaddr_in sin_server; sin_server.sin_family = AF_INET; sin_server.sin_port = htons(SERVER_PORT); /* Evaluating an IP address through 'DNS'. */ struct hostent *hent; if ((sin_server.sin_addr.s_addr = inet_addr(SERVER_NAME)) == -1) { // 'gethostbyname' is absolute now. if ((hent = gethostbyname(SERVER_NAME)) == NULL) { fprintf(stderr, "gethostbyname: %s\n", hstrerror(h_errno)); exit(EXIT_FAILURE); } memcpy(&sin_server.sin_addr.s_addr, hent->h_addr_list[0], hent->h_length); } /* Sending connection requests to the 'server'. */ if (connect(client_sock, (struct sockaddr *)&sin_server, sizeof(sin_server)) == -1) exit_sys("connect"); printf("connected...\n"); /* Sending data to the 'server'. */ char buffer[BUFFER_SIZE]; char* str; for (;;) { printf("CSD:>"); fflush(stdout); if (fgets(buffer, BUFFER_SIZE, stdin) == NULL) continue; if ((str = strchr(buffer, '\n')) != NULL) *str = '\0'; if (send(client_sock, buffer, strlen(buffer), 0) == -1) exit_sys("send"); if (!strcmp(buffer, "quit")) break; } /* Closing the communication properly. */ close(client_sock); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } /* server.c */ #include #include #include #include #include #include #include #define SERVER_PORT 55555 #define BUFFER_SIZE 1024 void exit_sys(const char *msg); int main(void) { /* Creation of a 'socket' */ int server_sock; struct sockaddr_in sin_server; if ((server_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); /* Binding the 'socket' to a port */ sin_server.sin_family = AF_INET; sin_server.sin_port = htons(SERVER_PORT); sin_server.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(server_sock, (const struct sockaddr *)&sin_server, sizeof(sin_server)) == -1) exit_sys("bind"); /* Listening the port */ if (listen(server_sock, 8) == -1) exit_sys("listen"); /* Evaluating connection requests in the connection queue. */ printf("waiting for connection...\n"); int client_sock; struct sockaddr_in sin_client; socklen_t sin_len = sizeof(sin_client); if ((client_sock = accept(server_sock, (struct sockaddr *)&sin_client, &sin_len)) == -1) exit_sys("accept"); printf( "Connected Client => [%s:%d]\n", inet_ntoa(sin_client.sin_addr), ntohs(sin_client.sin_port) ); /* Reading data sent from the 'client'. */ char buffer[BUFFER_SIZE + 1]; ssize_t result; for (;;) { if ((result = recv(client_sock, buffer, BUFFER_SIZE, 0)) == -1) exit_sys("recv"); printf("[%jd] => ", (intmax_t)result); if (0 == result) break; buffer[result] = '\0'; if (!strcmp(buffer, "quit")) break; printf("[%s]", buffer); } /* Closing the communication properly. */ shutdown(client_sock, SHUT_RDWR); close(client_sock); close(server_sock); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2, "Server-Client" programlar genellikle komut satırından argüman alacak biçimde tasarlanırlar; "IP" adresini ve "port" numarasını. /* client.c */ #include #include #include #include #include #include #include #include #define SERVER_NAME "127.0.0.1" #define SERVER_PORT 55555 #define BUFFER_SIZE 1024 void exit_sys(const char *msg); int main(int argc, char* argv[]) { /* Checking the server IP address via 'Command Line Arguments' */ int server_port; if (argc < 2) server_port = SERVER_PORT; else if (argc == 3) server_port = atoi(argv[1]); else { fprintf(stderr, "wrong number of arguments!..\n"); exit(EXIT_FAILURE); } /* Creation of a 'socket' */ int client_sock; if ((client_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); /* Binding the 'socket' to a port */ /* // Optional { struct sockaddr_in sin_client; sin_client.sin_family = AF_INET; sin_client.sin_port = htons(50000); sin_client.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(client_sock, (struct sockaddr *)&sin_client, sizeof(sin_client)) == -1) exit_sys("bind"); } */ printf("Connecting to Port: %d\n", server_port); struct sockaddr_in sin_server; sin_server.sin_family = AF_INET; sin_server.sin_port = htons(server_port); /* Evaluating an IP address through 'DNS'. */ struct hostent *hent; if ((sin_server.sin_addr.s_addr = inet_addr(SERVER_NAME)) == -1) { // 'gethostbyname' is absolute now. if ((hent = gethostbyname(SERVER_NAME)) == NULL) { fprintf(stderr, "gethostbyname: %s\n", hstrerror(h_errno)); exit(EXIT_FAILURE); } memcpy(&sin_server.sin_addr.s_addr, hent->h_addr_list[0], hent->h_length); } /* Sending connection requests to the 'server'. */ if (connect(client_sock, (struct sockaddr *)&sin_server, sizeof(sin_server)) == -1) exit_sys("connect"); printf("connected...\n"); /* Sending data to the 'server'. */ char buffer[BUFFER_SIZE]; char* str; for (;;) { printf("CSD:>"); fflush(stdout); if (fgets(buffer, BUFFER_SIZE, stdin) == NULL) continue; if ((str = strchr(buffer, '\n')) != NULL) *str = '\0'; if (send(client_sock, buffer, strlen(buffer), 0) == -1) exit_sys("send"); if (!strcmp(buffer, "quit")) break; } /* Closing the communication properly. */ close(client_sock); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } /* server.c */ #include #include #include #include #include #include #include #include #define SERVER_PORT 55555 #define BUFFER_SIZE 1024 void exit_sys(const char *msg); int main(int argc, char* argv[]) { /* Checking the port number via 'Command Line Arguments' */ int server_port; if (argc == 1) server_port = SERVER_PORT; else if (argc == 2) server_port = atoi(argv[1]); else { fprintf(stderr, "wrong number of arguments!..\n"); exit(EXIT_FAILURE); } /* Creation of a 'socket' */ int server_sock; struct sockaddr_in sin_server; if ((server_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); /* Binding the 'socket' to a port */ sin_server.sin_family = AF_INET; sin_server.sin_port = htons(server_port); sin_server.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(server_sock, (const struct sockaddr *)&sin_server, sizeof(sin_server)) == -1) exit_sys("bind"); /* Listening the port */ printf("Listening Port: %d\n", server_port); if (listen(server_sock, 8) == -1) exit_sys("listen"); /* Evaluating connection requests in the connection queue. */ printf("waiting for connection...\n"); int client_sock; struct sockaddr_in sin_client; socklen_t sin_len = sizeof(sin_client); if ((client_sock = accept(server_sock, (struct sockaddr *)&sin_client, &sin_len)) == -1) exit_sys("accept"); printf( "Connected Client => [%s:%d]\n", inet_ntoa(sin_client.sin_addr), ntohs(sin_client.sin_port) ); /* Reading data sent from the 'client'. */ char buffer[BUFFER_SIZE + 1]; ssize_t result; for (;;) { if ((result = recv(client_sock, buffer, BUFFER_SIZE, 0)) == -1) exit_sys("recv"); printf("[%jd] bytes received: ", (intmax_t)result); if (0 == result) break; buffer[result] = '\0'; if (!strcmp(buffer, "quit")) break; printf("[%s]", buffer); } /* Closing the communication properly. */ shutdown(client_sock, SHUT_RDWR); close(client_sock); close(server_sock); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 3.0, Yine "Server-Client" programımız için belli başlı seçenekler de tayin edebiliriz. /* client.c */ #include #include #include #include #include #include #include #include #define DEF_SERVER_NAME "127.0.0.1" #define DEF_SERVER_PORT 55555 #define BUFFER_SIZE 4096 void exit_sys(const char *msg); /* ./client [-s server] [-p server_port] [-b client_port] */ int main(int argc, char *argv[]) { int client_sock; struct sockaddr_in sin_server, sin_client; struct hostent *hent; char buf[BUFFER_SIZE]; char *str; int option; int s_flag, p_flag, b_flag, err_flag; const char *server_name; int server_port, bind_port; s_flag = p_flag = b_flag = err_flag = 0; opterr = 0; while ((option = getopt(argc, argv, "s:p:b:")) != -1) { switch (option) { case 's': s_flag = 1; server_name = optarg; break; case 'p': p_flag = 1; server_port = atoi(optarg); break; case 'b': b_flag = 1; bind_port = atoi(optarg); break; case '?': if (optopt == 's' || optopt == 'p' || optopt == 'b') fprintf(stderr, "-%c option must have an argument!\n", optopt); else fprintf(stderr, "-%c invalid option!\n", optopt); err_flag = 1; } } if (err_flag) exit(EXIT_FAILURE); if (optind - argc != 0) { fprintf(stderr, "too many arguments!...\n"); exit(EXIT_FAILURE); } if (!s_flag) server_name = DEF_SERVER_NAME; if (!p_flag) server_port = DEF_SERVER_PORT; if ((client_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); if (b_flag) { sin_client.sin_family = AF_INET; sin_client.sin_port = htons(bind_port); sin_client.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(client_sock, (struct sockaddr *)&sin_client, sizeof(sin_client)) == -1) exit_sys("bind"); } sin_server.sin_family = AF_INET; sin_server.sin_port = htons(server_port); if ((sin_server.sin_addr.s_addr = inet_addr(server_name)) == -1) { if ((hent = gethostbyname(server_name)) == NULL) { fprintf(stderr, "gethostbyname: %s\n", hstrerror(h_errno)); exit(EXIT_FAILURE); } memcpy(&sin_server.sin_addr.s_addr, hent->h_addr_list[0], hent->h_length); } if (connect(client_sock, (struct sockaddr *)&sin_server, sizeof(sin_server)) == -1) exit_sys("connect"); printf("connected server...\n"); for (;;) { printf("csd>"); fflush(stdout); if (fgets(buf, BUFFER_SIZE, stdin) == NULL) continue; if ((str = strchr(buf, '\n')) != NULL) *str = '\0'; if (send(client_sock, buf, strlen(buf), 0) == -1) exit_sys("send"); if (!strcmp(buf, "quit")) break; } shutdown(client_sock, SHUT_RDWR); close(client_sock); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } /* server.c */ #include #include #include #include #include #include #include #include #define DEF_SERVER_PORT 55555 #define BUFFER_SIZE 4096 void exit_sys(const char *msg); /* ./server [-p port] */ int main(int argc, char *argv[]) { int server_sock, client_sock; struct sockaddr_in sin_server, sin_client; socklen_t sin_len; char buf[BUFFER_SIZE + 1]; ssize_t result; int option; int server_port; int p_flag, err_flag; p_flag = err_flag = 0; opterr = 0; while ((option = getopt(argc, argv, "p:")) != -1) { switch (option) { case 'p': p_flag = 1; server_port = atoi(optarg); break; case '?': if (optopt == 'p') fprintf(stderr, "-%c option must have an argument!\n", optopt); else fprintf(stderr, "-%c invalid option!\n", optopt); err_flag = 1; } } if (err_flag) exit(EXIT_FAILURE); if (optind - argc != 0) { fprintf(stderr, "too many arguments!...\n"); exit(EXIT_FAILURE); } if (!p_flag) server_port = DEF_SERVER_PORT; if ((server_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); sin_server.sin_family = AF_INET; sin_server.sin_port = htons(server_port); sin_server.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(server_sock, (struct sockaddr *)&sin_server, sizeof(sin_server)) == -1) exit_sys("bind"); if (listen(server_sock, 8) == -1) exit_sys("listen"); printf("listening port %d\n", server_port); printf("waiting for connection...\n"); sin_len = sizeof(sin_client); if ((client_sock = accept(server_sock, (struct sockaddr *)&sin_client, &sin_len)) == -1) exit_sys("accept"); printf("connected client ===> %s:%d\n", inet_ntoa(sin_client.sin_addr), ntohs(sin_client.sin_port)); for (;;) { if ((result = recv(client_sock, buf, BUFFER_SIZE, 0)) == -1) exit_sys("recv"); if (result == 0) break; buf[result] = '\0'; if (!strcmp(buf, "quit")) break; printf("%jd byte(s) received: \"%s\"\n", (intmax_t)result, buf); } shutdown(client_sock, SHUT_RDWR); close(client_sock); close(server_sock); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 3.1, Yukarıdaki örneklerde yazdığımız "Server-Client" programın, güncel fonksiyonlar kullanılmış hali: /* client.c */ #include #include #include #include #include #include #include #include #define DEF_SERVER_NAME "127.0.0.1" #define DEF_SERVER_PORT "55555" #define BUFFER_SIZE 4096 void exit_sys(const char *msg); /* ./client [-s server] [-p server_port] [-b client_port] */ int main(int argc, char *argv[]) { int client_sock; struct sockaddr_in sin_client; struct addrinfo hints = {0, AF_INET, SOCK_STREAM}; struct addrinfo *res, *ri; int gai_result; char buf[BUFFER_SIZE]; char *str; int option; int s_flag, p_flag, b_flag, err_flag; const char *server_name; int bind_port; const char *server_port; s_flag = p_flag = b_flag = err_flag = 0; opterr = 0; while ((option = getopt(argc, argv, "s:p:b:")) != -1) { switch (option) { case 's': s_flag = 1; server_name = optarg; break; case 'p': p_flag = 1; server_port = optarg; break; case 'b': b_flag = 1; bind_port = atoi(optarg); break; case '?': if (optopt == 's' || optopt == 'p' || optopt == 'b') fprintf(stderr, "-%c option must have an argument!\n", optopt); else fprintf(stderr, "-%c invalid option!\n", optopt); err_flag = 1; } } if (err_flag) exit(EXIT_FAILURE); if (optind - argc != 0) { fprintf(stderr, "too many arguments!...\n"); exit(EXIT_FAILURE); } if (!s_flag) server_name = DEF_SERVER_NAME; if (!p_flag) server_port = DEF_SERVER_PORT; if ((client_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); if (b_flag) { sin_client.sin_family = AF_INET; sin_client.sin_port = htons(bind_port); sin_client.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(client_sock, (struct sockaddr *)&sin_client, sizeof(sin_client)) == -1) exit_sys("bind"); } if ((gai_result = getaddrinfo(server_name, server_port, &hints, &res)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(gai_result)); exit(EXIT_FAILURE); } for (ri = res; ri != NULL; ri = ri->ai_next) if (connect(client_sock, ri->ai_addr, ri->ai_addrlen) != -1) break; if (ri == NULL) exit_sys("connect"); freeaddrinfo(res); printf("connected server...\n"); for (;;) { printf("csd>"); fflush(stdout); if (fgets(buf, BUFFER_SIZE, stdin) == NULL) continue; if ((str = strchr(buf, '\n')) != NULL) *str = '\0'; if (send(client_sock, buf, strlen(buf), 0) == -1) exit_sys("send"); if (!strcmp(buf, "quit")) break; } shutdown(client_sock, SHUT_RDWR); close(client_sock); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } /* server.c */ #include #include #include #include #include #include #include #include #define DEF_SERVER_PORT 55555 #define BUFFER_SIZE 4096 void exit_sys(const char *msg); /* ./server [-p port] */ int main(int argc, char *argv[]) { int server_sock, client_sock; struct sockaddr_in sin_server, sin_client; socklen_t sin_len; char buf[BUFFER_SIZE + 1]; char ntopbuf[INET_ADDRSTRLEN]; ssize_t result; int option; int server_port; int p_flag, err_flag; p_flag = err_flag = 0; opterr = 0; while ((option = getopt(argc, argv, "p:")) != -1) { switch (option) { case 'p': p_flag = 1; server_port = atoi(optarg); break; case '?': if (optopt == 'p') fprintf(stderr, "-%c option must have an argument!\n", optopt); else fprintf(stderr, "-%c invalid option!\n", optopt); err_flag = 1; } } if (err_flag) exit(EXIT_FAILURE); if (optind - argc != 0) { fprintf(stderr, "too many arguments!...\n"); exit(EXIT_FAILURE); } if (!p_flag) server_port = DEF_SERVER_PORT; if ((server_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); sin_server.sin_family = AF_INET; sin_server.sin_port = htons(server_port); sin_server.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(server_sock, (struct sockaddr *)&sin_server, sizeof(sin_server)) == -1) exit_sys("bind"); if (listen(server_sock, 8) == -1) exit_sys("listen"); printf("listening port %d\n", server_port); printf("waiting for connection...\n"); sin_len = sizeof(sin_client); if ((client_sock = accept(server_sock, (struct sockaddr *)&sin_client, &sin_len)) == -1) exit_sys("accept"); printf("connected client ===> %s:%u\n", inet_ntop(AF_INET, &sin_client.sin_addr, ntopbuf, INET_ADDRSTRLEN), (unsigned)ntohs(sin_client.sin_port)); for (;;) { if ((result = recv(client_sock, buf, BUFFER_SIZE, 0)) == -1) exit_sys("recv"); if (result == 0) break; buf[result] = '\0'; if (!strcmp(buf, "quit")) break; printf("%jd byte(s) received: \"%s\"\n", (intmax_t)result, buf); } shutdown(client_sock, SHUT_RDWR); close(client_sock); close(server_sock); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } Pekiyi "TCP/IP" kullanımı sırasında "server" programa bağlanan birden fazla "client" program varsa, "read" / "recv" ile o "client" için okuma yaptığında henüz bilgi gelmemişse, "server" program bloke olacaktır. Dolayısıyla diğer "client" programlardan gelenleri okuyamayacaktır. Dolayısıyla bu durumda "Advanced IO" yöntemlerinin kullanılması gerekmektedir. Anımsanacağı üzere bu teknikler şunlardır; "Multiplexed IO", "Signal Driven IO", "Asynchronous IO". Aslında bu teknikleri biz boruları üzerinde işlemiştik fakat bu tekniklerin soketler üzerindeki kullanımları da aynıdır. Diğer yandan birden fazla "client" program bir "server" a bağlandığında, "server" programın bir döngü içerisinde "accept" çağrısı yapması gerekmektedir. Aynı zamanda da bağlanan "client" programlar ile de haberleşebilmesi gerekmektedir ki "accept" fonksiyonu da varsayılan senaryoda blokeye yol açmaktadır. İşte bu iki problemi gidermek için şu yaklaşımlar geliştirilmiştir: -> En basit fakat bir o kadar da verimsiz olan "fork" yöntemidir. Çünkü "fork" işlemi çok maliyetli bir işlemdir. Eğer az sayıda "client" bağlanacaksa tercih edilebilir. Bu modelde her "accept" işleminden sonra "fork" çağrısı yapılır. Böylece alt proses, bağlanan "client" ile haberleşmeye başlar. Üst proses de yine diğer "client" ların bağlanması için bir döngü içerisinde "accept" yapmaya devam eder. Fakat burada üst proses alt prosesin SONLANMASINI BEKLEMEMELİDİR. AKSİ HALDE ÜST PROSES DE BLOKE OLACAĞINDAN, "fork" İŞLEMİNİN BİR ANLAMI KALMAYACAKTIR. Diğer yandan alt prosesi beklemezsek, "zombie process" oluşacaktır. Dolayısıyla "SIGCHLD" sinyalini bizler "handler" etmeliyiz. Böylece üst proses alt prosesi beklemeyecektir. Bu yöntemdeki kritik nokta, "fork" işlemiyle üst prosesin bellek alanının alt prosese kopyalanmasıdır. Böylelikle "accept" ile elde ettiğimiz "Listening Socket" aslında alt prosese geçmiştir. -> Bir diğer yöntem ise "thread" yöntemidir. Bu yöntem, "fork" yönteminden daha az maliyetlidir. Bu yöntemde her "accept" işleminden sonra bir "thread" oluşturulur. "thread" ler aynı bellek alanını kullandığından, kullanacağımız parametrelere dikkat etmeliyiz. Fakat oluşturulan "thread" ler "detach" edilmelidir ki "zombie thread" oluşmasın. -> Bir diğer yöntem ise "select" yöntemidir. Biz bu yöntemi daha önce boruları üzerinde görmüştük. "socket" üzerindeki kullanımı da çok benzerdir. Şöyleki; "socket" nesnesine bilgi geldiğinde, karşı taraf "socket" nesnesini kapattığında ve yeni bir bağlantı isteği oluştuğunda bu durum, "select" tarafından, "read" olayı olarak yorumlanır. Diğer yandan işin başında "Passive Socket", "select" fonksiyonunun okuma kümesine, eklenir. Eğer "client" program tarafından yeni bir bağlantı isteği gelirse, takip edilen "Passive Socket" nezdinde beklenen okuma olayı gerçekleşmiş demektir. Dolayısıyla bizler "accept" çağrısını o "client" için yapabiliriz. Fakat "accept" ile elde ettiğimiz "Active Socket" nesnesini, "select" fonksiyonunun okuma kümesine eklememiz gerekmektedir. Artık "client" ile bilgi alışverişinde bulunabiliriz. Son olarak, karşı tarafın "socket" nesnesini kapattığını "recv" fonksiyonu ile anladığımız zaman, bizler de o "client" programa ilişkin "Active Socket" nesnesini kontrollü bir şekilde kapatmamız gerekmektedir. -> Bir diğer yöntem ise "poll" yöntemidir. "select" modeline benzerdir. Bu modeli kullanırken dikkat edilmesi gereken noktalar şunlardır; İlk olarak bir adet "Passive Socket" oluştururuz ve bunu "pollfd" dizisinin içerisine yerleştiririz. Anımsanacağı üzere bu dizinin elemanlarını takip edeceğiz, dinleyeceğiz. Daha sonra bu dizininin elemanları olan ilgili "pollfd" yapısının "events" isimli elemanına "POLLIN" değerini atarız. Devamında ise, yani "poll" fonksiyonundan geri döndüğümüzde, "pollfd" dizisini gezeriz ve elemanlarının sahip olduğu "revents" elemanlarını "POLLIN" değeriyle karşılaştırırız. Yeni bir "client" bağlanmışsa, "POLLIN" oluşacaktır. Dolayısıyla "accept" yapabiliriz. Fakat "accept" ile elde ettiğimiz "Active Socket" i de "pollfd" dizisine eklemeliyiz. Yani o "client" ın sahip olduğu "pollfd" bilgileri, "pollfd" dizisine eklenmelidir. Öte yandan karşı taraf soket nesnesini kapattığında yine "POLLIN" oluşacaktır. Bu durumda "recv" fonksiyonunun geri döndürdüğü değere bakarız. Eğer "0" ise "pollfd" dizisinden o "client" in sahip olduğu "pollfd" bilgileri çıkartılmalıdır. -> Bir diğer yöntemse "epoll" yöntemidir. Anımsanacağı üzere "epoll" fonksiyonları Linux dünyasında en verimli fonksiyonlardı. Bu fonksiyonlardan "epoll_create" ya da "epoll_create1" ile bir betimleyici oluşturuyor, daha sonra bu betimleyiciyi "epoll_ctl" ile izleme listesine ekliyor ve "epoll_wait" ile de izleme işlemini gerçekleştiriyorduk. Fakat "server" yazarkenki kritik noktalar ise şunlardır; ilk önce "Passive Socket" izleme listesine eklenmelidir. Soketten yapılan okuma işlemleri "EPOLLIN" olayına neden olmaktadır. Dolayısıyla bu olay oluştuğunda, olaya konu soketin ilgili "Passive Socket" olup olmadığı da sınanmalıdır. Eğer öyleyse, "accept" yapmalı ve "Active Socket" elde etmeliyiz. Karşı taraf soketi kapattığında ise hem "EPOLLIN" hem de "EPOLLERR" olayları oluşmaktadır. Dolayısıyla "EPOLLIN" oluştuğunda yine "recv" çağrısının geri dönüş değerini de kontrol etmeliyiz. Eğer bu değer "0" ise soketin karşı tarafça kapatıldığını düşünerek, biz de "Active Socket" nesnemizi kapatmalıyız. "Active Socket" in de kapatılmasıyla izleme işlemi de otomatik sona ereceğinden, yeniden "epoll_ctl" çağrısına gerek yoktur. Son olarak, karşı tarafın soketi kapatmasından dolayı oluşan "EPOLLIN" ve "EPOLLERR" olaylarında, "getpeername" fonksiyonu KULLANILMAMALIDIR. (Halbuki "select" ve "poll" fonksiyonlarında, bu durumda, "getpeername" fonksiyonu kullanılabilmektedir.) Öte yandan bu modeli kullanırken şu noktaya da dikkat etmemiz gerekmektedir; Anımsanacağı gibi "epoll" modelinde varsayılan izleme biçimi "Düzey Tetiklemeli (Level Triggered)" biçimdedir. Örneğin, Düzey Tetiklemeli izlemede sokete bilgi gelmiş olsun. Bu durumda "epoll_wait" yapıldığında "EPOLLIN" olayı gerçekleşecektir. Ancak eğer biz sokete gelen tüm bilgileri okumazsak, "epoll_wait" fonksiyonunu bir daha çağırdığımızda, yine "EPOLLIN" olayı gerçekleşecektir. Çünkü Düzey Tetiklemede sokette okunacak bilgi olduğu sürece, "epoll_wait" çağrısı, hep bu olayı oluşturacaktır. Ancak Kenar Tetiklemede durum böyle değildir. Kenar Tetiklemeli modda sokete bilgi gelmiş olsun. Bu durumda "epoll_wait" yapıldığında "EPOLLIN" olayı gerçekleşecektir. Biz bu olayda soketteki tüm bilgileri okumasak bile, artık "epoll_wait" fonksiyonunu çağırdığımızda, "EPOLLIN" olayı oluşmayacaktır. "EPOLLIN" olayı, bu modda, yalnızca sokete yeni bir bilgi geldiğinde oluşmaktadır. Dolayısıyla bu modda çalışırken şu noktalara dikkat etmeliyiz; İlk olarak "accept" ile elde ettiğimiz "Active Socket" nesnesine ait betimleyiciyi, Kenar Tetiklemeli modda izlemek için, "epoll_event" yapısının "events" elemanına "EPOLLET" bayrağının eklenmesi gerekmektedir. Daha sonra Kenar Tetiklemeli modda "Active Socket" e bilgi geldiğinde gelen bilgilerin hepsinin okunmasına gayret edilmelidir. Çünkü eğer biz sokete bilgi geldiğinde onların hepsini okumazsak, bir daha "EPOLLIN" olayı ancak yeni bir bilgi geldiğinde oluşacağından, gelmiş olan bilgilerin işleme sokulması gecikebilecektir. (Halbuki Düzey Tetiklemeli modda gelen bilgilerin hepsi okunmasa bile bir sonraki "epoll_wait" çağrısında yine "EPOLLIN" olayı gerçekleşeceği için böyle bir durum söz konusu olmayacaktır.) Diğer yandan Kenar Tetiklemeli modda sokete gelen tüm bilgilerin okunması için "Active Socket" nesnesine ait betimleyicinin blokesiz modda olması gerekir. Aksi takdirde "recv" ya da "read" yaparken sokette bilgi kalmamışsa, bloke oluşacaktır. Soket varsayılan olarak blokeli modda olup, blokesiz moda sokmak için aşağıdaki yöntemi kullanabiliriz. if (fcntl(sock, F_SETFL, fcntl(sock, F_GETFL) | O_NONBLOCK) == -1) exit_sys("fcntl"); Öte yandan blokesiz modda "recv" ya da "read" çağrıları ile, bu çağrılar başarısız olana kadar, okuma işlemi şu şekilde yapılabilir. for (;;) { if ((result = recv(...)) == -1) { if (errno == EAGAIN) break; exit_sys("recv"); } // ... } Son olarak, aslında, "epoll" modelinde bazı soket betimleyicileri Düzey Tetiklemeli bazıları ise Kenar Tetiklemeli modda olabilir. Yani, "Passive Socket" nesnesi Düzey Tetiklemeli modda tutup diğerlerini Kenar Tetiklemeli modda tutabilirsiniz. -> Bir diğer modelimiz ise "Asynchronous IO" modelinde kullanılan ve isimleri "aio_" ön ekine sahip fonksiyonları kullanmaktır. Anımsanacağı üzere bu modelde mekanizma başlatılıyor fakat bizim akışımız akmaya devam ediyordu. Sadece beklenen olay sona erdiğinde bize bilgisi geçiliyordu. Daha öncesinde bu modeli borular üzerinde işlemiştik. Bu modeli kullanırken şu noktalara dikkat etmeliyiz; "accept" işleminin bu mekanizmaya dahil edilmesi gerekmemektedir. Yani akış "accept" işleminde bloke olabilir. Tabii istenirse "accept" işlemi de bu mekanizmaya dahil edilebilir. Çünkü "accept" işlemi de bir okuma durumu oluşturmaktadır. Diğer yandan, bir okuma (ya da yazma) olayından sonra, yeniden aynı mekanizmanın "aio_read" fonksiyonu çağrılarak kurulması gerekmektedir. Yani "aio_read" bir kez değil, her defasında yeniden çağrılmalıdır. Şimdi de bu anlatılanlar doğrultusunda, "Client-Server" program örnekleri inceleyelim: * Örnek 1, "fork" yapılarak oluşturulan "Client-Server". /* server.c */ #include #include #include #include #include #include #include #include #include #define DEF_SERVER_PORT 55555 #define BUFFER_SIZE 4096 void client_proc(int sock, struct sockaddr_in *sin); char *revstr(char *str); void exit_sys(const char *msg); /* ./server [-p port] */ int main(int argc, char *argv[]) { int server_sock, client_sock; struct sockaddr_in sin_server, sin_client; socklen_t sin_len; char ntopbuf[INET_ADDRSTRLEN]; int option; int server_port; int p_flag, err_flag; pid_t pid; struct sigaction sa; p_flag = err_flag = 0; opterr = 0; while ((option = getopt(argc, argv, "p:")) != -1) { switch (option) { case 'p': p_flag = 1; server_port = atoi(optarg); break; case '?': if (optopt == 'p') fprintf(stderr, "-%c option must have an argument!\n", optopt); else fprintf(stderr, "-%c invalid option!\n", optopt); err_flag = 1; } } if (err_flag) exit(EXIT_FAILURE); if (optind - argc != 0) { fprintf(stderr, "too many arguments!...\n"); exit(EXIT_FAILURE); } if (!p_flag) server_port = DEF_SERVER_PORT; sa.sa_handler = SIG_IGN; // 'SIGCHLD' is ignored. if (sigaction(SIGCHLD, &sa, NULL) == -1) exit_sys("sigaction"); if ((server_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); sin_server.sin_family = AF_INET; sin_server.sin_port = htons(server_port); sin_server.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(server_sock, (struct sockaddr *)&sin_server, sizeof(sin_server)) == -1) exit_sys("bind"); if (listen(server_sock, 8) == -1) exit_sys("listen"); printf("listening port %d\n", server_port); for (;;) { printf("waiting for connection...\n"); sin_len = sizeof(sin_client); if ((client_sock = accept(server_sock, (struct sockaddr *)&sin_client, &sin_len)) == -1) exit_sys("accept"); printf("connected client ===> %s:%u\n", inet_ntop(AF_INET, &sin_client.sin_addr, ntopbuf, INET_ADDRSTRLEN), (unsigned)ntohs(sin_client.sin_port)); if ((pid = fork()) == -1) exit_sys("fork"); if (pid == 0) { client_proc(client_sock, &sin_client); exit(EXIT_SUCCESS); } } close(server_sock); return 0; } void client_proc(int sock, struct sockaddr_in *sin) { char buf[BUFFER_SIZE + 1]; // BUFFER_SIZE is enough char ntopbuf[INET_ADDRSTRLEN]; unsigned port; ssize_t result; inet_ntop(AF_INET, &sin->sin_addr, ntopbuf, INET_ADDRSTRLEN); port = (unsigned)ntohs(sin->sin_port); for (;;) { if ((result = recv(sock, buf, BUFFER_SIZE, 0)) == -1) exit_sys("recv"); if (result == 0) break; buf[result] = '\0'; if (!strcmp(buf, "quit")) break; printf("%jd byte(s) received from %s:%u: \"%s\"\n", (intmax_t)result, ntopbuf, port, buf); revstr(buf); if (send(sock, buf, result, 0) == -1) exit_sys("send"); } printf("client disconnected %s:%u\n", ntopbuf, port); shutdown(sock, SHUT_RDWR); close(sock); } char *revstr(char *str) { size_t i, k; char temp; for (i = 0; str[i] != '\0'; ++i) ; for (--i, k = 0; k < i; ++k, --i) { temp = str[k]; str[k] = str[i]; str[i] = temp; } return str; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } /* client.c */ #include #include #include #include #include #include #include #include #define DEF_SERVER_NAME "127.0.0.1" #define DEF_SERVER_PORT "55555" #define BUFFER_SIZE 4096 void exit_sys(const char *msg); /* ./client [-s server] [-p server_port] [-b client_port] */ int main(int argc, char *argv[]) { int client_sock; struct sockaddr_in sin_client; struct addrinfo hints = {0, AF_INET, SOCK_STREAM}; struct addrinfo *res, *ri; int gai_result; char buf[BUFFER_SIZE + 1]; // BUFFER_SIZE is enough char *str; int option; int s_flag, p_flag, b_flag, err_flag; const char *server_name; int bind_port; const char *server_port; ssize_t result; s_flag = p_flag = b_flag = err_flag = 0; opterr = 0; while ((option = getopt(argc, argv, "s:p:b:")) != -1) { switch (option) { case 's': s_flag = 1; server_name = optarg; break; case 'p': p_flag = 1; server_port = optarg; break; case 'b': b_flag = 1; bind_port = atoi(optarg); break; case '?': if (optopt == 's' || optopt == 'p' || optopt == 'b') fprintf(stderr, "-%c option must have an argument!\n", optopt); else fprintf(stderr, "-%c invalid option!\n", optopt); err_flag = 1; } } if (err_flag) exit(EXIT_FAILURE); if (optind - argc != 0) { fprintf(stderr, "too many arguments!...\n"); exit(EXIT_FAILURE); } if (!s_flag) server_name = DEF_SERVER_NAME; if (!p_flag) server_port = DEF_SERVER_PORT; if ((client_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); if (b_flag) { sin_client.sin_family = AF_INET; sin_client.sin_port = htons(bind_port); sin_client.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(client_sock, (struct sockaddr *)&sin_client, sizeof(sin_client)) == -1) exit_sys("bind"); } if ((gai_result = getaddrinfo(server_name, server_port, &hints, &res)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(gai_result)); exit(EXIT_FAILURE); } for (ri = res; ri != NULL; ri = ri->ai_next) if (connect(client_sock, ri->ai_addr, ri->ai_addrlen) != -1) break; if (ri == NULL) exit_sys("connect"); freeaddrinfo(res); printf("connected server...\n"); for (;;) { printf("csd>"); fflush(stdout); if (fgets(buf, BUFFER_SIZE, stdin) == NULL) continue; if ((str = strchr(buf, '\n')) != NULL) *str = '\0'; if (send(client_sock, buf, strlen(buf), 0) == -1) exit_sys("send"); if (!strcmp(buf, "quit")) break; if ((result = recv(client_sock, buf, BUFFER_SIZE, 0)) == -1) exit_sys("recv"); if (result == 0) break; buf[result] = '\0'; printf("%s\n", buf); } shutdown(client_sock, SHUT_RDWR); close(client_sock); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2, "thread" oluşturmak suretiyle oluşturulan "Client-Server". Burada ilgili "thread" e "client" programın özellikleri "CLIENT_INFO" yapısı ile verilmiştir. Bu yapı dinamik ömürlü olduğundan, işlemler sonrasında tekrardan geri verilmiştir. /* server.c */ #include #include #include #include #include #include #include #include #include #define DEF_SERVER_PORT 55555 #define BUFFER_SIZE 4096 void *client_thread_proc(void *param); char *revstr(char *str); void exit_sys(const char *msg); typedef struct tagCLIENT_INFO { int sock; struct sockaddr_in sin; } CLIENT_INFO; /* ./server [-p port] */ int main(int argc, char *argv[]) { int server_sock, client_sock; struct sockaddr_in sin_server, sin_client; socklen_t sin_len; char ntopbuf[INET_ADDRSTRLEN]; int option; int server_port; int p_flag, err_flag; pthread_t tid; CLIENT_INFO *ci; int result; p_flag = err_flag = 0; opterr = 0; while ((option = getopt(argc, argv, "p:")) != -1) { switch (option) { case 'p': p_flag = 1; server_port = atoi(optarg); break; case '?': if (optopt == 'p') fprintf(stderr, "-%c option must have an argument!\n", optopt); else fprintf(stderr, "-%c invalid option!\n", optopt); err_flag = 1; } } if (err_flag) exit(EXIT_FAILURE); if (optind - argc != 0) { fprintf(stderr, "too many arguments!...\n"); exit(EXIT_FAILURE); } if (!p_flag) server_port = DEF_SERVER_PORT; if ((server_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); sin_server.sin_family = AF_INET; sin_server.sin_port = htons(server_port); sin_server.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(server_sock, (struct sockaddr *)&sin_server, sizeof(sin_server)) == -1) exit_sys("bind"); if (listen(server_sock, 8) == -1) exit_sys("listen"); printf("listening port %d\n", server_port); for (;;) { printf("waiting for connection...\n"); sin_len = sizeof(sin_client); if ((client_sock = accept(server_sock, (struct sockaddr *)&sin_client, &sin_len)) == -1) exit_sys("accept"); printf("connected client ===> %s:%u\n", inet_ntop(AF_INET, &sin_client.sin_addr, ntopbuf, INET_ADDRSTRLEN), (unsigned)ntohs(sin_client.sin_port)); if ((ci = (CLIENT_INFO *)malloc(sizeof(CLIENT_INFO))) == NULL) { fprintf(stderr, "cannot allocate memory!...\n"); exit(EXIT_FAILURE); } ci->sock = client_sock; ci->sin = sin_client; if ((result = pthread_create(&tid, NULL, client_thread_proc, ci)) != 0) { fprintf(stderr, "pthread_create: %s\n", strerror(result)); exit(EXIT_FAILURE); } if ((result = pthread_detach(tid)) != 0) { fprintf(stderr, "pthread_detach: %s\n", strerror(result)); exit(EXIT_FAILURE); } } close(server_sock); return 0; } void *client_thread_proc(void *param) { char buf[BUFFER_SIZE + 1]; // BUFFER_SIZE is enough char ntopbuf[INET_ADDRSTRLEN]; unsigned port; ssize_t result; CLIENT_INFO *ci = (CLIENT_INFO *)param; inet_ntop(AF_INET, &ci->sin.sin_addr, ntopbuf, INET_ADDRSTRLEN); port = (unsigned)ntohs(ci->sin.sin_port); for (;;) { if ((result = recv(ci->sock, buf, BUFFER_SIZE, 0)) == -1) exit_sys("recv"); if (result == 0) break; buf[result] = '\0'; if (!strcmp(buf, "quit")) break; printf("%jd byte(s) received from %s:%u: \"%s\"\n", (intmax_t)result, ntopbuf, port, buf); revstr(buf); if (send(ci->sock, buf, result, 0) == -1) exit_sys("send"); } printf("client disconnected %s:%u\n", ntopbuf, port); shutdown(ci->sock, SHUT_RDWR); close(ci->sock); free(ci); return NULL; } char *revstr(char *str) { size_t i, k; char temp; for (i = 0; str[i] != '\0'; ++i) ; for (--i, k = 0; k < i; ++k, --i) { temp = str[k]; str[k] = str[i]; str[i] = temp; } return str; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } /* client.c */ #include #include #include #include #include #include #include #include #define DEF_SERVER_NAME "127.0.0.1" #define DEF_SERVER_PORT "55555" #define BUFFER_SIZE 4096 void exit_sys(const char *msg); /* ./client [-s server] [-p server_port] [-b client_port] */ int main(int argc, char *argv[]) { int client_sock; struct sockaddr_in sin_client; struct addrinfo hints = {0, AF_INET, SOCK_STREAM}; struct addrinfo *res, *ri; int gai_result; char buf[BUFFER_SIZE + 1]; // BUFFER_SIZE is enough char *str; int option; int s_flag, p_flag, b_flag, err_flag; const char *server_name; int bind_port; const char *server_port; ssize_t result; s_flag = p_flag = b_flag = err_flag = 0; opterr = 0; while ((option = getopt(argc, argv, "s:p:b:")) != -1) { switch (option) { case 's': s_flag = 1; server_name = optarg; break; case 'p': p_flag = 1; server_port = optarg; break; case 'b': b_flag = 1; bind_port = atoi(optarg); break; case '?': if (optopt == 's' || optopt == 'p' || optopt == 'b') fprintf(stderr, "-%c option must have an argument!\n", optopt); else fprintf(stderr, "-%c invalid option!\n", optopt); err_flag = 1; } } if (err_flag) exit(EXIT_FAILURE); if (optind - argc != 0) { fprintf(stderr, "too many arguments!...\n"); exit(EXIT_FAILURE); } if (!s_flag) server_name = DEF_SERVER_NAME; if (!p_flag) server_port = DEF_SERVER_PORT; if ((client_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); if (b_flag) { sin_client.sin_family = AF_INET; sin_client.sin_port = htons(bind_port); sin_client.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(client_sock, (struct sockaddr *)&sin_client, sizeof(sin_client)) == -1) exit_sys("bind"); } if ((gai_result = getaddrinfo(server_name, server_port, &hints, &res)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(gai_result)); exit(EXIT_FAILURE); } for (ri = res; ri != NULL; ri = ri->ai_next) if (connect(client_sock, ri->ai_addr, ri->ai_addrlen) != -1) break; if (ri == NULL) exit_sys("connect"); freeaddrinfo(res); printf("connected server...\n"); for (;;) { printf("csd>"); fflush(stdout); if (fgets(buf, BUFFER_SIZE, stdin) == NULL) continue; if ((str = strchr(buf, '\n')) != NULL) *str = '\0'; if (send(client_sock, buf, strlen(buf), 0) == -1) exit_sys("send"); if (!strcmp(buf, "quit")) break; if ((result = recv(client_sock, buf, BUFFER_SIZE, 0)) == -1) exit_sys("recv"); if (result == 0) break; buf[result] = '\0'; printf("%s\n", buf); } shutdown(client_sock, SHUT_RDWR); close(client_sock); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 3, "select" modeline ilişkin bir örnektir. Bu örnekte "select" fonksiyonunun blokesi çözüldüğünde önce betimleyicinin dinleme soketine ilişkin betimleyici olup olmadığına bakılmıştır. Eğer betimleyici dinleme soketine ilişkinse, "accept" işlemi uygulanmıştır. Değilse "recv" işlemi uygulanmıştır. Karşı taraf soketi kapattığında "recv" fonksiyonu "0" ile geri dönecektir. Bu durumda ilgili soket okuma kümesinden çıkartılmıştır. Pekala bu örnek nezdinde de, bağlanan her "client" için, yine bir "CLIENT_INFO" yapısı tahsis edilebilirdi. İş bu "client" bilgileri, bu yapının içinde saklanabilirdi. Sonra da dosya betimleyicisinden hareketle, "CLIENT_INFO" nesnesine hızlı bir biçimde erişmek için, "hash tablosu" oluşturulabilirdi. /* server.c */ #include #include #include #include #include #include #include #include #include #define DEF_SERVER_PORT 55555 #define BUFFER_SIZE 4096 char *revstr(char *str); void exit_sys(const char *msg); /* ./server [-p port] */ int main(int argc, char *argv[]) { int server_sock, client_sock; struct sockaddr_in sin_server, sin_client; socklen_t sinaddr_len; char buf[BUFFER_SIZE + 1]; /* BUFFER_SIZE is enough */ socklen_t sin_len; char ntopbuf[INET_ADDRSTRLEN]; int option; int server_port; int p_flag, err_flag; fd_set rset, tset; int maxfds; ssize_t result; p_flag = err_flag = 0; opterr = 0; while ((option = getopt(argc, argv, "p:")) != -1) { switch (option) { case 'p': p_flag = 1; server_port = atoi(optarg); break; case '?': if (optopt == 'p') fprintf(stderr, "-%c option must have an argument!\n", optopt); else fprintf(stderr, "-%c invalid option!\n", optopt); err_flag = 1; } } if (err_flag) exit(EXIT_FAILURE); if (optind - argc != 0) { fprintf(stderr, "too many arguments!...\n"); exit(EXIT_FAILURE); } if (!p_flag) server_port = DEF_SERVER_PORT; if ((server_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); sin_server.sin_family = AF_INET; sin_server.sin_port = htons(server_port); sin_server.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(server_sock, (struct sockaddr *)&sin_server, sizeof(sin_server)) == -1) exit_sys("bind"); if (listen(server_sock, 8) == -1) exit_sys("listen"); FD_ZERO(&rset); FD_SET(server_sock, &rset); maxfds = server_sock; printf("listening port %d\n", server_port); for (;;) { printf("waiting for connection...\n"); tset = rset; if (select(maxfds + 1, &tset, NULL, NULL, NULL) == -1) exit_sys("select"); for (int fd = 0; fd <= maxfds; ++fd) if (FD_ISSET(fd, &tset)) { if (fd == server_sock) { sin_len = sizeof(sin_client); if ((client_sock = accept(server_sock, (struct sockaddr *)&sin_client, &sin_len)) == -1) exit_sys("accept"); FD_SET(client_sock, &rset); if (client_sock > maxfds) maxfds = client_sock; inet_ntop(AF_INET, &sin_client.sin_addr, ntopbuf, INET_ADDRSTRLEN); printf("connected client ===> %s:%u\n", ntopbuf, (unsigned)ntohs(sin_client.sin_port)); } else { sinaddr_len = sizeof(sin_client); if (getpeername(fd, (struct sockaddr *)&sin_client, &sinaddr_len) == -1) exit_sys("getpeername"); inet_ntop(AF_INET, &sin_client.sin_addr, ntopbuf, INET_ADDRSTRLEN); if ((result = recv(fd, buf, BUFFER_SIZE, 0)) == -1) exit_sys("recv"); if (result > 0) { buf[result] = '\0'; if (!strcmp(buf, "quit")) goto DISCONNECT; printf("%jd byte(s) received from %s:%u: \"%s\"\n", (intmax_t)result, ntopbuf, (unsigned)ntohs(sin_client.sin_port), buf); revstr(buf); if (send(fd, buf, result, 0) == -1) exit_sys("send"); } else { /* result == 0 */ DISCONNECT: shutdown(fd, SHUT_RDWR); close(fd); FD_CLR(fd, &rset); printf("client disconnected ===> %s:%u\n", ntopbuf, (unsigned)ntohs(sin_client.sin_port)); } } } } close(server_sock); return 0; } char *revstr(char *str) { size_t i, k; char temp; for (i = 0; str[i] != '\0'; ++i) ; for (--i, k = 0; k < i; ++k, --i) { temp = str[k]; str[k] = str[i]; str[i] = temp; } return str; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } /* client.c */ #include #include #include #include #include #include #include #include #define DEF_SERVER_NAME "127.0.0.1" #define DEF_SERVER_PORT "55555" #define BUFFER_SIZE 4096 void exit_sys(const char *msg); /* ./client [-s server] [-p server_port] [-b client_port] */ int main(int argc, char *argv[]) { int client_sock; struct sockaddr_in sin_client; struct addrinfo hints = {0, AF_INET, SOCK_STREAM}; struct addrinfo *res, *ri; int gai_result; char buf[BUFFER_SIZE + 1]; // BUFFER_SIZE is enough char *str; int option; int s_flag, p_flag, b_flag, err_flag; const char *server_name; int bind_port; const char *server_port; ssize_t result; s_flag = p_flag = b_flag = err_flag = 0; opterr = 0; while ((option = getopt(argc, argv, "s:p:b:")) != -1) { switch (option) { case 's': s_flag = 1; server_name = optarg; break; case 'p': p_flag = 1; server_port = optarg; break; case 'b': b_flag = 1; bind_port = atoi(optarg); break; case '?': if (optopt == 's' || optopt == 'p' || optopt == 'b') fprintf(stderr, "-%c option must have an argument!\n", optopt); else fprintf(stderr, "-%c invalid option!\n", optopt); err_flag = 1; } } if (err_flag) exit(EXIT_FAILURE); if (optind - argc != 0) { fprintf(stderr, "too many arguments!...\n"); exit(EXIT_FAILURE); } if (!s_flag) server_name = DEF_SERVER_NAME; if (!p_flag) server_port = DEF_SERVER_PORT; if ((client_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); if (b_flag) { sin_client.sin_family = AF_INET; sin_client.sin_port = htons(bind_port); sin_client.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(client_sock, (struct sockaddr *)&sin_client, sizeof(sin_client)) == -1) exit_sys("bind"); } if ((gai_result = getaddrinfo(server_name, server_port, &hints, &res)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(gai_result)); exit(EXIT_FAILURE); } for (ri = res; ri != NULL; ri = ri->ai_next) if (connect(client_sock, ri->ai_addr, ri->ai_addrlen) != -1) break; if (ri == NULL) exit_sys("connect"); freeaddrinfo(res); printf("connected server...\n"); for (;;) { printf("csd>"); fflush(stdout); if (fgets(buf, BUFFER_SIZE, stdin) == NULL) continue; if ((str = strchr(buf, '\n')) != NULL) *str = '\0'; if (send(client_sock, buf, strlen(buf), 0) == -1) exit_sys("send"); if (!strcmp(buf, "quit")) break; if ((result = recv(client_sock, buf, BUFFER_SIZE, 0)) == -1) exit_sys("recv"); if (result == 0) break; buf[result] = '\0'; printf("%s\n", buf); } shutdown(client_sock, SHUT_RDWR); close(client_sock); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 4, "poll" modeline ilişkin bir örnektir. Burada pfds isimli bir pollfd dizisini oluşturulmuştur. Bu dizinin maksimum uzunluğu MAX_CLIENT kadardır. Her bağlantı sağlandığında yeni client için bu pollfd dizisine bir eleman eklenmiştir. Bir client disconnect olduğunda bu diziden ilgili eleman silinmiştir. /* server.c */ #include #include #include #include #include #include #include #include #include #define DEF_SERVER_PORT 55555 #define BUFFER_SIZE 4096 #define MAX_CLIENT 1000 char *revstr(char *str); void exit_sys(const char *msg); /* ./server [-p port] */ int main(int argc, char *argv[]) { int server_sock, client_sock; struct sockaddr_in sin_server, sin_client; socklen_t sinaddr_len; char buf[BUFFER_SIZE + 1]; /* BUFFER_SIZE is enough */ socklen_t sin_len; char ntopbuf[INET_ADDRSTRLEN]; int option; int server_port; int p_flag, err_flag; struct pollfd pfds[MAX_CLIENT]; int npfds, count; ssize_t result; p_flag = err_flag = 0; opterr = 0; while ((option = getopt(argc, argv, "p:")) != -1) { switch (option) { case 'p': p_flag = 1; server_port = atoi(optarg); break; case '?': if (optopt == 'p') fprintf(stderr, "-%c option must have an argument!\n", optopt); else fprintf(stderr, "-%c invalid option!\n", optopt); err_flag = 1; } } if (err_flag) exit(EXIT_FAILURE); if (optind - argc != 0) { fprintf(stderr, "too many arguments!...\n"); exit(EXIT_FAILURE); } if (!p_flag) server_port = DEF_SERVER_PORT; if ((server_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); sin_server.sin_family = AF_INET; sin_server.sin_port = htons(server_port); sin_server.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(server_sock, (struct sockaddr *)&sin_server, sizeof(sin_server)) == -1) exit_sys("bind"); if (listen(server_sock, 8) == -1) exit_sys("listen"); pfds[0].fd = server_sock; pfds[0].events = POLLIN; npfds = 1; printf("listening port %d\n", server_port); for (;;) { printf("waiting for connection...\n"); if (poll(pfds, npfds, -1) == -1) exit_sys("poll"); count = npfds; for (int i = 0; i < count; ++i) { if (pfds[i].revents & POLLIN) { if (pfds[i].fd == server_sock) { if (npfds >= MAX_CLIENT) { fprintf(stderr, "number of clints exceeds %d limit!...\n", MAX_CLIENT); continue; } sin_len = sizeof(sin_client); if ((client_sock = accept(server_sock, (struct sockaddr *)&sin_client, &sin_len)) == -1) exit_sys("accept"); pfds[npfds].fd = client_sock; pfds[npfds].events = POLLIN; ++npfds; inet_ntop(AF_INET, &sin_client.sin_addr, ntopbuf, INET_ADDRSTRLEN); printf("connected client ===> %s:%u\n", ntopbuf, (unsigned)ntohs(sin_client.sin_port)); } else { sinaddr_len = sizeof(sin_client); if (getpeername(pfds[i].fd, (struct sockaddr *)&sin_client, &sinaddr_len) == -1) exit_sys("getpeername"); inet_ntop(AF_INET, &sin_client.sin_addr, ntopbuf, INET_ADDRSTRLEN); if ((result = recv(pfds[i].fd, buf, BUFFER_SIZE, 0)) == -1) exit_sys("recv"); if (result > 0) { buf[result] = '\0'; if (!strcmp(buf, "quit")) goto DISCONNECT; printf("%jd byte(s) received from %s:%u: \"%s\"\n", (intmax_t)result, ntopbuf, (unsigned)ntohs(sin_client.sin_port), buf); revstr(buf); if (send(pfds[i].fd, buf, result, 0) == -1) exit_sys("send"); } else { DISCONNECT: shutdown(pfds[i].fd, SHUT_RDWR); close(pfds[i].fd); pfds[i] = pfds[npfds - 1]; --npfds; printf("client disconnected ===> %s:%u\n", ntopbuf, (unsigned)ntohs(sin_client.sin_port)); } } } } } close(server_sock); return 0; } char *revstr(char *str) { size_t i, k; char temp; for (i = 0; str[i] != '\0'; ++i) ; for (--i, k = 0; k < i; ++k, --i) { temp = str[k]; str[k] = str[i]; str[i] = temp; } return str; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } /* client.c */ #include #include #include #include #include #include #include #include #define DEF_SERVER_NAME "127.0.0.1" #define DEF_SERVER_PORT "55555" #define BUFFER_SIZE 4096 void exit_sys(const char *msg); /* ./client [-s server] [-p server_port] [-b client_port] */ int main(int argc, char *argv[]) { int client_sock; struct sockaddr_in sin_client; struct addrinfo hints = {0, AF_INET, SOCK_STREAM}; struct addrinfo *res, *ri; int gai_result; char buf[BUFFER_SIZE + 1]; // BUFFER_SIZE is enough char *str; int option; int s_flag, p_flag, b_flag, err_flag; const char *server_name; int bind_port; const char *server_port; ssize_t result; s_flag = p_flag = b_flag = err_flag = 0; opterr = 0; while ((option = getopt(argc, argv, "s:p:b:")) != -1) { switch (option) { case 's': s_flag = 1; server_name = optarg; break; case 'p': p_flag = 1; server_port = optarg; break; case 'b': b_flag = 1; bind_port = atoi(optarg); break; case '?': if (optopt == 's' || optopt == 'p' || optopt == 'b') fprintf(stderr, "-%c option must have an argument!\n", optopt); else fprintf(stderr, "-%c invalid option!\n", optopt); err_flag = 1; } } if (err_flag) exit(EXIT_FAILURE); if (optind - argc != 0) { fprintf(stderr, "too many arguments!...\n"); exit(EXIT_FAILURE); } if (!s_flag) server_name = DEF_SERVER_NAME; if (!p_flag) server_port = DEF_SERVER_PORT; if ((client_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); if (b_flag) { sin_client.sin_family = AF_INET; sin_client.sin_port = htons(bind_port); sin_client.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(client_sock, (struct sockaddr *)&sin_client, sizeof(sin_client)) == -1) exit_sys("bind"); } if ((gai_result = getaddrinfo(server_name, server_port, &hints, &res)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(gai_result)); exit(EXIT_FAILURE); } for (ri = res; ri != NULL; ri = ri->ai_next) if (connect(client_sock, ri->ai_addr, ri->ai_addrlen) != -1) break; if (ri == NULL) exit_sys("connect"); freeaddrinfo(res); printf("connected server...\n"); for (;;) { printf("csd>"); fflush(stdout); if (fgets(buf, BUFFER_SIZE, stdin) == NULL) continue; if ((str = strchr(buf, '\n')) != NULL) *str = '\0'; if (send(client_sock, buf, strlen(buf), 0) == -1) exit_sys("send"); if (!strcmp(buf, "quit")) break; if ((result = recv(client_sock, buf, BUFFER_SIZE, 0)) == -1) exit_sys("recv"); if (result == 0) break; buf[result] = '\0'; printf("%s\n", buf); } shutdown(client_sock, SHUT_RDWR); close(client_sock); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 5.0, "epoll" modeline ilişkin bir örnektir. Burada Düzey Tetiklemeli hali kullanılmıştır. Daha önce borular kullanılarak bir gerçekleştirim sağlanmıştı. Soketlerle de işlemler benzer biçimde yürütülmektedir. /* server.c */ #include #include #include #include #include #include #include #include #include #define DEF_SERVER_PORT 55555 #define BUFFER_SIZE 4096 #define MAX_EVENTS 1024 char *revstr(char *str); void exit_sys(const char *msg); /* ./server [-p port] */ int main(int argc, char *argv[]) { int server_sock, client_sock; struct sockaddr_in sin_server, sin_client; socklen_t sinaddr_len; char buf[BUFFER_SIZE + 1]; /* BUFFER_SIZE is enough */ socklen_t sin_len; char ntopbuf[INET_ADDRSTRLEN]; int option; int server_port; struct epoll_event ee; struct epoll_event ree[MAX_EVENTS]; int p_flag, err_flag; int epfd; int nevents; ssize_t result; p_flag = err_flag = 0; opterr = 0; while ((option = getopt(argc, argv, "p:")) != -1) { switch (option) { case 'p': p_flag = 1; server_port = atoi(optarg); break; case '?': if (optopt == 'p') fprintf(stderr, "-%c option must have an argument!\n", optopt); else fprintf(stderr, "-%c invalid option!\n", optopt); err_flag = 1; } } if (err_flag) exit(EXIT_FAILURE); if (optind - argc != 0) { fprintf(stderr, "too many arguments!...\n"); exit(EXIT_FAILURE); } if (!p_flag) server_port = DEF_SERVER_PORT; if ((server_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); sin_server.sin_family = AF_INET; sin_server.sin_port = htons(server_port); sin_server.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(server_sock, (struct sockaddr *)&sin_server, sizeof(sin_server)) == -1) exit_sys("bind"); if (listen(server_sock, 8) == -1) exit_sys("listen"); if ((epfd = epoll_create(1024)) == -1) exit_sys("epoll_create"); ee.events = EPOLLIN; ee.data.fd = server_sock; if (epoll_ctl(epfd, EPOLL_CTL_ADD, server_sock, &ee) == -1) exit_sys("epoll_ctl"); printf("listening port %d\n", server_port); for (;;) { printf("waiting for connection...\n"); if ((nevents = epoll_wait(epfd, ree, MAX_EVENTS, -1)) == -1) exit_sys("epoll_wait"); for (int i = 0; i < nevents; ++i) { if (ree[i].events & EPOLLIN) { if (ree[i].data.fd == server_sock) { sin_len = sizeof(sin_client); if ((client_sock = accept(server_sock, (struct sockaddr *)&sin_client, &sin_len)) == -1) exit_sys("accept"); ee.events = EPOLLIN; ee.data.fd = client_sock; if (epoll_ctl(epfd, EPOLL_CTL_ADD, client_sock, &ee) == -1) exit_sys("epoll_ctl"); inet_ntop(AF_INET, &sin_client.sin_addr, ntopbuf, INET_ADDRSTRLEN); printf("connected client ===> %s:%u\n", ntopbuf, (unsigned)ntohs(sin_client.sin_port)); } else { if ((result = recv(ree[i].data.fd, buf, BUFFER_SIZE, 0)) == -1) exit_sys("recv"); buf[result] = '\0'; if (result > 0) { sinaddr_len = sizeof(sin_client); if (getpeername(ree[i].data.fd, (struct sockaddr *)&sin_client, &sinaddr_len) == -1) exit_sys("getpeername"); inet_ntop(AF_INET, &sin_client.sin_addr, ntopbuf, INET_ADDRSTRLEN); printf("%jd byte(s) received from %s:%u: \"%s\"\n", (intmax_t)result, ntopbuf, (unsigned)ntohs(sin_client.sin_port), buf); revstr(buf); if (send(ree[i].data.fd, buf, result, 0) == -1) exit_sys("send"); } else { shutdown(ree[i].data.fd, SHUT_RDWR); close(ree[i].data.fd); printf("client disconnected\n"); } } } } } close(server_sock); return 0; } char *revstr(char *str) { size_t i, k; char temp; for (i = 0; str[i] != '\0'; ++i) ; for (--i, k = 0; k < i; ++k, --i) { temp = str[k]; str[k] = str[i]; str[i] = temp; } return str; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } /* client.c */ #include #include #include #include #include #include #include #include #define DEF_SERVER_NAME "127.0.0.1" #define DEF_SERVER_PORT "55555" #define BUFFER_SIZE 4096 void exit_sys(const char *msg); /* ./client [-s server] [-p server_port] [-b client_port] */ int main(int argc, char *argv[]) { int client_sock; struct sockaddr_in sin_client; struct addrinfo hints = {0, AF_INET, SOCK_STREAM}; struct addrinfo *res, *ri; int gai_result; char buf[BUFFER_SIZE + 1]; // BUFFER_SIZE is enough char *str; int option; int s_flag, p_flag, b_flag, err_flag; const char *server_name; int bind_port; const char *server_port; ssize_t result; s_flag = p_flag = b_flag = err_flag = 0; opterr = 0; while ((option = getopt(argc, argv, "s:p:b:")) != -1) { switch (option) { case 's': s_flag = 1; server_name = optarg; break; case 'p': p_flag = 1; server_port = optarg; break; case 'b': b_flag = 1; bind_port = atoi(optarg); break; case '?': if (optopt == 's' || optopt == 'p' || optopt == 'b') fprintf(stderr, "-%c option must have an argument!\n", optopt); else fprintf(stderr, "-%c invalid option!\n", optopt); err_flag = 1; } } if (err_flag) exit(EXIT_FAILURE); if (optind - argc != 0) { fprintf(stderr, "too many arguments!...\n"); exit(EXIT_FAILURE); } if (!s_flag) server_name = DEF_SERVER_NAME; if (!p_flag) server_port = DEF_SERVER_PORT; if ((client_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); if (b_flag) { sin_client.sin_family = AF_INET; sin_client.sin_port = htons(bind_port); sin_client.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(client_sock, (struct sockaddr *)&sin_client, sizeof(sin_client)) == -1) exit_sys("bind"); } if ((gai_result = getaddrinfo(server_name, server_port, &hints, &res)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(gai_result)); exit(EXIT_FAILURE); } for (ri = res; ri != NULL; ri = ri->ai_next) if (connect(client_sock, ri->ai_addr, ri->ai_addrlen) != -1) break; if (ri == NULL) exit_sys("connect"); freeaddrinfo(res); printf("connected server...\n"); for (;;) { printf("csd>"); fflush(stdout); if (fgets(buf, BUFFER_SIZE, stdin) == NULL) continue; if ((str = strchr(buf, '\n')) != NULL) *str = '\0'; if (send(client_sock, buf, strlen(buf), 0) == -1) exit_sys("send"); if (!strcmp(buf, "quit")) break; if ((result = recv(client_sock, buf, BUFFER_SIZE, 0)) == -1) exit_sys("recv"); if (result == 0) break; buf[result] = '\0'; printf("%s\n", buf); } shutdown(client_sock, SHUT_RDWR); close(client_sock); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 5.1, Yine "epoll" modeline ilişkin bir örnektir. Fakat burada artık Kenar Tetiklemeli hali kullanılmıştır. Fakat bu örnek Kenar Tetikleme için güzel bir örnek değildir. Bir nevi temsilidir. /* server.c */ #include #include #include #include #include #include #include #include #include #include #include #define DEF_SERVER_PORT 55555 #define BUFFER_SIZE 4096 #define MAX_EVENTS 1024 char *revstr(char *str); void exit_sys(const char *msg); /* ./server [-p port] */ int main(int argc, char *argv[]) { int server_sock, client_sock; struct sockaddr_in sin_server, sin_client; socklen_t sinaddr_len; char buf[BUFFER_SIZE + 1]; /* BUFFER_SIZE is enough */ socklen_t sin_len; char ntopbuf[INET_ADDRSTRLEN]; int option; int server_port; struct epoll_event ee; struct epoll_event ree[MAX_EVENTS]; int p_flag, err_flag; int epfd; int nevents; ssize_t result; p_flag = err_flag = 0; opterr = 0; while ((option = getopt(argc, argv, "p:")) != -1) { switch (option) { case 'p': p_flag = 1; server_port = atoi(optarg); break; case '?': if (optopt == 'p') fprintf(stderr, "-%c option must have an argument!\n", optopt); else fprintf(stderr, "-%c invalid option!\n", optopt); err_flag = 1; } } if (err_flag) exit(EXIT_FAILURE); if (optind - argc != 0) { fprintf(stderr, "too many arguments!...\n"); exit(EXIT_FAILURE); } if (!p_flag) server_port = DEF_SERVER_PORT; if ((server_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); sin_server.sin_family = AF_INET; sin_server.sin_port = htons(server_port); sin_server.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(server_sock, (struct sockaddr *)&sin_server, sizeof(sin_server)) == -1) exit_sys("bind"); if (listen(server_sock, 8) == -1) exit_sys("listen"); if ((epfd = epoll_create(1024)) == -1) exit_sys("epoll_create"); ee.events = EPOLLIN; ee.data.fd = server_sock; if (epoll_ctl(epfd, EPOLL_CTL_ADD, server_sock, &ee) == -1) exit_sys("epoll_ctl"); printf("listening port %d\n", server_port); for (;;) { printf("waiting for connection...\n"); if ((nevents = epoll_wait(epfd, ree, MAX_EVENTS, -1)) == -1) exit_sys("epoll_wait"); for (int i = 0; i < nevents; ++i) { if (ree[i].events & EPOLLIN) { if (ree[i].data.fd == server_sock) { sin_len = sizeof(sin_client); if ((client_sock = accept(server_sock, (struct sockaddr *)&sin_client, &sin_len)) == -1) exit_sys("accept"); if (fcntl(client_sock, F_SETFL, fcntl(client_sock, F_GETFL) | O_NONBLOCK) == -1) exit_sys("fcntl"); ee.events = EPOLLIN|EPOLLET; ee.data.fd = client_sock; if (epoll_ctl(epfd, EPOLL_CTL_ADD, client_sock, &ee) == -1) exit_sys("epoll_ctl"); inet_ntop(AF_INET, &sin_client.sin_addr, ntopbuf, INET_ADDRSTRLEN); printf("connected client ===> %s:%u\n", ntopbuf, (unsigned)ntohs(sin_client.sin_port)); } else { for (;;) { if ((result = recv(ree[i].data.fd, buf, BUFFER_SIZE, 0)) == -1) { if (errno == EAGAIN) break; exit_sys("recv"); } buf[result] = '\0'; if (result > 0) { sinaddr_len = sizeof(sin_client); if (getpeername(ree[i].data.fd, (struct sockaddr *)&sin_client, &sinaddr_len) == -1) exit_sys("getpeername"); inet_ntop(AF_INET, &sin_client.sin_addr, ntopbuf, INET_ADDRSTRLEN); printf("%jd byte(s) received from %s:%u: \"%s\"\n", (intmax_t)result, ntopbuf, (unsigned)ntohs(sin_client.sin_port), buf); revstr(buf); if (send(ree[i].data.fd, buf, result, 0) == -1) exit_sys("send"); } else { shutdown(ree[i].data.fd, SHUT_RDWR); close(ree[i].data.fd); printf("client disconnected\n"); break; } } } } } } close(server_sock); return 0; } char *revstr(char *str) { size_t i, k; char temp; for (i = 0; str[i] != '\0'; ++i) ; for (--i, k = 0; k < i; ++k, --i) { temp = str[k]; str[k] = str[i]; str[i] = temp; } return str; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } /* client.c */ #include #include #include #include #include #include #include #include #define DEF_SERVER_NAME "127.0.0.1" #define DEF_SERVER_PORT "55555" #define BUFFER_SIZE 4096 void exit_sys(const char *msg); /* ./client [-s server] [-p server_port] [-b client_port] */ int main(int argc, char *argv[]) { int client_sock; struct sockaddr_in sin_client; struct addrinfo hints = {0, AF_INET, SOCK_STREAM}; struct addrinfo *res, *ri; int gai_result; char buf[BUFFER_SIZE + 1]; // BUFFER_SIZE is enough char *str; int option; int s_flag, p_flag, b_flag, err_flag; const char *server_name; int bind_port; const char *server_port; ssize_t result; s_flag = p_flag = b_flag = err_flag = 0; opterr = 0; while ((option = getopt(argc, argv, "s:p:b:")) != -1) { switch (option) { case 's': s_flag = 1; server_name = optarg; break; case 'p': p_flag = 1; server_port = optarg; break; case 'b': b_flag = 1; bind_port = atoi(optarg); break; case '?': if (optopt == 's' || optopt == 'p' || optopt == 'b') fprintf(stderr, "-%c option must have an argument!\n", optopt); else fprintf(stderr, "-%c invalid option!\n", optopt); err_flag = 1; } } if (err_flag) exit(EXIT_FAILURE); if (optind - argc != 0) { fprintf(stderr, "too many arguments!...\n"); exit(EXIT_FAILURE); } if (!s_flag) server_name = DEF_SERVER_NAME; if (!p_flag) server_port = DEF_SERVER_PORT; if ((client_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); if (b_flag) { sin_client.sin_family = AF_INET; sin_client.sin_port = htons(bind_port); sin_client.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(client_sock, (struct sockaddr *)&sin_client, sizeof(sin_client)) == -1) exit_sys("bind"); } if ((gai_result = getaddrinfo(server_name, server_port, &hints, &res)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(gai_result)); exit(EXIT_FAILURE); } for (ri = res; ri != NULL; ri = ri->ai_next) if (connect(client_sock, ri->ai_addr, ri->ai_addrlen) != -1) break; if (ri == NULL) exit_sys("connect"); freeaddrinfo(res); printf("connected server...\n"); for (;;) { printf("csd>"); fflush(stdout); if (fgets(buf, BUFFER_SIZE, stdin) == NULL) continue; if ((str = strchr(buf, '\n')) != NULL) *str = '\0'; if (send(client_sock, buf, strlen(buf), 0) == -1) exit_sys("send"); if (!strcmp(buf, "quit")) break; if ((result = recv(client_sock, buf, BUFFER_SIZE, 0)) == -1) exit_sys("recv"); if (result == 0) break; buf[result] = '\0'; printf("%s\n", buf); } shutdown(client_sock, SHUT_RDWR); close(client_sock); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 6, "Asynchronous IO" modeline ilişkin bir örnektir. /* server.c */ #include #include #include #include #include #include #include #include #include #include #define DEF_SERVER_PORT 55555 #define BUFFER_SIZE 4096 #define MAX_EVENTS 1024 void io_proc(union sigval sval); char *revstr(char *str); void exit_sys(const char *msg); typedef struct tagCLIENT_INFO { struct aiocb cb; char buf[BUFFER_SIZE + 1]; /* BUFFER_SIZE is enough */ char ntopbuf[INET_ADDRSTRLEN]; unsigned port; } CLIENT_INFO; /* ./server [-p port] */ int main(int argc, char *argv[]) { int server_sock, client_sock; struct sockaddr_in sin_server, sin_client; socklen_t sin_len; int option; int server_port; CLIENT_INFO *ci; int p_flag, err_flag; p_flag = err_flag = 0; opterr = 0; while ((option = getopt(argc, argv, "p:")) != -1) { switch (option) { case 'p': p_flag = 1; server_port = atoi(optarg); break; case '?': if (optopt == 'p') fprintf(stderr, "-%c option must have an argument!\n", optopt); else fprintf(stderr, "-%c invalid option!\n", optopt); err_flag = 1; } } if (err_flag) exit(EXIT_FAILURE); if (optind - argc != 0) { fprintf(stderr, "too many arguments!...\n"); exit(EXIT_FAILURE); } if (!p_flag) server_port = DEF_SERVER_PORT; if ((server_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); sin_server.sin_family = AF_INET; sin_server.sin_port = htons(server_port); sin_server.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(server_sock, (struct sockaddr *)&sin_server, sizeof(sin_server)) == -1) exit_sys("bind"); if (listen(server_sock, 8) == -1) exit_sys("listen"); printf("listening port %d\n", server_port); for (;;) { printf("waiting for connection...\n"); sin_len = sizeof(sin_client); if ((client_sock = accept(server_sock, (struct sockaddr *)&sin_client, &sin_len)) == -1) exit_sys("accept"); if ((ci = (CLIENT_INFO *)calloc(1, sizeof(CLIENT_INFO))) == NULL) { fprintf(stderr, "cannot allocate memory!...\n"); exit(EXIT_FAILURE); } inet_ntop(AF_INET, &sin_client.sin_addr, ci->ntopbuf, INET_ADDRSTRLEN); ci->port = ntohs(sin_client.sin_port); printf("connected client ===> %s:%u\n", ci->ntopbuf, ci->port); ci->cb.aio_fildes = client_sock; ci->cb.aio_offset = 0; ci->cb.aio_buf = ci->buf; ci->cb.aio_nbytes = BUFFER_SIZE; ci->cb.aio_reqprio = 0; ci->cb.aio_sigevent.sigev_notify = SIGEV_THREAD; ci->cb.aio_sigevent.sigev_value.sival_ptr = ci; ci->cb.aio_sigevent.sigev_notify_function = io_proc; ci->cb.aio_sigevent.sigev_notify_attributes = NULL; if (aio_read(&ci->cb) == -1) exit_sys("aio_read"); } close(server_sock); return 0; } void io_proc(union sigval sval) { CLIENT_INFO *ci = (CLIENT_INFO *)sval.sival_ptr; ssize_t result; if ((result = aio_return(&ci->cb)) == -1) exit_sys("aio_return"); ci->buf[result] = '\0'; if (result > 0) { printf("%jd byte(s) received from %s:%u: \"%s\"\n", (intmax_t)result, ci->ntopbuf, (ci->port), ci->buf); revstr(ci->buf); if (send(ci->cb.aio_fildes, ci->buf, result, 0) == -1) exit_sys("send"); if (aio_read(&ci->cb) == -1) exit_sys("aio_read"); } else { shutdown(ci->cb.aio_fildes, SHUT_RDWR); close(ci->cb.aio_fildes); printf("client disconnected ===> %s:%u\n", ci->ntopbuf, (ci->port)); free(ci); } } char *revstr(char *str) { size_t i, k; char temp; for (i = 0; str[i] != '\0'; ++i) ; for (--i, k = 0; k < i; ++k, --i) { temp = str[k]; str[k] = str[i]; str[i] = temp; } return str; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } /* client.c */ #include #include #include #include #include #include #include #include #define DEF_SERVER_NAME "127.0.0.1" #define DEF_SERVER_PORT "55555" #define BUFFER_SIZE 4096 void exit_sys(const char *msg); /* ./client [-s server] [-p server_port] [-b client_port] */ int main(int argc, char *argv[]) { int client_sock; struct sockaddr_in sin_client; struct addrinfo hints = {0, AF_INET, SOCK_STREAM}; struct addrinfo *res, *ri; int gai_result; char buf[BUFFER_SIZE + 1]; // BUFFER_SIZE is enough char *str; int option; int s_flag, p_flag, b_flag, err_flag; const char *server_name; int bind_port; const char *server_port; ssize_t result; s_flag = p_flag = b_flag = err_flag = 0; opterr = 0; while ((option = getopt(argc, argv, "s:p:b:")) != -1) { switch (option) { case 's': s_flag = 1; server_name = optarg; break; case 'p': p_flag = 1; server_port = optarg; break; case 'b': b_flag = 1; bind_port = atoi(optarg); break; case '?': if (optopt == 's' || optopt == 'p' || optopt == 'b') fprintf(stderr, "-%c option must have an argument!\n", optopt); else fprintf(stderr, "-%c invalid option!\n", optopt); err_flag = 1; } } if (err_flag) exit(EXIT_FAILURE); if (optind - argc != 0) { fprintf(stderr, "too many arguments!...\n"); exit(EXIT_FAILURE); } if (!s_flag) server_name = DEF_SERVER_NAME; if (!p_flag) server_port = DEF_SERVER_PORT; if ((client_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); if (b_flag) { sin_client.sin_family = AF_INET; sin_client.sin_port = htons(bind_port); sin_client.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(client_sock, (struct sockaddr *)&sin_client, sizeof(sin_client)) == -1) exit_sys("bind"); } if ((gai_result = getaddrinfo(server_name, server_port, &hints, &res)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(gai_result)); exit(EXIT_FAILURE); } for (ri = res; ri != NULL; ri = ri->ai_next) if (connect(client_sock, ri->ai_addr, ri->ai_addrlen) != -1) break; if (ri == NULL) exit_sys("connect"); freeaddrinfo(res); printf("connected server...\n"); for (;;) { printf("csd>"); fflush(stdout); if (fgets(buf, BUFFER_SIZE, stdin) == NULL) continue; if ((str = strchr(buf, '\n')) != NULL) *str = '\0'; if (send(client_sock, buf, strlen(buf), 0) == -1) exit_sys("send"); if (!strcmp(buf, "quit")) break; if ((result = recv(client_sock, buf, BUFFER_SIZE, 0)) == -1) exit_sys("recv"); if (result == 0) break; buf[result] = '\0'; printf("%s\n", buf); } shutdown(client_sock, SHUT_RDWR); close(client_sock); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } >>> "UDP/IP" : Anımsanacağı üzere bu protokol, bağlantılı olmayan bir protokoldür. Bir taraf diğer tarafa hiç bağlanmadan, onun "IP" adresini ve "port" numarasını bilerek, "UDP" paketlerini gönderebilir. Fakat gönderen taraf, alan tarafın paketi alıp almadığını bilmez. Yani "UDP" protokolünde bir akış kontrolü yoktur. Dolayısıyla alan taraf pekala bilgi kaçırabilir. Bu protokol, kaçırılan bu bilgilerin telafisini kendisi yapmamaktadır. Halbuki "TCP" protokülünde, bir bağlantı oluşturulduğu için bir akış kontrolü uygulanarak, karşı tarafa ulaşmamış "TCP" paketlerinin yeniden gönderilmesi sağlanmaktadır. Diğer yandan "UDP", "TCP" ye göre daha hızlıdır. Zaten "TCP" bir bakıma "UDP" nin organize edilmiş bağlantılı biçimidir. Pekiyi "UDP" protokolü ile ağ katmanı protokolü olan "IP" protokolü arasındaki fark nedir? Açıkçası her iki protokol aslında paketlerin iletimini yapmaktadır. Aslında "UDP" protokolünün gerçekten de "IP" protokolünden çok farkı yoktur. Ancak "UDP", bir "Transport Layer" protokolü olduğu için, "port" numarası içermektedir. Halbuki "IP" protokolünde port numarası kavramı yoktur. Yani "IP" protokolünde biz bir "host" cihaza paket gönderebiliriz. Fakat onun belli bir "port" una paket gönderemeyiz. Bunun dışında "UDP" ile "IP" protokollerinin kullanımları konusunda yine de bazı farklılıklar vardır. Aslında biz programcı olarak doğrudan "IP" paketleri de gönderebiliriz. Buna "raw socket" kullanımı denilmektedir. >>>> "Raw Socket" : Bazen "TCP" ve "UDP" yerine doğrudan "IP" protokolünü kullanmak isteyebiliriz. Buna soket programlamada "raw socket" denilmektedir. Diğer protokol ailelerinde de "raw socket", "Network Layer" protokolünü belirtmektedir. Biz kursumuzda "raw socket" işlemleri üzerinde durmayacağız. Ancak daha aşağı seviyeli çalışmalar için ya da örneğin "Transport Layer" gerçekleştirmek için "raw socket" kullanımını bilmek gerekir. Genel bir "raw soket" oluşturmak için, soket nesnesi yaratılırken, protokol ailesi için "AF_PACKET" girilir. Soket türü için de "SOCK_RAW" girilmelidir. "IP" protokolü için protokol ailesi yine "AF_INET" ya da "AF_INET6" girilip soket türü "SOCK_RAW" olarak girilebilir. Öte yandan, "TCP" protokolündeki "server" ve "client" kavramları, "UDP" protokolünde çok keskin değildir. Ancak yine de genellikle hizmet alan tarafa "client", hizmet veren tarafa "server" denilmektedir. UDP'de "client" daha çok gönderim yapan, "server" ise okuma yapan taraftır. Bütün bunlara ek olarak, "UDP" özellikle periyodik kısa bilgilerin gönderildiği ve alındığı durumlarda hız nedeniyle tercih edilmektedir. "UDP" haberleşmesinde bilgiyi alan tarafın ("server") bilgi kaçırabilmesi söz konusu olabileceğinden dolayı, böyle kaçırmalarda sistemde önemli bir aksamanın olmaması gerekir. Eğer bilgi kaçırma durumlarında sistemde önemli aksamalar oluşabiliyorsa "UDP" yerine "TCP" tercih edilmelidir. Örneğin, bir televizyon yayınında görüntüye ilişkin bir "frame" karşı tarafça alınmadığında önemli bir aksama söz konusu değildir. Belki görüntüde bir kasis olabilir ancak bu durum önemli kabul edilmemektedir. Benzer şekilde, birtakım makineler belli periyotlarda "server" a "ben çalışıyorum" demek için periyodik "UDP" paketleri yollayabilir. "Server" da hangi makinenin çalışmakta olduğunu, yani bozulmamış olduğunu, bu sayede anlayabilir. Diğer yandan, bir araba simülatörü arabanın durumunu "UDP" paketleriyle dış dünyaya verebilir. Son olarak, "TCP" ve "UDP" protokollerinde bir uzunluk bilgisi yoktur. Uzunluk bilgisi "IP" protokolünde bulunmaktadır. "IPv4" ve "IPv6" protokollerinde bir "IP" paketi en fazla "64K" uzunlukta olabilmektedir. Tabii "TCP" stream tabanlı olduğu için bu "64K" uzunluğun "TCP" için bir önemi yoktur. Ancak "UDP" paket tabanlı olduğu için bir, "UDP" paketi IP paketinin uzunluğunu aşamaz. Dolayısıyla bir "UDP" paketi en fazla "64K" uzunlukta olabilmektedir. Bu yüzden büyük paketlerin "UDP" ile gönderilmesi için programcının paketlere kendisinin manuel numaralar vermesi gerekebilir (zaten "TCP" protokolü bu şekilde bir numaralandırmayı kendi içerisinde yapmaktadır). "UDP" haberleşmesinin önemli bir farkı da "broadcasting" işlemidir. "broadcasting", yerel ağda belli bir "host" cihazın diğer tüm "host" cihazlara "UDP" paketleri gönderebilmesine denilmektedir. "TCP" de böyle bir "broadcasting" mekanizması yoktur. Aşağıda "UDP Header" yapısının gösterimi verilmiştir. <------- Byte 1 -------><------- Byte 2 -------><------- Byte 3 -------><------- Byte 4 -------> +----------------------------------------------+-----------------------------------------------+ ^ | Source Port | Destination Port | (4 bytes) | | (16 bits) | (16 bits) | | +----------------------------------------------+-----------------------------------------------+ | 8 bytes | Header Length | Checksum | (4 bytes) | | (16 bits) | (16 bits) | | +----------------------------------------------+-----------------------------------------------+ v | Application Layer Data | | (Size Varies) | +----------------------------------------------------------------------------------------------+ Görüleceği üzere "UDP Header" yapısı sekiz bayt uzunluğundadır. Şimdi de "UDP Server Program" ve "UDP Client Program" yazımlarını inceleyelim. >>>> "UDP Server Program": Bu program tipik olarak aşağıdaki fonksiyonları sırayla çağırarak gerçekleştirilir. "socket (SOCK_DGRAM)" ---> "bind" ---> "recvfrom/sendto" ---> "close" >>>> "UDP Client Program": Bu program tipik olarak aşağıdaki fonksiyonları sırayla çağırarak gerçekleştirilir. "socket (SOCK_DGRAM)" ---> "bind (isteğe bağlı)" ---> "gethostbyname/getaddrinfo (isteğe bağlı)" ---> "sendto/recvfrom" ---> "close" Bu fonksiyonlardan "socket", "bind" ve "close" fonksiyonlarını görmüştük. Dikkat etmemiz gereken nokta "socket" fonksiyonuna artık "SOCK_DGRAM" sembolik sabitini geçmemiz ve haberleşmenin sonlanması sırasında "shutdown" fonksiyonuna gerek kalmamasıdır. "recvfrom" ile "sendto" ise aşağıdaki gibidir. -> "recvfrom" : "UDP" paketlerini okumak için kullanılan recvfrom prototipi şöyledir: #include ssize_t recvfrom(int socket, void *buffer, size_t length, int flags, struct sockaddr *address, socklen_t *address_len); Fonksiyonun birinci parametresi okuma işleminin yapılacağı soketi belirtir. İkinci parametre alınacak bilginin yerleştirileceği adresi belirtmektedir. Üçüncü parametre, ikinci parametredeki alanın uzunluğunu belirtir. Eğer buradaki değer "UDP" paketindeki gönderilmiş olan bayt sayısından daha az ise kırpılarak diziye yerleştirme yapılmaktadır. Fonksiyonun üçüncü parametresi birkaç bayrak değerine sahiptir ki bunlar "MSG_PEEK", "MSG_OOB", "MSG_WAITALL" isimli bayraklardır. Fakat bu parametre için "0" girilebilir. Fonksiyonun dördüncü parametresi "UDP" paketini gönderen tarafın "IP" adresinin ve "port" numarasının yerleştirileceği "sockaddr_in" yapısının adresini alır. Son parametre ise bu yapının uzunluğunu tutan "int" nesnenin adresini almaktadır. Fonksiyon başarı durumunda "UDP" paketindeki byte sayısına, başarısızlık durumunda "-1" değerine geri dönmektedir. Eğer okunacak paket yoksa ve karşı taraf da haberleşmeyi sonlandırmışsa, fonksiyon "0" ile geri dönmektedir. Diğer yandan "recvfrom" fonksiyonunun herhangi bir "client" tan gelen paketi alabildiğine dikkat ediniz. Dolayısıyla her "recvfrom" ile alınan paket farklı bir "client" a ilişkin olabilmektedir. Son olarak, "recvfrom" fonksiyonu, eğer soketi blokeli moddaysa ki varsayılan durum budur, "UDP" paketi gelene kadar blokeye yol açar. Blokesiz modda ise fonksiyon bekleme yapmaz ve "-1" değeriyle geri döner ve "errno" değişkeni "EAGAIN" değeriyle "set" edilir. -> "sendto" : Fonksiyonunun prototipi de şöyledir: #include ssize_t sendto(int socket, const void *message, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t dest_len); Fonksiyonun parametreleri "recvfrom" da olduğu gibidir. Yani birinci parametre gönderim yapılacak soketi belirtir. İkinci ve üçüncü parametreler gönderilecek bilgilerin bulunduğu tamponu ve onun uzunluğunu belirtmektedir. Yine bu fonksiyonda da bir "flags" parametresi vardır. Dördüncü parametre bilginin gönderileceği "IP" adresini ve "port" numarasını belirtir. Son parametre ise dördüncü parametredeki yapının ("sockaddr_in" ya da "sockaddr_in6") uzunluğunu alır. Fonksiyon blokeli modda, bilgi pakedi "network" tamponuna yazılana kadar, blokeye yol açmaktadır. "sendto" fonksiyonu da başarı durumunda "network" tamponuna yazılan bayt sayısına, başarısızlık durumunda "-1" e geri dönmektedir. Şimdi de bu anlatılanların gösterildiği bir adet "server-client" uygulama örneği verelim. * Örnek 1, Aşağıda tipik bir "UDP client-server" örneği verilmiştir. Bu örnekte "client", yine bir "prompt" a düşerek kullanıcıdan bir yazı istemektedir. Bu yazıyı "UDP" paketi biçiminde "server" a yollamaktadır. "Server" da bu yazıyı alıp görüntüledikten sonra yazıyı ters çevirip "client" a geri yollamaktadır. Programların komut satırı argümanları diğer örneklerde olduğu gibidir. /* server.c */ #include #include #include #include #include #include #include #include #define DEF_SERVER_PORT 55555 #define BUFFER_SIZE 4096 char *revstr(char *str); void exit_sys(const char *msg); /* ./server [-p port] */ int main(int argc, char *argv[]) { int server_sock; struct sockaddr_in sin_server, sin_client; socklen_t sin_len; int server_port; char buf[BUFFER_SIZE + 1]; char ntopbuf[INET_ADDRSTRLEN]; ssize_t result; int option; int p_flag, err_flag; p_flag = err_flag = 0; opterr = 0; while ((option = getopt(argc, argv, "p:")) != -1) { switch (option) { case 'p': p_flag = 1; server_port = atoi(optarg); break; case '?': if (optopt == 'p') fprintf(stderr, "-%c option must have an argument!\n", optopt); else fprintf(stderr, "-%c invalid option!\n", optopt); err_flag = 1; } } if (err_flag) exit(EXIT_FAILURE); if (optind - argc != 0) { fprintf(stderr, "too many arguments!...\n"); exit(EXIT_FAILURE); } if (!p_flag) server_port = DEF_SERVER_PORT; if ((server_sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) exit_sys("socket"); sin_server.sin_family = AF_INET; sin_server.sin_port = htons(server_port); sin_server.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(server_sock, (struct sockaddr *)&sin_server, sizeof(sin_server)) == -1) exit_sys("bind"); printf("waiting UDP packet...\n"); for (;;) { sin_len = sizeof(sin_client); if ((result = recvfrom(server_sock, buf, BUFFER_SIZE, 0, (struct sockaddr *)&sin_client, &sin_len)) == -1) exit_sys("recvfrom"); buf[result] = '\0'; inet_ntop(AF_INET, &sin_client.sin_addr, ntopbuf, INET_ADDRSTRLEN); printf("%jd byte(s) received from %s:%u: \"%s\"\n", (intmax_t)result, ntopbuf, (unsigned)ntohs(sin_client.sin_port), buf); revstr(buf); if (sendto(server_sock, buf, result, 0, (struct sockaddr *)&sin_client, sizeof(sin_client)) == -1) exit_sys("sendto"); } close(server_sock); return 0; } char *revstr(char *str) { size_t i, k; char temp; for (i = 0; str[i] != '\0'; ++i) ; for (--i, k = 0; k < i; ++k, --i) { temp = str[k]; str[k] = str[i]; str[i] = temp; } return str; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } /* client.c */ #include #include #include #include #include #include #include #include #include #define DEF_SERVER_NAME "localhost" #define DEF_SERVER_PORT "55555" #define BUFFER_SIZE 4096 void exit_sys(const char *msg); /* ./client [-s server] [-p server_port] [-b client_port] */ int main(int argc, char *argv[]) { int client_sock; struct sockaddr_in sin_client, sin_server; socklen_t sin_len; struct addrinfo hints = {0, AF_INET, SOCK_STREAM}; struct addrinfo *res; int gai_result; int option; int s_flag, p_flag, b_flag, err_flag; const char *server_name; int bind_port; const char *server_port; char buf[BUFFER_SIZE + 1]; /* BUFFER_SIZE is enough */ char ntopbuf[INET_ADDRSTRLEN]; ssize_t result; char *str; s_flag = p_flag = b_flag = err_flag = 0; opterr = 0; while ((option = getopt(argc, argv, "s:p:b:")) != -1) { switch (option) { case 's': s_flag = 1; server_name = optarg; break; case 'p': p_flag = 1; server_port = optarg; break; case 'b': b_flag = 1; bind_port = atoi(optarg); break; case '?': if (optopt == 's' || optopt == 'p' || optopt == 'b') fprintf(stderr, "-%c option must have an argument!\n", optopt); else fprintf(stderr, "-%c invalid option!\n", optopt); err_flag = 1; } } if (err_flag) exit(EXIT_FAILURE); if (optind - argc != 0) { fprintf(stderr, "too many arguments!...\n"); exit(EXIT_FAILURE); } if (!s_flag) server_name = DEF_SERVER_NAME; if (!p_flag) server_port = DEF_SERVER_PORT; if ((client_sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) exit_sys("socket"); if (b_flag) { sin_client.sin_family = AF_INET; sin_client.sin_port = htons(bind_port); sin_client.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(client_sock, (struct sockaddr *)&sin_client, sizeof(sin_client)) == -1) exit_sys("bind"); } if ((gai_result = getaddrinfo(server_name, server_port, &hints, &res)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(gai_result)); exit(EXIT_FAILURE); } freeaddrinfo(res); for (;;) { printf("csd>"); fflush(stdout); if (fgets(buf, BUFFER_SIZE, stdin) == NULL) continue; if ((str = strchr(buf, '\n')) != NULL) *str = '\0'; if (!strcmp(buf, "quit")) break; if (sendto(client_sock, buf, strlen(buf), 0, res->ai_addr, sizeof(struct sockaddr_in)) == -1) exit_sys("send"); sin_len = sizeof(sin_server); if ((result = recvfrom(client_sock, buf, BUFFER_SIZE, 0, (struct sockaddr *)&sin_server, &sin_len)) == -1) exit_sys("recvfrom"); buf[result] = '\0'; inet_ntop(AF_INET, &sin_server.sin_addr, ntopbuf, INET_ADDRSTRLEN); printf("%jd byte(s) received from server %s:%u: \"%s\"\n", (intmax_t)result, ntopbuf, (unsigned)ntohs(sin_server.sin_port), buf); } close(client_sock); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } "UDP/IP" konusundaki şu noktalar da önemlidir: -> Anımsanacağı üzere "connect" ve "accept" çağrılarının "TCP" de kullanıldığından bahsetmiştik. Açıkçası bu fonksiyonları pekala "UDP" de de kullanabiliriz. Eğer "UDP" bir soket, "connect" ile "UDP" "server" a bağlanırsa ki "server" da bunu "accept" ile kabul etmelidir, bu durumda artık iki taraf da "recvfrom" ve "sendto" fonksiyonlarının yerine "recv" ve "send" fonksiyonlarını kullanabilir. Tabii burada yine datagram haberleşmesi yapılmaktadır. Yalnızca her defasında gönderme ve alma işlemlerinde karşı tarafın soketine ilişkin bilgilerin belirtilmesine gerek kalmamaktadır. Bu biçimdeki "connect" ve "accept" bağlantısında yine bir akış kontrolü uygulanmamaktadır. -> Aslında "UDP" de kullandığımız "recvfrom" ve "sendto" fonksiyonları yerine "recv" ve "send" fonksiyonlarını da kullanabilirdik. Bunun için "recvfrom" ve "sendto" fonksiyonlarının son iki parametresine "NULL" değerini geçmeliyiz. Şöyleki, result = recv(sock, buf, len, flags); result = recvfrom(sock, buf, len, flags, NULL, NULL); çağrısı ile result = send(sock, buf, len, flags); result = sendto(sock, buf, len, flags, any_value, any_value); çağrıları eş değerdir. >> "Server-Client" arasındaki haberleşmenin detayları: >>> "TCP/IP" ya da "UDP/IP" kullanılarak oluşturulan "server" uygulamalarında, "server" programın gelen istekleri yerine getirmesi, zaman kaybı oluşturabilmektedir. Çünkü bir "client" programdan gelen istekleri işlerken, diğer "client" programları bekletmek durumundadır. Bu bekletmeyi en aza indirmek için de çeşitli çözümler geliştirilmiştir. Örneğin, "client" programlardan gelen istekleri bir "thread" e yaptırtmak gibi. Bu durumda da "thread" oluşturma ve yok etmenin maliyeti karşımıza çıkmaktadır. Çünkü çözümün "scalable" olması da gerekmektedir. Bu problem için de işin başında belli sayıda "thread" in hayata getirilmesi fakat "suspend" edilmesi, yani "thread pool" mekanizmasının kullanılması, gerekmektedir. Fakat gerek POSIX gerek Standart C fonksiyonlarında böyle bir mekanizma standart olarak mevcut olmadığından, ya bizler geliştirmeliyiz ya da üçüncü kişiler tarafından geliştirilenleri kullanmalıyız. (Windows sistemlerinde böyle bir mekanizma mevcuttur.) İşte bu noktada sistemimizdeki çekirdek sayısı devreye girmektedir. Çünkü tavsiye edilen şudur ki sistemimizdeki çekirdek adedince "thread" oluşturup, bir takım "Processor Affinity" teknikleriyle, işleri bu "thread" lere eş zamanlı olarak yaptırılması taktirde gerçekten de verim elde etmiş olacağız. Aksi halde, sistemimizdeki çekirdek sayısından daha fazla "thread" kullanmamız durumunda, gözle görülür bir verim elde edemeyebiliriz. Buradaki kilit nokta sistemimizdeki her bir işlemcinin kendine has "Run Queue" ya sahip olması, bizlerin de oluşturduğumuz "thread" leri ayrı ayrı bunlara ataması, dolayısıyla bir "t" anında bütün çekirdeklerin bizim için çalışmasıdır. Tabii bunun gerçekleştirim yöntemleri birden fazla olabilir. Şöyleki; -> Sistemimizdeki çekirdek sayısınca "thread" oluşturulup, her bir "thread" ayrı ayrı aşağıdaki kodu çağırır: for (;;) { recvfrom(...) } -> "server" program aldıklarını bir kuyruğa yazar. Oluşturduğumuz "thread" ler ise bu kuyruktan alım yaparlar. Yani, for (;;) { recvfrom(...) } Bu ikisi arasındaki en temel fark birisinde gelenler önce kuyrukta biriktirilir, diğerinde ise direkt olarak ilgili "thread" tarafından işleme alınır. (Linux sistemlerindeki "epoll" modelinde, bu şekilde bir "thread" li kullanımda, Linux genel olarak her işlemci ya da çekirdek için gerektiğinde kendisi "thread" oluşturmaktadır. Dolayısıyla "epoll" modelinde yukarıdaki gibi bir organizasyon yapılmasa da daha iyi bir performans göstermektedir.) Pekiyi bu çözüm de yeterli gelmezse ne yapmamız gerekmektedir? İşte bu noktada devreye "Load Balancing" mekanizması ki işletmecisine de "Load Balancer" denir, girer. Bu mekanizmaya göre "server" makinenin bir klonu çıkartılır ve sistem içerisine dahil edilir. Anlık olarak "server" makinaların iş yükü kontrol edilir ve "client" programlardan gelen istekler, "server" programların yoğunluğuna göre, sistem içerisinde dağıtılır. İşte bu dağıtımı yapana "Load Balancer", sistemin kendisine ise "Load Balancing" denir. Fakat burada iki farklı yaklaşım söz konusudur. Bu sistemin kabaca çalışma mantığı ise şöyledir; -> "client" program aslında "Load Balancer" ile bağlantı sağlar. "Load Balancer" ise "client" programı, en az meşgul olan "server" a iletir. "Load Balancing" sistemi "hardware" temelli kurulabileceği gibi "software" temelli de kurulabilir. Yazılımsal gerçekleştirim, donanımsala göre, daha esnek olabilmektedir. Ancak donanımsal gerçekleştirimler yine bazı durumlarda daha etkin olabilmektedir. Donanmsal "Load Balancer" larda "client", "server" ile bağlantı kurmak istediğinde, "Load Balancer" devreye girip sanki yalnızca en az meşgul olan "server" sistemde varmış gibi bağlantıyı onun kabul etmesini sağlamaktadır. Yazılımsal "Load Balancer" larda "Load Balancer" ise bir "proxy" gibi çalışmaktadır. "client", "Load Balancer" ile bağlantı sağlar. "Load Balancer" ise bunu en az meşgul "server" a yönlendirir. Bu kez "client", bu server ile bağlantı kurar. Buradaki "Load Balancer" görevini yapan "proxy" programınının "server" yüklerini sürekli izlemesi gerekmektedir. Bunun için genellikle "UDP" protokolü kullanılmaktadır. Yani "UDP" ile "server" makineler sürekli bir biçimde kendi durumlarını "proxy" ye iletirler. "proxy" de bu bilgilerden hareketle en az meşgul "server" ı tespit eder. Tabii "server" makineler eğer devre dışı kalırsa "proxy" inin bunu fark etmesi ve artık ona yönlendirme yapmaması gerekir. Benzer biçimde yeni bir "server" makinesi sisteme eklendiğinde "proxy" hemen onu da sisteme otomatik olarak dahil etmelidir. >>> Şimdiye kadarki "Server-Client" program örneklerinde bizler metin mesajı gönderdik ve aldık. Halbuki gerçek hayattaki uygulamalarda veri alışverişi bu kadar basit değildir, daha karmaşıktır. "client" program, "server" programdan, çok çeşitli şeyleri yapmasını isteyebilir. Pekiyi bu istekler nasıl iletilir? Bu problem için iki farklı teknik kullanılır. Bunlar, "binary-based" ve "text-based" yaklaşımlardır. Bu yaklaşımlardan, >>>> "binary-based" : Bu yönteme göre mesajı gönderecek kişi ilk olarak mesajın tipini ve uzunluk bilgisini bir yapı nesnesi içerisine almak suretiyle gönderir. Daha sonra da gönderilecek mesajın kendisini karşı tarafa gönderir. Yani gönderen taraf kabaca şu şekilde bir süreç işletir; //... typedef struct tagMSG_HEADER { int len; int type; } MSG_HEADER; // Mesajın tipini ve uzunluk bilgisini göndereceğimiz o yapı nesnesi. typedef struct tagMSG_XXX { // message info } MSG_XXX; // Gönderilecek mesajın kendisi. typedef struct tagMSG_YYY { // message info } MSG_YYY; // Gönderilecek bir başka mesajın kendisi. //... MSG_HEADER header; header.len = sizeof(MSG_XXX); header.type = MSG_TYPE_XXX; send(sock, &header, sizeof(MSG_HEADER), 0); MSG_XXX msg_xxx; // Mesajın içeriği doldurulur. send(sock, &msg_xxx, sizeof(MSG_XXX), 0); MSG_YYY MSG_yyy; // Mesajın içeriği doldurulur. send(sock, &MSG_yyy, sizeof(MSG_YYY), 0); //... Buna karşın mesajı alan da kabaca aşağıdaki gibi bir süreç işletir; //... typedef struct tagMSG_HEADER { int len; int type; } MSG_HEADER; // Mesajın tipini ve uzunluk bilgisini göndereceğimiz o yapı nesnesi. //... MSG_HEADER header; recv(sock, &header, sizeof(MSG_HEADER), MSG_WAITALL); switch (header.type) { case MSG_TYPE_XXX: recv(sock, &msg_xxx, sizeof(header.len), MSG_WAITALL); process_msg_xxx(&msg_xxx); break; case MSG_TYPE_YYY: recv(sock, &msg_yyy, sizeof(header.len), MSG_WAITALL); process_msg_yyy(&msg_yyy); break; // ... } //... Burada aklınıza şöyle bir soru gelebilir: Okuyan taraf zaten mesajın kodunu (numarasını) elde edince o mesajın kaç bayt uzunlukta olduğunu bilmeyecek mi? Bu durumda mesajın uzunluğunun karşı tarafa iletilmesine ne gerek var? İşte mesajlar sabit uzunlukta olmayabilir. Örneğin mesajın içerisinde bir metin bulunabilir. Bu durumda mesajın gerçek uzunluğu bu metnin uzunluğuna bağlı olarak değişebilir. Genel bir çözüm için mesajın uzunluğunun da karşı tarafa iletilmesi gerekmektedir. >>>> "text-based" : Her ne kadar "binary-based" yöntemi daha hızlı ve etkin olma eğiliminde ise de pratikte daha çok "text-based" yöntemi kullanılmaktadır. Çünkü bu yöntemde mesajlaşmalar insanlar tarafından yazısal biçimde de oluşturulabilmektedir. Örneğin, "IP" protokol ailesinin uygulama katmanındaki "POP3", "Telnet", "FTP" gibi protokoller "text-based" mesajlaşmayı kullanmaktadır. Bu yöntem ise işleyiş kabaca şu şekildedir; -> "client" programdan "server" programa ve "server" programdan "client" programa gönderilen mesajlar bir yazı olarak gönderilir. Karşı taraf bu yazıyı alır, "parse" eder ve gereğini yapar. Programlama dillerinde yazılarla işlem yapabilen pek çok standart araç bulunduğu için bu biçimde mesajların işlenmesi genel olarak daha kolaydır. Ancak mesaj tabanlı haberleşme genel olarak daha yavaştır. Çünkü birtakım bilgilerin yazısal olarak ifade edilmesi "binary" ifade edilmesinden genel olarak daha fazla yer kaplama eğilimindedir. Diğer taraftan "text-based" yöntemdeki bir diğer handikap ise mesajın nerede bittiğinin tespit edilmesinin güç olmasıdır. Bu problem için de bir kaç çözüm geliştirilmiştir. Bu çözümlerden en çok kullanılanı, gönderilen mesajların sonuna bir takım özel karakterler eklenmesidir. Örneğin "IP" protokol ailesinin uygulama katmanındaki protokoller genel olarak mesajları "CR/LF ('\r' ve '\n' çiftiyle)" bitirmektedir. Tabii bu biçimdeki mesajlaşmalarda soketten "CR/LF" çifti görülene kadar okuma yapılması gerekir. Bu işlem soketten "byte-byte" okuma ile yapılmamalıdır. Çünkü her bayt okuması için prosesin "kernel" moda geçmesi zaman kaybı oluşturmaktadır. Belli bir karakter ya da karakter kümesi görülene kadar soketten okuma işleminin yapılması gerekmektedir. Bunun için şöyle bir yöntem izlenebilir; -> Karakterler soketten tek tek okunmaz. Blok blok okunurak bir tampona yerleştirilir. -> Sonra bu tampondan karakterler elde edilir. Tabii blok okuması yapıldığında birden fazla satır tamponda bulunabilecektir. -> Bu durumda okuma sırasında tamponda nerede kalındığının da tutulması gerekir. İşte bu gerçekleştirimi sağlayan klasik bir algoritma da "Effective TCP/IP Programming" kitabında verilmiştir. Takribi olarak aşağıdaki gibi bir algoritmadır: ssize_t sock_readline(int sock, char *str, size_t size) { char *bstr = str; static char *bp; static ssize_t count = 0; static char buf[2048]; if (size <= 2) { errno = EINVAL; return -1; } while (--size > 0) { if (--count <= 0) { if ((count = recv(sock, buf, sizeof(buf), 0)) == -1) return -1; if (count == 0) return 0; bp = buf; } *str++ = *bp++; if (str[-1] == '\n') if (str - bstr > 1 && str[-2] == '\r') { *str = '\0'; break; } } return (ssize_t)(str - bstr); // The result will be cast to 'ssize_t' from 'ptrdiff_t'. } Bu fonksiyonun birinci parametresi okuma yapılacak soketi, ikinci ve üçüncü parametreleri okunacak satırın yerleştirileceği dizinin adresini ve uzunluğunu almaktadır. Buradaki dizinin sonunda her zaman "CR/LF" ve "NULL" karakter bulunacaktır. Fonksiyon başarı durumunda diziye yerleştirilen karakter sayısı ile ("CR/LF" dahil) geri dönmektedir. Karşı taraf soketi kapatmışsa ve hiçbir okuma yapılamamışsa bu durumda fonksiyon "0" ile geri dönmektedir. Bu durumda programcının verdiği dizinin içeriği kullanılmamalıdır. Mesajın sonunda "CR/LF" çifti olmadıktan sonra fonksiyon başarılı okuma yapmamaktadır. Fonksiyon başarısızlık durumunda "-1" değerine geri döner ve "errno" uygun biçimde "set" edilir. Buradaki "sock_readline" fonksiyonu bir satır okunana kadar blokeye yol açmaktadır. Dolayısıyla çok "client" lı "server" uygulamalarında "select", "poll" ve "epoll" gibi modellerde bu fonksiyon bu haliyle kullanılamaz. Örneğin, biz "select" fonksiyonunda bir grup soketi bekliyor olalım. Bir sokete bir satırın yarısı gelmiş olabilir. Bu durumda biz "read_line" fonksiyonunu çağırırsak bloke oluşacaktır. Tabii gerçi satırın geri kalan kısmı zaten kısa bir süre sonra gelecek olsa da bu durum yine bir kusur oluşturacaktır. Aşağıda bu fonksiyonun kullanımına ilişkin bir örnek verilmiştir. * Örnek 1, Aşağıdaki örnekte "client" program, "server" programa "CR/LF" ile sonlandırılmış bir yazı göndermektedir. "server" program da bu yazıyı yukarıdaki "sock_readline" fonksiyonunu kullanarak okuyup ekrana "(stdout dosyasına)" yazdırmaktadır. /* server.c */ #include #include #include #include #include #include #include #include #include #define DEF_SERVER_PORT 55555 #define BUFFER_SIZE 4096 ssize_t sock_readline(int sock, char *str, size_t size); void exit_sys(const char *msg); /* ./server [-p port] */ int main(int argc, char *argv[]) { int server_sock, client_sock; struct sockaddr_in sin_server, sin_client; socklen_t sin_len; char buf[BUFFER_SIZE + 1]; char ntopbuf[INET_ADDRSTRLEN]; ssize_t result; int option; int server_port; int p_flag, err_flag; p_flag = err_flag = 0; opterr = 0; while ((option = getopt(argc, argv, "p:")) != -1) { switch (option) { case 'p': p_flag = 1; server_port = atoi(optarg); break; case '?': if (optopt == 'p') fprintf(stderr, "-%c option must have an argument!\n", optopt); else fprintf(stderr, "-%c invalid option!\n", optopt); err_flag = 1; } } if (err_flag) exit(EXIT_FAILURE); if (optind - argc != 0) { fprintf(stderr, "too many arguments!...\n"); exit(EXIT_FAILURE); } if (!p_flag) server_port = DEF_SERVER_PORT; if ((server_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); sin_server.sin_family = AF_INET; sin_server.sin_port = htons(server_port); sin_server.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(server_sock, (struct sockaddr *)&sin_server, sizeof(sin_server)) == -1) exit_sys("bind"); if (listen(server_sock, 8) == -1) exit_sys("listen"); printf("listening port %d\n", server_port); printf("waiting for connection...\n"); sin_len = sizeof(sin_client); if ((client_sock = accept(server_sock, (struct sockaddr *)&sin_client, &sin_len)) == -1) exit_sys("accept"); printf("connected client ===> %s:%u\n", inet_ntop(AF_INET, &sin_client.sin_addr, ntopbuf, INET_ADDRSTRLEN), (unsigned)ntohs(sin_client.sin_port)); for (;;) { if ((result = sock_readline(client_sock, buf, BUFFER_SIZE)) == -1) exit_sys("sock_readline"); if (result == 0) break; if (!strcmp(buf, "quit\r\n")) break; buf[strlen(buf) - 2] = '\0'; printf("%jd byte(s) received: \"%s\"\n", (intmax_t)result, buf); } shutdown(client_sock, SHUT_RDWR); close(client_sock); close(server_sock); return 0; } ssize_t sock_readline(int sock, char *str, size_t size) { char *bstr = str; static char *bp; static ssize_t count = 0; static char buf[2048]; if (size <= 2) { errno = EINVAL; return -1; } while (--size > 0) { if (--count <= 0) { if ((count = recv(sock, buf, sizeof(buf), 0)) == -1) return -1; if (count == 0) return 0; bp = buf; } *str++ = *bp++; if (str[-1] == '\n') if (str - bstr > 1 && str[-2] == '\r') { *str = '\0'; break; } } return (ssize_t) (str - bstr); } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } /* client.c */ #include #include #include #include #include #include #include #include #define DEF_SERVER_NAME "127.0.0.1" #define DEF_SERVER_PORT "55555" #define BUFFER_SIZE 4096 void exit_sys(const char *msg); /* ./client [-s server] [-p server_port] [-b client_port] */ int main(int argc, char *argv[]) { int client_sock; struct sockaddr_in sin_client; struct addrinfo hints = {0, AF_INET, SOCK_STREAM}; struct addrinfo *res, *ri; int gai_result; char buf[BUFFER_SIZE]; char *str; int option; int s_flag, p_flag, b_flag, err_flag; const char *server_name; int bind_port; const char *server_port; s_flag = p_flag = b_flag = err_flag = 0; opterr = 0; while ((option = getopt(argc, argv, "s:p:b:")) != -1) { switch (option) { case 's': s_flag = 1; server_name = optarg; break; case 'p': p_flag = 1; server_port = optarg; break; case 'b': b_flag = 1; bind_port = atoi(optarg); break; case '?': if (optopt == 's' || optopt == 'p' || optopt == 'b') fprintf(stderr, "-%c option must have an argument!\n", optopt); else fprintf(stderr, "-%c invalid option!\n", optopt); err_flag = 1; } } if (err_flag) exit(EXIT_FAILURE); if (optind - argc != 0) { fprintf(stderr, "too many arguments!...\n"); exit(EXIT_FAILURE); } if (!s_flag) server_name = DEF_SERVER_NAME; if (!p_flag) server_port = DEF_SERVER_PORT; if ((client_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); if (b_flag) { sin_client.sin_family = AF_INET; sin_client.sin_port = htons(bind_port); sin_client.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(client_sock, (struct sockaddr *)&sin_client, sizeof(sin_client)) == -1) exit_sys("bind"); } if ((gai_result = getaddrinfo(server_name, server_port, &hints, &res)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(gai_result)); exit(EXIT_FAILURE); } for (ri = res; ri != NULL; ri = ri->ai_next) if (connect(client_sock, ri->ai_addr, ri->ai_addrlen) != -1) break; if (ri == NULL) exit_sys("connect"); freeaddrinfo(res); printf("connected server...\n"); for (;;) { printf("csd>"); fflush(stdout); if (fgets(buf, BUFFER_SIZE, stdin) == NULL) continue; if ((str = strchr(buf, '\n')) != NULL) *str = '\0'; strcat(str, "\r\n"); if (send(client_sock, buf, strlen(buf), 0) == -1) exit_sys("send"); if (!strcmp(buf, "quit\r\n")) break; } shutdown(client_sock, SHUT_RDWR); close(client_sock); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } Bir diğer yöntem ise, baştan yazının uzunluğunun iletilmesidir. Yani önce mesajın uzunluğu gönderilir. Daha sonra mesajın kendisi. Karşı taraf da önce yazının uzunluğunu alır, daha sonra o uzunluk kadar okuma yapar. Şimdi de "recv" / "recvfrom" ve "send" / "sendto" fonksiyonlarında kullandığımız sembolik sabitler olan "MSG_PEEK", "MSG_OOB", "MSG_WAITALL", "MSG_EOR", "MSG_NOSIGNAL" vb. bayrakların işlevleri nelerdir? Bu bayrakların işlevleri kabaca şunlardır: -> "MSG_PEEK" : Bu bayrak gelen bilginin tampondan okunacağını ancak tampondan atılmayacağını belirtmektedir. Yani biz "MSG_PEEK" bayrak değeri ile okuma yaparsak hem bilgiyi elde ederiz hem de sanki hiç okuma yapmamışız gibi bilgi "network" tamponunda kalır. Dolayısıyla bizim "thread" imiz ya da başka bir "thread" "recv" yaparsa tampondakini okuyacaktır. Bazen (çok seyrek olarak) mesajın ne olduğuna "MSG_PEEK" ile bakıp duruma göre onu kuyruktan almak isteyebiliriz. Eğer mesaj bizim beğenmediğimiz bir mesajsa onu almak istemeyebiliriz. Onun başka bir "thread" tarafından işlenmesini sağlayabiliriz. İşte bu amaç için bu bayrağı kullanabiliriz. -> "MSG_OOB" : "Out-of-band data (urgent data)" denilen okumalar için kullanılmaktadır. "Out-of-band data" konusu ayrı bir paragrafta açıklanacaktır. -> "MSG_WAITALL" : Bu bayrak "n-byte" okunmak istendiğinde bu "n-byte" ın hepsi okunana kadar bekleme sağlamaktadır. Fakat bu durumda bir sinyal geldiğinde yine "recv", "-1" ile geri döner ve "errno" değişkeni "EINTR" ile "set" edilir. Yine soket kapatıldığında ya da soket üzerinde bir hata oluştuğunda fonksiyon talep edilen kadar bilgiyi okuyamamış olabilir. Biz daha önce "n-byte" okuma yapmak için aşağıdaki gibi bir fonksiyon önermiştik: ssize_t read_socket(int sock, char *buf, size_t len) { size_t left, index; left = len; index = 0; while (left > 0) { if ((result = recv(sock, buf + index, left, 0)) == -1) return -1; if (result == 0) break; index += result; left -= result; } return (ssize_t) index; } İşte aslında "recv" fonksiyonundaki "MSG_WAITALL" bayrağı adeta bunu sağlamaktadır. Ancak yine de bu bayrağın bazı sistemlerde bazı problemleri vardır. Örneğin, "Windows" sistemlerinde ve "Linux" sistemlerinde bu bayrakla okunmak istenen miktar "network" alım tamponunun büyüklüğünden fazlaysa istenen miktarda bayt okunamayabilmektedir. Ancak bu bayrak yüksek olmayan miktarlarda okumalar için, yukarıdaki fonksiyonun yerine, kullanılabilmektedir. -> "MSG_EOR" : Soket türü "SOCK_SEQPACKET" ise kaydı sonlandırmakta kullanılır. -> "MSG_NOSIGNAL" : Normal olarak "send" ya da "write" işlemi yapılırken karşı taraf soketi kapatmışsa bu fonksiyonların çağrıldığı tarafta "SIGPIPE" sinyali oluşmaktadır. Ancak bu bayrak kullanılırsa böylesi durumlarda "SIGPIPE" sinyali oluşmaz, "send" ya da "write" fonksiyonu "-1" ile geri döner ve "errno" değişkeni "EPIPE" değeri ile "set" edilir. Yine Linux sistemlerine ögzü bazı bayraklar da mevcuttur. Örneğin "recv" ve "send" işleminde "MSG_DONTWAIT" bayrağı bir çağrımlık "non-blocking" etki yaratmaktadır. Yani "recv" sırasında "network" tamponunda hiç bilgi yoksa "recv" bloke olmaz, "-1" ile geri döner ve "errno" değişkeni "EAGAIN" değeri ile "set" edilir. "send" işlemi sırasında da "network" tamponu dolu ise "send" bloke olmaz "-1" ile geri döner ve "errno" yine "EAGAIN" değeri ile "set" edilir. Şimdi de bir takım "Client-Server" programların nasıl yazıldıklarına değinelim: >>> Matematiksel işlem yapabilen bir "Client-Server" program yazmak isteyelim. Birden fazla "client" program, "server" programımıza bağlanabilsin ve her bir "client" için de "thread" kullanalım. Genel olarak bu tip programlarda "client" program bir mesaj gönderdiğinde "server" program kendisine geri dönüş yapar. Bu geri dönüş olumlu ise istediği şeyin sonucu, olumsuz ise hatanın nedenine ilişkindir. Yine bu tür programlarda "client" programın kullanacağı komutlar ile "server" programın karşılık vereceği komutlar da önceden belirlenmiş durumdadır. Diğer taraftan, "Application Layer" daki protokollerde gerçekleşen fiziksel bağlantı, aslında hizmet almak için yeterli değildir. "client" programların ayrıca "server" programla mantıksal bağlantı da kurması gerekmektedir. Yani "server" ile "client" programlar birbirlerine gönderdikleri komutların manalarını da bilmeleri gerekmektedir. Örneğin, "client" program "LOGIN" komutuyla birlikte kullanıcı adı ve şifre bilgisini "server" a iletir. "server" program da doğrulamanın sağlanması durumunda "LOGIN_ACCEPTED", sağlanamaması durumunda "ERROR LOGIN_FAILED" mesajını tekrar "client" programa iletir. Bu şekilde iki program arasında mantıksal bağlantı da kurulmuş olur. Aşağıda bu konuya ilişkin bir örnek de verilmiştir. * Örnek 1, Bu örnekte her "client" bağlantısında bir "thread" açılıp o "thread" yoluyla ilgili "client" ile konuşulmaktadır. Yani buradaki "server", "IO" modeli olarak, "thread" modelini kullanmaktadır. Bir "client", "server" a kullanıcı adı ve parola ile bağlanmaktadır. "server" program bir "CSV" dosyasına bakarak kullanıcı adı ve parola bilgisini doğrulamaktadır. Bir "client" bağlandığında "server" program "CLIENT_INFO" isimli bir yapı türünden bir nesne yaratıp "client" ın bilgilerini orada saklamaktadır. Aslında bu tür programlarda tüm "client" ların bilgileri bir dizi ya da bağlı liste içerisinde tutulmalıdır. Çünkü "server" tüm "client" lara belli bir mesajı göndermek isteyebilir. "server" programı aşağıdaki gibi derleyebilirsiniz: gcc -Wall -o calc-server calc-server.c -lm Prototipleri "math.h" içerisinde olan Standart C fonksiyonları "libc" kütüphanesinde değildir. "libm" isimli ayrı bir kütüphanededir. Maalesef "gcc" otomatik olarak bu kütüphaneyi link aşamasına dahil etmemektedir. Bu nedenle matematiksel fonksiyonları kullanırken "linker" için "-lm" seçeneğinin bulundurulması gerekmektedir. "client" program, yani "calc-client.c", "server" a fiziksel olarak "TCP" ile bağlandıktan sonra ona kullanıcı adı ve parolayı mesaj olarak gönderir. Sonra bir komut satırına düşer. Komutlar komut satırından verilmektedir. "client" programın "server" programa gönderebileceği komutlar, "LOGIN \r\n" "ADD op1 op2\r\n" "SUB op1 op2\r\n" "MUL op1 op2\r\n" "DIV op1 op2\r\n" "SQRT op1\r\n" "POW op1\r\n" "LOGOUT\r\n" şeklindedir. Diğer taraftan "server" programın "client" programa göndereceği mesajlar ise, "LOGIN_ACCEPTED\r\n" "LOGOUT_ACCEPTED\r\n" "RESULT result\r\n" "ERROR message\r\n" şeklinde olacaktır. /* calc-server.c */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define CREDENTIALS_PATH "credentials.csv" #define DEF_SERVER_PORT 55555 #define MAX_MSG_SIZE 4096 #define MAX_MSG_PARAMS 32 #define MAX_USER_NAME 64 #define MAX_PASSWORD 64 #define MAX_CREDENTIALS 1024 typedef struct tagCREDENTIAL { char user_name[MAX_USER_NAME]; char password[MAX_PASSWORD]; } CREDENTIAL; typedef struct tagCLIENT_INFO { int sock; struct sockaddr_in sin; char buf[MAX_MSG_SIZE + 1]; // MAX_MSG_SIZE is enough CREDENTIAL credential; } CLIENT_INFO; typedef struct tagMSG { char *params[MAX_MSG_PARAMS]; int count; } MSG; typedef struct tagCLIENT_MSG_PROC { char *msg; bool (*proc)(CLIENT_INFO *, const MSG *); } CLIENT_MSG_PROC; int read_credentials(void); void *client_thread_proc(void *param); ssize_t sock_readline(int sock, char *str, size_t size); void receive_msg(CLIENT_INFO *ci); void send_msg(CLIENT_INFO *ci, const char *msg); int is_empty_line(const char *line); void exit_client_thread(CLIENT_INFO *ci); void parse_msg(char *msg, MSG *msgs); bool login_proc(CLIENT_INFO *ci); bool add_proc(CLIENT_INFO *ci, const MSG *msg); bool sub_proc(CLIENT_INFO *ci, const MSG *msg); bool mul_proc(CLIENT_INFO *ci, const MSG *msg); bool div_proc(CLIENT_INFO *ci, const MSG *msg); bool sqrt_proc(CLIENT_INFO *ci, const MSG *msg); bool pow_proc(CLIENT_INFO *ci, const MSG *msg); bool logout_proc(CLIENT_INFO *ci, const MSG *msg); char *revstr(char *str); void exit_sys(const char *msg); CLIENT_MSG_PROC g_client_msgs[] = { {"ADD", add_proc}, {"SUB", sub_proc}, {"MUL", mul_proc}, {"DIV", div_proc}, {"SQRT", sqrt_proc}, {"POW", pow_proc}, {"LOGOUT", logout_proc}, {NULL, NULL} }; CREDENTIAL g_credentials[MAX_CREDENTIALS]; int g_ncredentials; /* ./server [-p port] */ int main(int argc, char *argv[]) { int server_sock, client_sock; struct sockaddr_in sin_server, sin_client; socklen_t sin_len; int option; int server_port; int p_flag, err_flag; pthread_t tid; CLIENT_INFO *ci; int result; p_flag = err_flag = 0; opterr = 0; while ((option = getopt(argc, argv, "p:")) != -1) { switch (option) { case 'p': p_flag = 1; server_port = atoi(optarg); break; case '?': if (optopt == 'p') fprintf(stderr, "-%c option must have an argument!\n", optopt); else fprintf(stderr, "-%c invalid option!\n", optopt); err_flag = 1; } } if (err_flag) exit(EXIT_FAILURE); if (optind - argc != 0) { fprintf(stderr, "too many arguments!...\n"); exit(EXIT_FAILURE); } if (read_credentials() == -1) { fprintf(stderr, "cannot read credentials...\n"); exit(EXIT_FAILURE); } if (!p_flag) server_port = DEF_SERVER_PORT; if ((server_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); sin_server.sin_family = AF_INET; sin_server.sin_port = htons(server_port); sin_server.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(server_sock, (struct sockaddr *)&sin_server, sizeof(sin_server)) == -1) exit_sys("bind"); if (listen(server_sock, 8) == -1) exit_sys("listen"); printf("listening port %d\n", server_port); for (;;) { sin_len = sizeof(sin_client); if ((client_sock = accept(server_sock, (struct sockaddr *)&sin_client, &sin_len)) == -1) exit_sys("accept"); // printf("connected client ===> %s : %u\n", inet_ntop(AF_INET, &sin_client.sin_addr, ntopbuf, INET_ADDRSTRLEN), (unsigned)ntohs(sin_client.sin_port)); if ((ci = (CLIENT_INFO *)malloc(sizeof(CLIENT_INFO))) == NULL) { fprintf(stderr, "cannot allocate memory!...\n"); exit(EXIT_FAILURE); } ci->sock = client_sock; ci->sin = sin_client; if ((result = pthread_create(&tid, NULL, client_thread_proc, ci)) != 0) { fprintf(stderr, "pthread_create: %s\n", strerror(result)); exit(EXIT_FAILURE); } if ((result = pthread_detach(tid)) != 0) { fprintf(stderr, "pthread_detach: %s\n", strerror(result)); exit(EXIT_FAILURE); } } close(server_sock); return 0; } int read_credentials(void) { char buf[MAX_USER_NAME + MAX_PASSWORD + 32]; FILE *f; char *str; if ((f = fopen(CREDENTIALS_PATH, "r")) == NULL) return -1; g_ncredentials = 0; while (fgets(buf, MAX_USER_NAME + MAX_PASSWORD + 32, f) != NULL) { if (is_empty_line(buf) == 0) continue; if ((str = strtok(buf, ",")) == NULL) return -1; strcpy(g_credentials[g_ncredentials].user_name, str); if ((str = strtok(NULL, "\n")) == NULL) return -1; strcpy(g_credentials[g_ncredentials].password, str); if ((str = strtok(NULL, "\n")) != NULL) return -1; ++g_ncredentials; } fclose(f); return 0; } int is_empty_line(const char *line) { while (*line != '\0') { if (!isspace(*line)) return -1; ++line; } return 0; } void *client_thread_proc(void *param) { char ntopbuf[INET_ADDRSTRLEN]; unsigned port; CLIENT_INFO *ci = (CLIENT_INFO *)param; MSG msg; int i; inet_ntop(AF_INET, &ci->sin.sin_addr, ntopbuf, INET_ADDRSTRLEN); port = (unsigned)ntohs(ci->sin.sin_port); if (!login_proc(ci)) { send_msg(ci, "ERROR incorrect user name or password\r\n"); exit_client_thread(ci); } send_msg(ci, "LOGIN_ACCEPTED\r\n"); printf("client connected with user name \"%s\"\n", ci->credential.user_name); for (;;) { receive_msg(ci); *strchr(ci->buf, '\r') = '\0'; printf("Message from \"%s\": \"%s\"\n", ci->credential.user_name, ci->buf); parse_msg(ci->buf, &msg); if (msg.count == 0) { send_msg(ci, "ERROR empty command\r\n"); continue; } for (i = 0; g_client_msgs[i].msg != NULL; ++i) if (!strcmp(g_client_msgs[i].msg, msg.params[0])) { if (!g_client_msgs[i].proc(ci, &msg)) goto EXIT; break; } if (g_client_msgs[i].msg == NULL) { send_msg(ci, "ERROR invalid command\r\n"); } } printf("client disconnected %s : %u\n", ntopbuf, port); EXIT: shutdown(ci->sock, SHUT_RDWR); close(ci->sock); free(ci); return NULL; } ssize_t sock_readline(int sock, char *str, size_t size) { char *bstr = str; static char *bp; static ssize_t count = 0; static char buf[2048]; if (size <= 2) { errno = EINVAL; return -1; } while (--size > 0) { if (--count <= 0) { if ((count = recv(sock, buf, sizeof(buf), 0)) == -1) return -1; if (count == 0) return 0; bp = buf; } *str++ = *bp++; if (str[-1] == '\n') if (str - bstr > 1 && str[-2] == '\r') { *str = '\0'; break; } } return (ssize_t) (str - bstr); } void receive_msg(CLIENT_INFO *ci) { ssize_t result; if ((result = sock_readline(ci->sock, ci->buf, MAX_MSG_SIZE)) == -1) { fprintf(stderr, "sock_readline: %s\n", strerror(errno)); exit_client_thread(ci); } if (result == 0) { fprintf(stderr, "sock_readline: client unexpectedly down...\n"); exit_client_thread(ci); } } void send_msg(CLIENT_INFO *ci, const char *msg) { if (send(ci->sock, msg, strlen(msg), 0) == -1) exit_client_thread(ci); } void exit_client_thread(CLIENT_INFO *ci) { shutdown(ci->sock, SHUT_RDWR); close(ci->sock); free(ci); pthread_exit(NULL); } void parse_msg(char *buf, MSG *msg) { char *str; msg->count = 0; for (str = strtok(buf, " \r\n\t"); str != NULL; str = strtok(NULL, " \r\n\t")) msg->params[msg->count++] = str; msg->params[msg->count] = NULL; } bool login_proc(CLIENT_INFO *ci) { MSG msg; char *user_name, *password; receive_msg(ci); parse_msg(ci->buf, &msg); user_name = msg.params[1]; password = msg.params[2]; if (msg.count != 3) return false; if (strcmp(msg.params[0], "LOGIN") != 0) return false; for (int i = 0; i < g_ncredentials; ++i) if (strcmp(user_name, g_credentials[i].user_name) == 0 && strcmp(password, g_credentials[i].password) == 0) { ci->credential = g_credentials[i]; return true; } return false; } bool add_proc(CLIENT_INFO *ci, const MSG *msg) { double op1, op2, result; char buf[MAX_MSG_SIZE]; if (msg->count != 3) { send_msg(ci, "ERROR invalid operand in command\r\n"); return true; } op1 = atof(msg->params[1]); op2 = atof(msg->params[2]); result = op1 + op2; sprintf(buf, "RESULT %f\r\n", result); send_msg(ci, buf); return true; } bool sub_proc(CLIENT_INFO *ci, const MSG *msg) { double op1, op2, result; char buf[MAX_MSG_SIZE]; if (msg->count != 3) { send_msg(ci, "ERROR invalid operand in command\r\n"); return true; } op1 = atof(msg->params[1]); op2 = atof(msg->params[2]); result = op1 - op2; sprintf(buf, "RESULT %f\r\n", result); send_msg(ci, buf); return true; } bool mul_proc(CLIENT_INFO *ci, const MSG *msg) { double op1, op2, result; char buf[MAX_MSG_SIZE]; if (msg->count != 3) { send_msg(ci, "ERROR invalid operand in command\r\n"); return true; } op1 = atof(msg->params[1]); op2 = atof(msg->params[2]); result = op1 * op2; sprintf(buf, "RESULT %f\r\n", result); send_msg(ci, buf); return true; } bool div_proc(CLIENT_INFO *ci, const MSG *msg) { double op1, op2, result; char buf[MAX_MSG_SIZE]; if (msg->count != 3) { send_msg(ci, "ERROR invalid operand in command\r\n"); return true; } op1 = atof(msg->params[1]); op2 = atof(msg->params[2]); result = op1 / op2; sprintf(buf, "RESULT %f\r\n", result); send_msg(ci, buf); return true; } bool sqrt_proc(CLIENT_INFO *ci, const MSG *msg) { double op, result; char buf[MAX_MSG_SIZE]; if (msg->count != 2) { send_msg(ci, "ERROR invalid operand in command\r\n"); return true; } op = atof(msg->params[1]); result = sqrt(op); sprintf(buf, "RESULT %f\r\n", result); send_msg(ci, buf); return true; } bool pow_proc(CLIENT_INFO *ci, const MSG *msg) { double op1, op2, result; char buf[MAX_MSG_SIZE]; if (msg->count != 3) { send_msg(ci, "ERROR invalid operand in command\r\n"); return true; } op1 = atof(msg->params[1]); op2 = atof(msg->params[2]); result = pow(op1, op2); sprintf(buf, "RESULT %f\r\n", result); send_msg(ci, buf); return true; } bool logout_proc(CLIENT_INFO *ci, const MSG *msg) { send_msg(ci, "LOGOUT_ACCEPTED\r\n"); printf("%s logging out...\n", ci->credential.user_name); return false; } char *revstr(char *str) { size_t i, k; char temp; for (i = 0; str[i] != '\0'; ++i) ; for (--i, k = 0; k < i; ++k, --i) { temp = str[k]; str[k] = str[i]; str[i] = temp; } return str; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } /* calc-client.c */ #include #include #include #include #include #include #include #include #include #include #include #define DEF_SERVER_NAME "127.0.0.1" #define DEF_SERVER_PORT "55555" #define MAX_MSG_SIZE 4096 #define MAX_MSG_PARAMS 32 typedef struct tagMSG { char *params[MAX_MSG_PARAMS]; int count; } MSG; typedef struct tagSERVER_MSG_PROC { char *msg; bool (*proc)(const MSG *); } SERVER_MSG_PROC; ssize_t sock_readline(int sock, char *str, size_t size); void receive_msg(int sock, char *msg); void send_msg(int sock, const char *msg); void parse_msg(char *buf, MSG *msg); int parse_error(char *buf, MSG *msg); int login_attempt(int sock, const char *user_name, const char *password); bool result_proc(const MSG *msg); bool error_proc(const MSG *msg); bool logout_accepted_proc(const MSG *msg); void exit_sys(const char *msg); SERVER_MSG_PROC g_server_msgs[] = { {"ERROR", error_proc}, {"RESULT", result_proc}, {"LOGOUT_ACCEPTED", logout_accepted_proc}, {NULL, NULL} }; /* ./calc-client [-s server] [-p server_port] [-b client_port] */ int main(int argc, char *argv[]) { int client_sock; struct sockaddr_in sin_client; struct addrinfo hints = {0, AF_INET, SOCK_STREAM}; struct addrinfo *res, *ri; int gai_result; char buf[MAX_MSG_SIZE]; char *str; int option; int s_flag, p_flag, b_flag, err_flag; const char *server_name; int bind_port; const char *server_port; const char *user_name, *password; MSG msg; int i; s_flag = p_flag = b_flag = err_flag = 0; opterr = 0; while ((option = getopt(argc, argv, "s:p:b:")) != -1) { switch (option) { case 's': s_flag = 1; server_name = optarg; break; case 'p': p_flag = 1; server_port = optarg; break; case 'b': b_flag = 1; bind_port = atoi(optarg); break; case '?': if (optopt == 's' || optopt == 'p' || optopt == 'b') fprintf(stderr, "-%c option must have an argument!\n", optopt); else fprintf(stderr, "-%c invalid option!\n", optopt); err_flag = 1; } } if (err_flag) exit(EXIT_FAILURE); if (argc - optind != 2) { fprintf(stderr, "wrong number of arguments!...\n"); exit(EXIT_FAILURE); } user_name = argv[optind + 0]; password = argv[optind + 1]; if (!s_flag) server_name = DEF_SERVER_NAME; if (!p_flag) server_port = DEF_SERVER_PORT; if ((client_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); if (b_flag) { sin_client.sin_family = AF_INET; sin_client.sin_port = htons(bind_port); sin_client.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(client_sock, (struct sockaddr *)&sin_client, sizeof(sin_client)) == -1) exit_sys("bind"); } if ((gai_result = getaddrinfo(server_name, server_port, &hints, &res)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(gai_result)); exit(EXIT_FAILURE); } for (ri = res; ri != NULL; ri = ri->ai_next) if (connect(client_sock, ri->ai_addr, ri->ai_addrlen) != -1) break; if (ri == NULL) exit_sys("connect"); freeaddrinfo(res); if (login_attempt(client_sock, user_name, password) == -1) goto EXIT; printf("connection successful...\n"); for (;;) { printf("csd>"); fflush(stdout); if (fgets(buf, MAX_MSG_SIZE, stdin) == NULL) continue; if ((str = strchr(buf, '\n')) != NULL) *str = '\0'; if (buf[strspn(buf, " \t")] == '\0') /* check if buf contains white spaces */ continue; strcat(str, "\r\n"); send_msg(client_sock, buf); receive_msg(client_sock, buf); parse_msg(buf, &msg); for (i = 0; g_server_msgs[i].msg != NULL; ++i) if (!strcmp(g_server_msgs[i].msg, msg.params[0])) { if (!g_server_msgs[i].proc(&msg)) goto EXIT; break; } } EXIT: shutdown(client_sock, SHUT_RDWR); close(client_sock); return 0; } ssize_t sock_readline(int sock, char *str, size_t size) { char *bstr = str; static char *bp; static ssize_t count = 0; static char buf[2048]; if (size <= 2) { errno = EINVAL; return -1; } while (--size > 0) { if (--count <= 0) { if ((count = recv(sock, buf, sizeof(buf), 0)) == -1) return -1; if (count == 0) return 0; bp = buf; } *str++ = *bp++; if (str[-1] == '\n') if (str - bstr > 1 && str[-2] == '\r') { *str = '\0'; break; } } return (ssize_t) (str - bstr); } void receive_msg(int sock, char *msg) { ssize_t result; if ((result = sock_readline(sock, msg, MAX_MSG_SIZE)) == -1) exit_sys("receive_msg"); if (result == 0) { fprintf(stderr, "receive_msg: unexpectedly down...\n"); exit(EXIT_FAILURE); } } void send_msg(int sock, const char *msg) { if (send(sock, msg, strlen(msg), 0) == -1) exit_sys("send_msg"); } void parse_msg(char *buf, MSG *msg) { char *str; if (parse_error(buf, msg) == 0) return; msg->count = 0; for (str = strtok(buf, " \r\n\t"); str != NULL; str = strtok(NULL, " \r\n\t")) msg->params[msg->count++] = str; msg->params[msg->count] = NULL; } int parse_error(char *buf, MSG *msg) { while (isspace(*buf)) ++buf; if (!strncmp(buf, "ERROR", 5)) { buf += 5; while (isspace(*buf)) ++buf; *strchr(buf, '\r') = '\0'; msg->count = 2; msg->params[0] = "ERROR"; msg->params[1] = buf; return 0; } return -1; } int login_attempt(int sock, const char *user_name, const char *password) { char buf[MAX_MSG_SIZE]; MSG msg; sprintf(buf, "LOGIN %s %s\r\n", user_name, password); send_msg(sock, buf); receive_msg(sock, buf); parse_msg(buf, &msg); if (!strcmp(msg.params[0], "ERROR")) { fprintf(stderr, "login error: %s\n", msg.params[1]); return -1; } if (strcmp(msg.params[0], "LOGIN_ACCEPTED") != 0) { fprintf(stderr, "unexpected server message!...\n"); return -1; } return 0; } bool result_proc(const MSG *msg) { printf("%s\n", msg->params[1]); return true; } bool error_proc(const MSG *msg) { printf("Error: %s\n", msg->params[1]); return true; } bool logout_accepted_proc(const MSG *msg) { return false; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } >>> "chat" programı yazmak istediğimizde "client" programların "server" programa göndereceği komutlar, "LOGIN \r\n" "SEND_MESSAGE \r\n "LOGOUT\r\n" şeklinde olacaktır. Buna karşılık "server" programın da "client" programa göndereceği komutlar, "LOGIN_ACCEPTED\r\n" "ACTIVE_USERS\r\n" "NEW_USER_LOGGEDIN \r\n" "USER_LOGGEDOUT \r\n" "LOGOUT_ACCEPTED\r\n" "DISTRIBUTE_MESSAGE \r\n" "LOGOUT_ACCEPTED\r\n" "ERROR " şeklinde olacaktır. Bütün bunları göz önüne aldığımız zaman öyle bir programın akışı ise şu şekilde olacaktır; -> "client" önce "LOGIN" mesajı ile "server" a mantıksal bakımdan bağlanır. "server" da bağlantıyı kabul ederse "client" a "LOGIN_ACCEPTED" mesajını gönderir. Tabii oturuma yeni kullanıcı katıldığı için aynı zamanda "server" diğer tüm "client" lara "NEW_USER_LOGGEDIN" yollar. Bağlanan "client" a ise oturumdakilerin hepsinin listesini "ACTIVE_USERS" mesajı ile iletmektedir. -> "client" bir mesajın oturumdaki herkes tarafından görülmesini sağlamak amacıyla "server" a "SEND_MESSAGE" mesajını gönderir. "server" da bu mesajı oturumdaki tüm "client" lara "DISTRIBUTE_MESSAGE" mesajıyla iletir. -> Bir kullanıcı "logout" olmak istediğinde "server" a "LOGOUT" mesajını gönderir. "server" da bunu kabul ederse "client" a "LOGOUT_ACCEPTED" mesajını gönderir. Ancak "client" ın "logout" olduğu bilgisinin oturumdaki diğer "client" lara da iletilmesi gerekmektedir. Bunun için "server" tüm "client" lara "USER_LOGGED" mesajını göndermelidir. -> Yine bir hata durumunda server "client" lara "ERROR" mesajı gönderebilir. Aslında "chat" programları için "IP" protokol ailesinde "IRC (Internet Relay Chat)" protokolü bulunmaktadır. "IRC-server" ve "IRC-client" programları Linux sistemlerinde zaten bulunmaktadır. Siz de bu protokolün dokümanlarını inceleyerek bu protokol için "client" ve/veya "server" programları yazabilirsiniz. "IRC" protokolü "RFC 1459" olarak dokümante edilmiştir. Başka kurumların da "chat" protokollerinin bazıları artık dokümante edilmiştir. Microsoft'un "MSN Chat" protokülünün dokümanlarına aşağıdaki adresten erişebilirsiniz: http://www.hypothetic.org/docs/msn/ >>> Komut satırı tabanlı bir "telnet", "ssh" benzeri, "client-server" bir program yazmak isteyelim. Amacımız bir "client" ın server makinede komut satırından işlem yapmasını sağlamak olsun. Yani "client", "server" a bağlanacak ve ona "shell" komutları yollayacak. Komutların çıktısını da görecek. Böyle bir programın "server" tarafının çatısı şöyle oluşturulabilir: -> "client" program bağlandığında server iki boru yaratır ve "fork" işlemi yapar. -> "fork" işleminden sonra, henüz "exec" işlemi yapmadan, alt prosesin "stdin" betimleyicisini borunun birine, "stdout" betimleyicisini de diğerine yönlendirir. Böylece üst proses boruya yazma yaptığında aslında alt proses bunu "stdin" betimleyicisinden okuyacaktır. Benzer biçimde alt proses diğer boruya yazma yaptığında üst proses de bunu diğer borudan okuyabilecektir. -> Bu yönlendirmelerden sonra "server", "exec" yaparak "shell" programını çalıştırır. -> "client", "server" a "shell" komutunu gönderdiğinde "server", komutu "shell" programına işletir ve çıktısını elde eder ve "client" a yollar. Aslında bu işlemi yapan iki standart protokol vardır; "telnet" ve "ssh" protokolleri. "telnet" protokolü güvenlik bakımından zayıf olduğu için günümüzde daha çok "ssh" kullanılmaktadır. Aşağıda konuya ilişkin bir örnek verilmiştir. * Örnek 1, Our Bash Program (version 6) : Aşağıdaki programda üst prosesin "/bin/bash" programını çalıştırıp ona komutlar yollaması ve ondan komutlar alması beklenmektedir. Bunun için üst proses iki boru yaratmıştır. Sonra alt prosesi yaratarak "exec" uygulamıştır. Ancak üst proses henüz "exec" yapmadan alt prosesin "stdin" betimleyicisini ve "stdout" betimleyicisini boruya yönlendirmiştir. Böylece şöyle bir mekanizma oluşturulmuştur: Üst proses borulardan birine yazdığında sanki alt prosesin "stdin" dosyasına yazmış gibi olmaktadır. Yine üst proses diğer borudan okuma yaptığında alt prosesin "stdout" dosyasına yazılanları okumuş gibi olmaktadır. Fakat bu programın da şöyle kusurları vardır; Bu kusurlardan ilki üst proses kabuğa komutu ilettikten sonra onun "stdout" ya da "stdin" dosyasına yazdıklarını okumaya çalışmaktadır. Ancak ne kadar bilginin okunacağı belli değildir. İkinci kusur ise üst proses alt prosesin yazdıklarını okuyabilmek için biraz gecikme uygulamıştır. Ancak alt proses bu gecikmeden uzun süre çalışıyorsa program hatalı çalışır. #include #include #include #include #include #include #include #define BUFFER_SIZE 65536 void exit_sys(const char *msg); int main(void) { pid_t pid; int fdsout[2]; int fdsin[2]; char buf[BUFFER_SIZE + 1]; ssize_t result; int status; if (pipe(fdsin) == -1) exit_sys("pipe"); if (pipe(fdsout) == -1) exit_sys("pipe"); if ((pid = fork()) == -1) exit_sys("fork"); if (pid == 0) { /* child */ close(fdsin[0]); close(fdsout[1]); if (dup2(fdsin[1], 1) == -1) _exit(EXIT_FAILURE); if (dup2(fdsin[1], 2) == -1) _exit(EXIT_FAILURE); if (dup2(fdsout[0], 0) == -1) _exit(EXIT_FAILURE); close(fdsin[1]); close(fdsout[0]); if (execl("/bin/bash", "/bin/bash", (char *)NULL) == -1) _exit(EXIT_FAILURE); /* unreachable code */ } /* parent process */ close(fdsin[1]); close(fdsout[0]); /* parent writes fdsout[1] and read fdsin[0] */ if (fcntl(fdsin[0], F_SETFL, fcntl(fdsin[0], F_GETFL)|O_NONBLOCK) == -1) exit_sys("fcntl"); for (;;) { printf("Command: "); fflush(stdout); if (fgets(buf, BUFFER_SIZE, stdin) == NULL) continue; if (write(fdsout[1], buf, strlen(buf)) == -1) exit_sys("write"); if (!strcmp(buf, "exit\n")) break; usleep(300000); if ((result = read(fdsin[0], buf, BUFFER_SIZE)) == -1) { if (errno == EAGAIN) continue; exit_sys("read"); } buf[result] = '\0'; printf("%s", buf); } if (wait(&status) == -1) exit_sys("wait"); if (WIFEXITED(status)) printf("Shell exits normally with exit code: %d\n", WEXITSTATUS(status)); else printf("shell exits abnormally!..\n"); close(fdsin[0]); close(fdsout[1]); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } >>> Şimdi de elektronik posta alma ve göndermenin nasıl olduğuna değinelim; tipik olarak e-posta gönderme ve alma işlemleri aşağıdaki gibidir: -> Bunun için bir e-posta sunucu programının bulunuyor olması gerekir. Eğer tüm sistemi siz kuruyorsanız bu sunucuyu ("server") da sizin kurmanız gerekmektedir. Zaten Windows sistemlerinde ve UNIX/Linux sistemlerinde bu sunucular hazır biçimde bulunmaktadır. Tabii eğer "domain" hizmetini aldığınız bir kurum varsa, onlar da zaten e-posta hizmeti vermek için hazır e-posta sunucuları bulundurmaktadır. E-posta gönderebilmek için ya da e-posta alabilmek için bizim e-posta sunucusunun adresini biliyor olmamız gerekir. Gönderme işleminde kullanılacak sunucu ile alma işleminde kullanılacak sunucu farklı olabilmektedir. Örneğin CSD'nin e-posta sunucusuna "mail.csystem.org" adresiyle erişilebilmektedir. Bu sunucu hem gönderme hem de alma işlemini yapmaktadır. E-posta gönderebilmek için "client" program ile "server" program, "SMTP (Simple Mail Transfer Protocol)" denilen bir protokolle haberleşmektedir. O halde gönderim için bizim e-posta sunucusuna bağlanarak "SMTP" protokolü ile göndereceğimiz e-postayı ona iletmemiz gerekir. -> Biz göndereceğimiz e-postayı "SMTP" protokolü ile e-posta sunucumuza ilettikten sonra, bu sunucu hedef e-posta sunucusuna, bu e-postayı yine "SMTP" protokolü ile iletmektedir. E-postayı alan sunucu bunu bir posta kutusu "(mail box)" içerisinde saklar. -> Karşı taraftaki "client" program, "POP3" ya da "IMAP" protokolü ile, kendi e-posta sunucuna bağlanarak posta kutusundaki e-postayı yerel makineye indirir. Bir diğer deyişle akış aşağıdaki gibidir: client ---> kendi e-posta sunucumuz ---> hedef e-posta sunucusu --------> client SMTP SMTP POP3/IMAP Görüldüğü gibi "POP3" ve "IMAP" protokolleri e-posta sunucusunun posta kutusundaki zaten gelmiş ve saklanmış olan e-postaları yerel makineye indirmek için kullanılmaktadır. E-posta almak için yaygın kullanılan iki adet protokol vardır. Bunlar, "POP3 (Post Office Protocol Version 3)" ve "IMAP (Internet Message Access Protocol)" isimli protokollerdir. "IMAP" protokolü, "POP3" protokolünden daha güvenli ve ayrıntılı tasarlanmıştır. Bu iki protokol e-posta almak için kullanıldığından, aslında birer "client" protokolleridir. "client" programlar e-posta almak için "POP3" ve "IMAP" isimli "server" programlara bağlanırlar ve buraya gelmiş olanları kendi sistemlerine "get" ederler. Bu protokollerden, >>>> "POP3 (Post Office Protocol Version 3)" : Detayları "RFC 1939" dokümanlarında açıklanmıştır. Fakat kabaca şöyledir: -> "client" program 110 numaralı (ya da 995 numaralı) porttan "server" a "TCP" ile fiziksel olarak bağlanır. -> Protokolde mesajlaşma tamamen "text-based" ve satırsal biçimde yapılmaktadır. Satırlar "CR/LF" karakterleriyle sonlandırılmaktadır. Protokolde "client" ın gönderdiği her komuta karşı "server" bir yanıt göndermektedir. (Fiziksel bağlantı sağlandığında da "server" bir onay mesajı gönderir.) Eğer yanıt olumluysa mesaj "+OK" ile, eğer yanıt olumsuzsa mesaj "-ERR" ile başlatılmaktadır. Yani "server" ın "client" a gönderdiği mesajın genel biçimi şöyledir: "+OK [diğer bilgiler] CR/LF" "-ERR [diğer bilgiler] CR/LF" -> Fiziksel bağlantıdan sonra "client" program mantıksal olarak "server" a "login" olmalıdır. "login" olmak için önce "user name" sonra da "password" gönderilmektedir. "User name" ve "password" gönderme işlemi aşağıdaki iki komutla yapılmaktadır. "USER CR/LF" "PASS CR/LF" Kullanıcı adı e-posta adresiyle aynıdır. Örneğin biz "test@csystem.org" için e-posta sunucusuna bağlanıyorsak buradaki kullanıcı ismi "test@csystem.org" olacaktır. Parola e-postalarınızı okumak için kullandığınız paroladır. Sisteme başarılı bir biçimde "login" olduğumuzu varsayıyoruz. Tipik olarak "server" bize şu mesajı iletecektir: +OK Logged in. Eğer "password" yanlış girilmişse yeniden önce "user name" ve sonra "password" gönderilmelidir. -> Client program "LIST" komutunu göndererek e-posta kutusundaki mesaj bilgilerini elde eder. "LIST" komutuna karşılık "server" önce aşağıdaki gibi bir satır gönderir: +OK 6 messages: Burada "server" e-posta kutusunda kaç e-posta olduğunu belirtmektedir. Sonra her e-postaya bir numara vererek onların "byte" uzunluklarını satır satır iletir. Komut yalnızca "." içeren bir satırla son bulmaktadır. Örneğin: +OK 6 messages: 1 1565 2 5912 3 11890 4 4920 5 9714 6 4932 . -> Belli bir e-posta "RETR" komutuyla elde edilmektedir. Bu komuta elde edilecek e-postanın index numarası girilir. Örneğin: "RETR 2 CR/LF" "RETR" komutuna karşı server önce aşağıdaki gibi bir satır gönderir: +OK 5912 octets Burada programcı bu satırı "parse" ederek burada belirtilen miktarda "byte" kadar soketten okuma yapmalıdır. Anımsanacağı gibi porttan tam olarak "n-byte" okumak "TCP" de tek bir "recv" ile yapılamamaktadır. -> Mesajı silmek için "DELE" komutu kullanılır. Komuta parametre olarak silinecek mesajın indeks numarası girilmektedir. Örneğin: "DELE 3 CR/LF" Bu komut uygulandığında "server" henüz e-postayı posta kutusundan silmez. Yalnızca onu "silinecek" biçiminde işaretler. Silme işlemi "QUIT" komutuyla oturum sonlandırıldığında yapılmaktadır. Eğer "client" silme eyleminden pişmanlık duyarsa "RSET" komutuyla ilk duruma gelir. "RSET" komutu "logout" yapmaz. Yalnızca silinmiş olarak işaretlenenlerin işaretlerini kaldırır. -> "STAT" komutu o anda e-posta kutusundaki e-posta sayısını bize vermektedir. Bu komut gönderildiğinde aşağıdaki gibi bir yanıt alınacaktır: +OK 5 27043 Burada "server" e-posta kutusunda toplam 5 adet e-postanın bulunduğunu ve bunların "byte" uzunluklarının da 27043 olduğunu söylemektedir. -> Protokol, "client" programın "QUIT" komutunu göndermesiyle sonlandırılmaktadır. Örneğin: "QUIT CR/LF" -> "POP3" protokolününde "client" belli bir süre server'a hiç mesaj göndermezse, "server" "client" ın soketini kapatıp bağlantıyı koparmaktadır. Her ne kadar "RFC 1939" da "server" ın en azından 10 dakika beklemesi gerektiği söylenmişse de "server" ların çoğu çok daha az bir süre beklemektedir. Diğer yandan "POP3" protokolünde "client" programın gönderdiği yazısal komutlar için "server" programın gönderdiği yanıtlar "parse" edilerek tam gerektiği kadar okuma yapılabilir. * Örnek 1, Aşağıdaki programda biz, basitlik sağlamak amacıyla, "server" dan gelen mesajları başka bir "thread" ile ele aldık. /* pop3.c */ #include #include #include #include #include #include #include #include #include #include #include #include #define DEF_SERVER_NAME "mail.csystem.org" #define DEF_SERVER_PORT "110" #define BUFFER_SIZE 4096 ssize_t sock_readline(int sock, char *str, size_t size); void *thread_proc(void *param); void send_msg(int sock, const char *msg); void exit_sys_thread(const char *msg, int err); void exit_sys(const char *msg); /* ./pop3 [-s server] [-p server_port] [-b client_port] */ int main(int argc, char *argv[]) { int client_sock; struct sockaddr_in sin_client; struct addrinfo hints = {0, AF_INET, SOCK_STREAM}; struct addrinfo *res, *ri; int gai_result; char *str; int option; int s_flag, p_flag, b_flag, err_flag; const char *server_name; int bind_port; const char *server_port; char buf[BUFFER_SIZE + 1]; pthread_t tid; int result; s_flag = p_flag = b_flag = err_flag = 0; opterr = 0; while ((option = getopt(argc, argv, "s:p:b:")) != -1) { switch (option) { case 's': s_flag = 1; server_name = optarg; break; case 'p': p_flag = 1; server_port = optarg; break; case 'b': b_flag = 1; bind_port = atoi(optarg); break; case '?': if (optopt == 's' || optopt == 'p' || optopt == 'b') fprintf(stderr, "-%c option must have an argument!\n", optopt); else fprintf(stderr, "-%c invalid option!\n", optopt); err_flag = 1; } } if (err_flag) exit(EXIT_FAILURE); if (argc - optind != 0) { fprintf(stderr, "wrong number of arguments!...\n"); exit(EXIT_FAILURE); } if (!s_flag) server_name = DEF_SERVER_NAME; if (!p_flag) server_port = DEF_SERVER_PORT; if ((client_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); if (b_flag) { sin_client.sin_family = AF_INET; sin_client.sin_port = htons(bind_port); sin_client.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(client_sock, (struct sockaddr *)&sin_client, sizeof(sin_client)) == -1) exit_sys("bind"); } if ((gai_result = getaddrinfo(server_name, server_port, &hints, &res)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(gai_result)); exit(EXIT_FAILURE); } for (ri = res; ri != NULL; ri = ri->ai_next) if (connect(client_sock, ri->ai_addr, ri->ai_addrlen) != -1) break; if (ri == NULL) exit_sys("connect"); freeaddrinfo(res); if ((result = pthread_create(&tid, NULL, thread_proc, (void *)client_sock)) != 0) exit_sys_thread("pthread_create", result); usleep(500000); for (;;) { printf("csd>"); fflush(stdout); if (fgets(buf, BUFFER_SIZE, stdin) == NULL) continue; if ((str = strchr(buf, '\n')) != NULL) *str = '\0'; if (buf[strspn(buf, " \t")] == '\0') /* check if buf contains white spaces */ continue; strcat(str, "\r\n"); send_msg(client_sock, buf); sleep(1); if (!strcmp(buf, "QUIT\r\n")) break; } shutdown(client_sock, SHUT_RDWR); close(client_sock); if ((result = pthread_join(tid, NULL)) != 0) exit_sys_thread("pthread_join", result); return 0; } void *thread_proc(void *param) { int sock = (int)param; char buf[BUFFER_SIZE]; int result; for (;;) { if ((result = sock_readline(sock, buf, BUFFER_SIZE)) == -1) exit_sys("receive_msg"); if (result == 0) { printf("closing connection...\n"); break; } printf("%s", buf); } exit(EXIT_SUCCESS); return NULL; } ssize_t sock_readline(int sock, char *str, size_t size) { char *bstr = str; static char *bp; static ssize_t count = 0; static char buf[2048]; if (size <= 2) { errno = EINVAL; return -1; } while (--size > 0) { if (--count <= 0) { if ((count = recv(sock, buf, sizeof(buf), 0)) == -1) return -1; if (count == 0) return 0; bp = buf; } *str++ = *bp++; if (str[-1] == '\n') if (str - bstr > 1 && str[-2] == '\r') { *str = '\0'; break; } } return (ssize_t) (str - bstr); } void send_msg(int sock, const char *msg) { if (send(sock, msg, strlen(msg), 0) == -1) exit_sys("send_msg"); } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } void exit_sys_thread(const char *msg, int err) { fprintf(stderr, "%s: %s\n", msg, strerror(err)); exit(EXIT_FAILURE); } * Örnek 2, Aslında "POP3" protokolünde "client" programın her mesajına karşılık "server" ın nasıl bir yazı gönderdiği bilindiğine göre, buradan hareketle hiç "thread" oluşturmadan gönderilen komut için, yanıt elde edilebilir. Aşağıda bu fikre bir örnek verilmiştir. Örneğimizde "LIST" ve "RETR" komutları özel olarak ele alınmıştır. "LIST" komutunda "server" ın listeyi ilettikten sonra son satırda "." gönderdiğini anımsayınız. Biz de aşağıda programda satırda "." görene kadar okuma yaptık. "RETR" komutunda "+OK" yazısından sonra mesajdaki "byte" sayısının da gönderildiğini anımsayınız. Biz de bundan faydalanarak soketten o kadar "byte" okuduk. /* pop3.c */ #include #include #include #include #include #include #include #include #include #include #include #define DEF_SERVER_NAME "mail.csystem.org" #define DEF_SERVER_PORT "110" #define BUFFER_SIZE 4096 ssize_t sock_readline(int sock, char *str, size_t size); void send_msg(int sock, const char *msg); void receive_msg(int sock, char *msg); void getcmd(const char *buf, char *cmd); void proc_list(int sock); void proc_retr(int sock); void exit_sys(const char *msg); /* ./pop3 [-s server] [-p server_port] [-b client_port] */ int main(int argc, char *argv[]) { int client_sock; struct sockaddr_in sin_client; struct addrinfo hints = {0, AF_INET, SOCK_STREAM}; struct addrinfo *res, *ri; int gai_result; char *str; int option; int s_flag, p_flag, b_flag, err_flag; const char *server_name; int bind_port; const char *server_port; char buf[BUFFER_SIZE + 1]; char cmd[BUFFER_SIZE]; s_flag = p_flag = b_flag = err_flag = 0; opterr = 0; while ((option = getopt(argc, argv, "s:p:b:")) != -1) { switch (option) { case 's': s_flag = 1; server_name = optarg; break; case 'p': p_flag = 1; server_port = optarg; break; case 'b': b_flag = 1; bind_port = atoi(optarg); break; case '?': if (optopt == 's' || optopt == 'p' || optopt == 'b') fprintf(stderr, "-%c option must have an argument!\n", optopt); else fprintf(stderr, "-%c invalid option!\n", optopt); err_flag = 1; } } if (err_flag) exit(EXIT_FAILURE); if (argc - optind != 0) { fprintf(stderr, "wrong number of arguments!...\n"); exit(EXIT_FAILURE); } if (!s_flag) server_name = DEF_SERVER_NAME; if (!p_flag) server_port = DEF_SERVER_PORT; if ((client_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); if (b_flag) { sin_client.sin_family = AF_INET; sin_client.sin_port = htons(bind_port); sin_client.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(client_sock, (struct sockaddr *)&sin_client, sizeof(sin_client)) == -1) exit_sys("bind"); } if ((gai_result = getaddrinfo(server_name, server_port, &hints, &res)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(gai_result)); exit(EXIT_FAILURE); } for (ri = res; ri != NULL; ri = ri->ai_next) if (connect(client_sock, ri->ai_addr, ri->ai_addrlen) != -1) break; if (ri == NULL) exit_sys("connect"); receive_msg(client_sock, buf); printf("%s", buf); freeaddrinfo(res); usleep(500000); for (;;) { printf("csd>"); fflush(stdout); if (fgets(buf, BUFFER_SIZE, stdin) == NULL) continue; if ((str = strchr(buf, '\n')) != NULL) *str = '\0'; if (buf[strspn(buf, " \t")] == '\0') /* check if buf contains white spaces */ continue; strcat(str, "\r\n"); send_msg(client_sock, buf); getcmd(buf, cmd); if (!strcmp(cmd, "LIST")) proc_list(client_sock); else if (!strcmp(cmd, "RETR")) proc_retr(client_sock); else { receive_msg(client_sock, buf); printf("%s", buf); } if (!strcmp(cmd, "QUIT")) break; } shutdown(client_sock, SHUT_RDWR); close(client_sock); return 0; } ssize_t sock_readline(int sock, char *str, size_t size) { char *bstr = str; static char *bp; static ssize_t count = 0; static char buf[2048]; if (size <= 2) { errno = EINVAL; return -1; } while (--size > 0) { if (--count <= 0) { if ((count = recv(sock, buf, sizeof(buf), 0)) == -1) return -1; if (count == 0) return 0; bp = buf; } *str++ = *bp++; if (str[-1] == '\n') if (str - bstr > 1 && str[-2] == '\r') { *str = '\0'; break; } } return (ssize_t) (str - bstr); } void send_msg(int sock, const char *msg) { if (send(sock, msg, strlen(msg), 0) == -1) exit_sys("send_msg"); } void receive_msg(int sock, char *msg) { ssize_t result; if ((result = sock_readline(sock, msg, BUFFER_SIZE)) == -1) exit_sys("receive_msg"); if (result == 0) { fprintf(stderr, "receive_msg: unexpectedly down...\n"); exit(EXIT_FAILURE); } } void getcmd(const char *buf, char *cmd) { int i; for (i = 0; buf[i] != '\0' && !isspace(buf[i]); ++i) cmd[i] = buf[i]; cmd[i] = '\0'; } void proc_retr(int sock) { ssize_t result; char bufrecv[BUFFER_SIZE + 1]; ssize_t n; int i, ch; for (i = 0;; ++i) { if ((result = recv(sock, &ch, 1, 0)) == -1) exit_sys("sock_readline"); if (result == 0) return; if ((bufrecv[i] = ch) == '\n') break; } bufrecv[i] = '\0'; printf("%s\n", bufrecv); n = (ssize_t)strtol(bufrecv + 3, NULL, 10); while (n > 0) { if ((result = recv(sock, bufrecv, BUFFER_SIZE, 0)) == -1) exit_sys("recv"); if (result == 0) break; bufrecv[result] = '\0'; printf("%s", bufrecv); fflush(stdout); n -= result; } } void proc_list(int sock) { ssize_t result; char bufrecv[BUFFER_SIZE]; do { if ((result = sock_readline(sock, bufrecv, BUFFER_SIZE)) == -1) exit_sys("sock_readline"); if (result == 0) break; printf("%s", bufrecv); } while (*bufrecv != '.'); } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } Pekiyi biz bir e-postaya bir resim ya da dosya iliştirirsek ne olacaktır? "POP3" protokolü çok eski bir protokoldür. Internet'in uygulama katmanındaki ilk protokollerden biridir. Bu protokolde her şey yazı gibi gönderilip alınmaktadır. Dolayısıyla bir kullanıcı e-postasına bir resim ya da dosya iliştirdiğinde, onun içeriği yazıya dönüştürülerek sanki bir yazıymış gibi, gönderilmektedir. Pekiyi e-postanın bu gibi farklı içerikleri "client" tarafından nasıl ayrıştırılacaktır? İşte bir yazı içerisinde değişik içerikler "MIME" denilen sistemle başlıklandırılmaktadır. E-postaları içeriklerine ayrıştırabilmek için ilgili içeriklerin nasıl yazıya dönüştürüldüğünü ve nasıl geri dönüşüm yapıldığını bilmeniz gerekmektedir. Bunun için "Base-64" denilen yöntem kullanılmaktadır. Şimdi de oluşturduğumuz soket nesnelerinin özelliklerini nasıl "set" ve "get" edebileceğimize bakalım. Bunun için sırasıyla iki fonksiyon bulundurulmuştur. Bunlar "setsockopt" ve "getsockopt" isimli fonksiyonlardır. Fonksiyonlardan, >>> "setsockopt" : Soket özelliğini "set" etmek için kullanılır. Fonksiyonun prototipi aşağıdaki gibidir. #include int setsockopt(int socket, int level, int option_name, const void *option_value, socklen_t option_len); Fonksiyonun birinci parametresi özelliği değiştirilecek soketi belirtir. İkinci parametresi değişimin hangi düzeyde yapılacağını belirten bir sembolik sabit biçiminde girilir. Soket düzeyi için tipik olarak "SOL_SOCKET" girilmelidir. Üçüncü parametre hangi özelliğin değiştirileceğini belirtmektedir. Dördüncü parametre değiştirilecek özelliğin değerinin bulunduğu nesnenin adresini almaktadır. Son parametre, dördüncü parametredeki nesnenin uzunluğunu belirtmektedir. Fonksiyon başarı durumunda "0", başarısızlık durumunda "-1" değerine geri döner. >>> "getsockopt" : Soket özelliğini "get" etmek için kullanılır. Fonksiyonun prototipi aşağıdaki gibidir. #include int getsockopt(int socket, int level, int option_name, void * option_value, socklen_t * option_len); Parametreler "setsockopt" ta olduğu gibidir. Yalnızca dördüncü parametrenin yönü değişiktir ve beşinci parametre gösterici almaktadır. Fonksiyonların üçüncü parametresine geçtiğimiz sembolik sabitler ise şunlardır; "SO_BROADCAST", "SO_OOBLINE", "SO_SNDBUF", "SO_RECVBUF", "SO_REUSEADDR", ... Bu sabitlerden, -> "SO_REUSEADDR" : Bu seçeneği belli bir "port" için "bind" işlemi yapmış bir "server" ın, "client" bağlantısı sağladıktan sonra, sonlanması sonucunda bu "server" ın yeniden çalıştırılıp aynı "port" u "bind" edebilmesi için kullanılmaktadır. Bir "port" u "bind" eden server, bir "client" ile bağlandıktan sonra çökerse ya da herhangi bir biçimde sonlanırsa işletim sistemleri o "port" un yeniden belli bir süre "bind" edilmesini engellemektedir. Bunun nedeni eski çalışan "server" ile yeni çalışacak olan "server" ın göndereceği ve alacağı paketlerin karışabilme olasılığıdır. Eski bağlantıda yollanmış olan paketlerin ağda maksimum bir geçerlilik süresi vardır ki işletim sistemi de bunun iki katı kadar bir süre (2 dakika civarı; neden iki katı olduğu protokolün aşağı seviyeli çalışması ile ilgilidir) bu portun yeniden bind edilmesini engellemektedir. İşte eğer "SO_REUSEADDR" soket seçeneği kullanılırsa, artık sonlanan ya da çöken bir "server" hemen yeniden çalıştırıldığında, "bind" işlemi sırasında "Address already in use" biçiminde bir hata ile karşılaşılmayacaktır. Bu soket seçeneğini, int sockopt = 1; ... if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &sockopt, sizeof(sockopt)) == -1) exit_sys("setsockopt"); şeklinde "setsockopt" fonksiyonu ile "set" edebiliriz. Buradan da görüleceği üzere bu bayrağı kullanırken "int" bir nesne alıp onun içerisine sıfır dışı bir değer yerleştirip, onun adresini "setsockopt" fonksiyonunun dördüncü parametresine girmek gerekir. Bu nesneye "0" girip fonksiyonu çağırırsak bu özelliği kapatmış oluruz. Diğer yandan "SO_REUSEADDR" bayrağı daha önce bir program tarafından "bind" edilmiş soketin, ikinci kez başka bir program tarafından "bind" edilmesi için kullanılmamaktadır. Eğer böyle bir ihtiyaç varsa (nadiren olabilir) Linux'ta (fakat POSIX'te değil) "SO_REUSEPORT" soket seçeneği kullanılmalıdır. Aşağıda bu bayrağın kullanımına ilişkin bir örnek verilmiştir. * Örnek 1, Aşağıdaki "server" programını "client" ile bağlandıktan sonra "Ctrl+C" ile sonlandırınız. Sonra yeniden çalıştırmaya çalışınız. "SO_REUSEADDR" seçeneği kullanıldığından dolayı bir sorun ile karşılaşılmayacaktır. Daha sonra "server" programdan o kısmı silerek yeniden denemeyi yapınız. Örneğimizdeki "client" program "server" adresini ve "port" numarasını, "server" program ise yalnızca "port" numarasını komut satırı argümanı olarak almaktadır. Programları farklı terminallerden, ./server 55555 ./client localhost 555555 şeklinde çalıştırabilirsiniz. /* server.c */ #include #include #include #include #include #include #include #define BUFFER_SIZE 1024 void exit_sys(const char *msg); int main(int argc, char *argv[]) { int sock, sock_client; struct sockaddr_in sinaddr, sinaddr_client; socklen_t sinaddr_len; char ntopbuf[INET_ADDRSTRLEN]; in_port_t port; ssize_t result; char buf[BUFFER_SIZE + 1]; int sockopt = 1; if (argc != 2) { fprintf(stderr, "wrong number of arguments!..\n"); exit(EXIT_FAILURE); } port = (in_port_t)strtoul(argv[1], NULL, 10); if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &sockopt, sizeof(sockopt)) == -1) exit_sys("setsockopt"); sinaddr.sin_family = AF_INET; sinaddr.sin_port = htons(port); sinaddr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(sock, (struct sockaddr *)&sinaddr, sizeof(sinaddr)) == -1) exit_sys("bind"); if (listen(sock, 8) == -1) exit_sys("listen"); printf("Waiting for connection...\n"); sinaddr_len = sizeof(sinaddr_client); if ((sock_client = accept(sock, (struct sockaddr *)&sinaddr_client, &sinaddr_len)) == -1) exit_sys("accept"); printf("Connected: %s : %u\n", inet_ntop(AF_INET, &sinaddr_client, ntopbuf, INET_ADDRSTRLEN), (unsigned)ntohs(sinaddr_client.sin_port)); for (;;) { if ((result = recv(sock_client, buf, BUFFER_SIZE, 0)) == -1) exit_sys("recv"); if (result == 0) break; buf[result] = '\0'; if (!strcmp(buf, "quit")) break; printf("%ld bytes received from %s (%u): %s\n", (long)result, ntopbuf, (unsigned)ntohs(sinaddr_client.sin_port), buf); } shutdown(sock_client, SHUT_RDWR); close(sock_client); close(sock); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } /* client.c */ #include #include #include #include #include #include #include #include #define BUFFER_SIZE 1024 void exit_sys(const char *msg); int main(int argc, char *argv[]) { int sock; struct addrinfo *ai, *ri; struct addrinfo hints = {0}; char buf[BUFFER_SIZE]; char *str; int result; if (argc != 3) { fprintf(stderr, "wrong number of arguments!..\n"); exit(EXIT_FAILURE); } if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; if ((result = getaddrinfo(argv[1], argv[2], &hints, &ai)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(result)); exit(EXIT_FAILURE); } for (ri = ai; ri != NULL; ri = ri->ai_next) if (connect(sock, ri->ai_addr, ri->ai_addrlen) != -1) break; if (ri == NULL) exit_sys("connect"); freeaddrinfo(ai); printf("Connected...\n"); for (;;) { printf("Yazı giriniz:"); if (fgets(buf, BUFFER_SIZE, stdin) == NULL) continue; if ((str = strchr(buf, '\n')) != NULL) *str = '\0'; if (!strcmp(buf, "quit")) break; if ((send(sock, buf, strlen(buf), 0)) == -1) exit_sys("send"); } shutdown(sock, SHUT_RDWR); close(sock); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } -> "SO_REUSEPORT" : Bu soket seçeneği benzer biçimde Windows sistemlerinde "SO_EXCLUSIVEADDRUSE" biçimindedir. Yani bu soket seçenekleri kullanıldığında aynı "port" birden fazla "server" tarafından "bind" edilip aynı anda kullanılabilir. Bu bayraklarla birden fazla proses aynı "port" u "bind" ettiğinde bir "client" tan bu "port" a "connect" işlemi yapıldığı zaman işletim sistemi belli bir "load balancing" yaparak bağlantının "server" lardan biri tarafından kabul edilmesini sağlayacaktır. Şimdi de "TCP/IP" ve "UDP/IP" protokollerinin alt seviyelerini incelemeye başlayalım. Ancak bu protokollerin aşağı seviyeli çalışma biçimleri biraz karmaşıktır. Biz burada çok derine inmeden bu çalışma biçimini açıklayacağız. >> "TCP/IP" Protokolünün Alt Seviye İşlemleri: "TCP" protokolü 1981 yılında "RFC 793" dokümanı ile tanımlanmıştır (https://tools.ietf.org/html/rfc793). Sonradan protokole bazı revizyonlar ve eklemeler de yapılmıştır. Protokolün son güncel versiyonu 2022'de "RFC 9293" dokümanında tanımlanmıştır. Paket tabanlı protokollerin hepsinde gönderilip alınan veriler paket biçimindedir (yani bir grup bayt biçimindedir). Bu paketlerin "başlık (header)" ve "veri (data)" kısımları vardır. Örneğin "Ethernet paketinin başlık" ve "veri kısmı", "IP paketinin başlık" ve "veri kısmı", "TCP paketinin başlık" ve "veri kısmı" vb. Öte yandan "TCP" protokolü aslında "IP" protokolünün üzerine oturtulmuştur. Yani aslında "TCP" paketleri "IP" paketleri gibi gönderilip alınmaktadır. Nihayet aslında bu paketler bilgisayarımıza "Ethernet" ya da "Wireless" paketi olarak gelmektedir. Paketlerin başlık kısımlarında önemli "meta data" bilgileri bulunmaktadır. O halde, örneğin aslında bizim "network" kartımıza bilgiler, "Ethernet" paketi gibi gelmektedir. Aslında "IP" paketi "Ethernet" paketinin veri kısmında, "TCP" paketi de aslında "IP" paketinin veri kısmında konuşlandırılmaktadır. Yani aslında bize gelen "Ethernet" paketinin veri kısmında "IP" paketi, "IP" paketinin veri kısmında da "TCP" paketi bulunmaktadır. "TCP" de gönderdiğimiz veriler aslında "IP" paketinin veri kısmını oluşturmaktadır. * Örnek 1, bir "host" tan diğerine bir "TCP" paketinin gönderildiğini düşünelim. "TCP" paketi "TCP Header" ve "TCP Data" kısmından oluşmaktadır. Şöyleki: +-------------------------+ | TCP Header | +-------------------------+ | TCP Data | +-------------------------+ Ancak "TCP" paketi aslında "IP" paketi gibi gönderilmektedir. "IP" paketi de "IP Header" ve "IP Data" kısımlarından oluşmaktadır. İşte aslında "TCP" paketi "IP" paketinin "Data" kısmında bulundurulur. Yani yolculuk eden "TCP" paketinin görünümü şöyledir: +-------------------------+ | IP Header | +-------------------------+ <---+ | TCP Header | | +-------------------------+ IP Data | TCP Data | | +-------------------------+ <---+ "TCP" paketi de bilgisayarımızın "Ethernet" kartına sanki "Ethernat" paketi gibi gelmektedir. "Ethernet" paketi de "Ethernet Header" ve "Ethernet Data" kısımların oluşmaktadır. İşte bütün "TCP" paketi aslında "IP" paketi gibi, "IP" paketi de Ethernet paketi gibi, gönderilip alınmaktadır. Şöyleki: +-------------------------+ | Ethernet Header | +-------------------------+ <----------------+ | IP Header | | +-------------------------+ <---+ | | TCP Header | | Ethernet Data +-------------------------+ IP Data | | TCP Data | | | +-------------------------+ <---+------------+ Bu durumu aşağıdaki gibi de gösterebiliriz: +-------------------------+ | Ethernet Header | +----+--------------------+----+ | IP Header | +----+--------------------+----+ | TCP Header | +----+--------------------+----+ | TCP Data | +-------------------------+ * Örnek 2, Biz "TCP" de "send" fonksiyonuyla "ankara" yazısını gönderiyor olalım. Bu "ankara" yazısını oluşturan baytlar aslında "TCP" paketinin veri kısmındadır. Şöyleki: +-------------------------+ | Ethernet Header | +----+--------------------+----+ | IP Header | +----+--------------------+----+ | TCP Header | +----+--------------------+----+ | "ankara" | +-------------------------+ Diğer yandan "Ethernet" protokolu ise, "(IEEE 802.3)", "OSI" katmanına göre fiziksel ve veri bağlantı katmanının işlevlerini yerine getirmektedir. Son olarak "Wireless" haberleşme için kullanılan "Wi-Fi" protokolü ve "(IEEE 802.11)", "Ethernet" protokolünün telsiz (wireless) biçimi gibi düşünülebilir. Tabii "IP" paketleri aslında yalnızca bilgisayarımıza gelirken "Ethernet" paketi ya da "Wi-Fi" paketi olarak gelir. Dışarıda rotalanırken "Ethernet" paketi söz konusu değildir. Diğer yandan "IP" protokolünün "IPv4" ve "IPv6" biçiminde iki versiyonunun olduğunu da anımsayınız. Ancak "TCP" ve "UDP" protokollerinin böyle bir versiyon numarası yoktur. "TCP" paketi "IPv4" paketinin veri kısmında da "IPv6" paketinin veri kısmında da konuşlandırılmış olabilir. Şimdi de "IPv4" ve "IPv6" protokollerinin başlık kısımlarını irdeleyelim. >>> "IPv4" protokolünün başlık kısmı şöyledir: Her satırda 4 bayt bulunmaktadır. Toplam başlık uzunluğu 20 bayttır. <------- Byte 1 -------><------- Byte 2 -------><------- Byte 3 -------><------- Byte 4 -------> +-----------+-----------+----------------------+-----------------------------------------------+ ^ | Version | IHL | Type of Service | Total Length | (4 bytes) | | (4 bits) | (4 bits) | (8 bits) | (16 bits) | | +-----------+-----------+----------------------+-----------+-----------------------------------+ | | Identification | Flags | Fragment Offset | (4 bytes) | | (16 bits) | (3 bits) | (13 bits) | | +-----------------------+----------------------+-----------+-----------------------------------+ | | Time to Live (TTL) | Protocol | Header Checksum | (4 bytes) | 20 bytes | (8 bits) | (8 bits) | (16 bits) | | +-----------------------+----------------------+-----------------------------------------------+ | | Source IP Address (32 bits) | (4 bytes) | +----------------------------------------------------------------------------------------------+ | | Destination IP Address (32 bits) | (4 bytes) | +----------------------------------------------------------------------------------------------+ v | Segment (L4 protocol (TCP/UDP) + Data) | +----------------------------------------------------------------------------------------------+ Bu başlıklardan, -> "Version" : "IP" versiyonunu içerir. "IPv4" ya da "IPv6" değerlerinden birini içermektedir. "IPv4" için "4" değeri kullanılmaktadır. -> "IHL" : "Internet Header Length" bilgisini içermektedir. Genelde 20 bayt değerini içerir. Fakat farklı değerler aldığı durumlar da söz konusu olabilmektedir. -> "Type of Service" : "DS" / "DSCP" / "ECN" alanlarını içermektedir. Paket önceliği konusunda kullanılmaktadır. -> "Total Length" : "Header" ve "data" nın toplam uzunluk bilgisini içermektedir. -> "Identification", "Flags", "Frament Offset" : Paketin ikinci 4 baytlık kısmı "fragmentation" için kullanılmaktadır. -> "Time to Live (TTL)" : Paketin yaşam ömrünün bilgisini içermektedir. Yaşam ömrü her "router" geçildiğinde bir azalmaktadır. Eğer "TTL" değeri "0" olursa paket "router" tarafından çöpe atılmaktadır. "TTL" genel olarak yolunu şaşırmış paketlerin "network" lerde sonsuza kadar dolaşmasını önlemek için kullanılmaktadır. Bazen de paket "router" lar arasında bir "loop (routing loop)" içerisinde takılıp kalmaktadır. "TTL", bu gibi durumları engellemek için kullanılmaktadır. Dünya'da en uzak noktaya bile data gönderirken maksimum 15-20 router geçilmektedir. -> "Protocol" : "L4" protokol bilgisini içermektedir. Örneğin, "TCP" için "6", "UDP" için "17", "ICMP" için "1" değerlerini içermektedir. -> "Header Checksum" : "Router" lar "IP" paket "header" ının yolda bozulup bozulmadığını, bu değeri kontrol ederek sağlayabilmektedir. -> "Source IP Address" : Kaynak "IP" adresinin "unicast IP" adresi olması gerekmektedir. -> "Destination IP Address" : Hedef "IP" adresi "unicast", "broadcast" ve "multicast" "IP" adresi olabilir. Buradan da gördüğünüz gibi "IP" başlığında kaynak ve hedef "IP" adresleri ve "IP" paketinin toplam uzunluğu bulunmaktadır. Port kavramının "IP" protokolünde olmadığını anımsayınız. >>> "IPv6" protokolünün başlık kısmı şöyledir: Toplam başlık uzunluğu 40 byte'a sabitlenmiştir. <------- Byte 1 -------><------- Byte 2 -------><------- Byte 3 -------><------- Byte 4 -------> +-----------+-----------------+----------------------------------------------------------------+ ^ | Version | Traffic Class | Flow Label | (4 bytes) | | (4 bits) | (4 bits) | | | +-----------+-----------------+----------------+-----------------------+-----------------------+ | | Payload Length | Next Header | Hop Limit | (4 bytes) | | (16 bits) | | | | +----------------------------------------------+-----------------------+-----------------------+ | | | | | | | | | | | | | | Source IP Address (128 bits) | (16 bytes) | | | | 40 bytes | | | | | | +----------------------------------------------------------------------------------------------+ | | | | | | | | | | | | | | Destination IP Address (128 bits) | (16 bytes) | | | | | | | | | | +----------------------------------------------------------------------------------------------+ v Bu başlıklardan, -> "Version" : "IP" versiyonunu içerir. "IPv6" için "6" değeri kullanılmaktadır. -> "Traffic Class" : Paket önceliği konusunda kullanılmaktadır. -> "Flow Label" : Bir sunucuyla yapılan haberleşme için bir numara belirlenmektedir ve haberleşme boyunca bütün paketlerde bu numara kullanılarak iletişim sağlanmaktadır. Farklı amaçlar için de kullanıldığı durumlar vardır. -> "Payload Length" : Datanın boyutunu içermektedir. -> "Next Header" : "L4" protokol bilgisini içermektedir. -> "Hop Limit" : "IPv4"'teki "TTL" alanıyla aynıdır. TCP paketi yukarıda da belirttiğimiz gibi "IP" paketinin veri kısmındadır. "TCP" paketi de "TCP Header" ve "TCP Data" kısımlarından oluşmaktadır. "TCP" başlık kısmı şöyledir ki her satırda 4 byte bulunmaktadır: <------- Byte 1 -------><------- Byte 2 -------><------- Byte 3 -------><------- Byte 4 -------> +----------------------------------------------+-----------------------------------------------+ ^ | Source Port | Destination Port | (4 bytes) | | (16 bits) | (16 bits) | | +----------------------------------------------+-----------------------------------------------+ | | Sequence Number | (4 bytes) | | (32 bits) | | +----------------------------------------------------------------------------------------------+ | | Acknowledgement Number | (4 bytes) | | (32 bits) | | 20 bytes +-----------+----------------+-----------------+-----------------------------------------------+ | |Header Len.| Reserved | Control Bits | Window Size | (4 bytes) | | (4 bits) | (6 bits) | (6 bits) | (16 bits) | | +-----------+----------------+-----------------+-----------------------------------------------+ | | Checksum | Urgent | (4 bytes) | | (16 bits) | (16 bits) | | +----------------------------------------------+-----------------------------------------------+ v | Options | | (0 or 32 bits) | +----------------------------------------------------------------------------------------------+ | Application Layer Data | | (Size Varies) | +----------------------------------------------------------------------------------------------+ Bu başlıklardan, -> Burada her satır "32-bit" yani "4" bayt yer kaplamaktadır. -> "TCP" başlığı "20" bayttan "60" bayta kadar değişen uzunlukta olabilir. -> "Header Length", "TCP" verisinin hangi "offset" ten başladığını dolayısıyla "TCP" başlığının "DWORD" (4 bayt olarak) uzunluğunu belirtir. Yani başlığın bayt uzunluğu için buradaki değer "4" ile çarpılmalıdır. Böylece "Header Length" kısmında en az "5" (toplam "20" bayt), en fazla "15" (toplamda "60" bayt) değeri bulunabilir. -> Bu başlıkta kaynak ve hedef "IP" adreslerinin ve "TCP" nin veri kısmının uzunluğunun bulunmadığına dikkat ediniz. Çünkü bu bilgiler zaten "IP" başlığında doğrudan ya da dolaylı biçimde bulunmaktadır. "TCP" paketi her zaman "IP" paketinin veri kısmında konuşlandırılmaktadır. -> Başlıktaki "Control Bits" alanı "6" bitten oluşmaktadır. Her bit, bir özelliği temsil eder. Buradaki belli bitler "set" edildiğinde başlıktaki belli alanlar da anlamlı hale gelebilmektedir. Buradaki bitler, yani bayraklar şunlardır: "URG", "ACK", "PSH", "RST", "SYN" ve "FIN". -> Flags alanındaki birden fazla bit 1 olabilir. Yani birden fazla flag set edilmiş olabilir. Pekala Bir "TCP" paketi "(TCP segment)" yalnızca başlık içerebilir. Yani hiç veri içermeyebilir. Başka bir deyişle, "TCP" paketinin veri kısmı "0" bayt uzunluğunda olabilir. Yine "TCP" protokolünde o anda iki tarafın da bulunduğu bir "durum (state)" vardır. Taraflar belli eylemler sonucunda durumdan duruma geçiş yaparlar. Bu nedenle "TCP" nin çalışması bir "sonlu durum makinesi (finite state machine)" biçiminde ele alınıp açıklanabilir. Henüz bağlantı yoksa iki taraf da "CLOSED" denilen durumdadır. "TCP" de tarafların hangi olaylar sonucunda hangi durumda olduklarına ilişkin diyagrama "durum diyagramı (state diagram)" denilmektedir. ("TCP" durum diyagramı için Google'da "TCP state diagram" araması ile görsellerden çizilmiş diyagramları görebilirsiniz.). "TCP" bağlantısının kurulması için "client" ile "server", veri kısmı boş olan (yani yalnızca başlık kısmı bulunan) paketleri gönderip almaktadır. Buna "el sıkışma (hand shaking)" denilmektedir. "TCP" de bağlantı kurulması için yapılan el sıkışma dörtlü "(four-way)" ya da üçlü "(three-way)" olabilir. Burada üçlü demekle bağlantı için toplam "3" paketin yolculuk etmesi, dörtlü demekle toplam "4" paketin yolculuk etmesi kastedilmektedir. Uygulamada daha çok üçlü el sıkışma kullanılmaktadır. "TCP" de bağlantının kurulabilmesi için iki tarafın da birbirlerine; -> "SYN" biti "set" edilmiş ve veri kısmı olmayan "TCP" paketi (20 bayt) göndermesi -> Karşı taraftan "ACK" biti "set" edilmiş ve verisi olmayan "TCP" paketi alması gerekir. Yukarıda da belirttiğimiz gibi bunun iki yolu olabilir; Dörtlü ve Üçlü El Sıkışması >>> Dörtlü El Sıkışması: Temsili gösterimi aşağıdaki gibidir. Burada "4" adet paket kullanıldığı için buna dörtlü el sıkışma denilmektedir. Client Server +-----------------+ +-----------------+ | CLOSED | | LISTEN | +-----------------+ +-----------------+ ------- SYN -----> +-----------------+ | SYN-SENT | +-----------------+ <------ ACK ------ <------ SYN ------ +-----------------+ +-----------------+ | ESTABLISHED | | SYN-RECEIVED | +-----------------+ +-----------------+ ------- ACK -----> +-----------------+ | ESTABLISHED | +-----------------+ >>> Üçlü El Sıkışması: Temsili gösterimi aşağıdaki gibidir. "Server" bağlantı sırasında "ACK" ile "SYN" bitini tek bir paket olarak da gönderilebilir. (Yani paketin "Flags" kısmında hem "SYN" hem de "ACK" biti "set" edilmiş olabilir.) Buna da üçlü el sıkışma denilmektedir. Client Server +-----------------+ +-----------------+ | CLOSED | | LISTEN | +-----------------+ +-----------------+ ------- SYN -----> +-----------------+ | SYN-SENT | +-----------------+ <--- SYN + ACK --- +-----------------+ | SYN-RECEIVED | +-----------------+ ------- ACK -----> +-----------------+ +-----------------+ | ESTABLISHED | | ESTABLISHED | +-----------------+ +-----------------+ Bağlantının kopartılması için iki tarafın da birbirlerine "FIN" biti "set" edilmiş paketler gönderip "ACK" biti "set" edilmiş paketleri alması gerekir. Bağlantının kopartılması da tipik olarak üçlü ya da dörtlü el sıkışma yoluyla yapılmaktadır. Bağlantının kopartılması talebini herhangi bir taraf başlatabilir. >>> Dörtlü El Sıkışması: "FIN" ve "ACK" paketleri ayrı ayrı gönderilirse dörtlü el sıkışma gerçekleşmiş olur. Dörtlü el sıkışma ile bağlantının kopartılması şöyle yapılmaktadır: Peer - 1 Peer - 2 +-----------------+ +-----------------+ | ESTABLISHED | | ESTABLISHED | +-----------------+ +-----------------+ ------- FIN -----> +-----------------+ +-----------------+ | FIN-WAIT-1 | | CLOSE_WAIT | +-----------------+ +-----------------+ <------ ACK ------ +-----------------+ | FIN-WAIT-2 | +-----------------+ <------ FIN ------ +-----------------+ | LAST-ACK | +-----------------+ ------- ACK -----> +-----------------+ +-----------------+ | TIME-WAIT | | CLOSED | +-----------------+ +-----------------+ +-----------------+ | CLOSED | +-----------------+ Burada iki taraf da birbirlerine "FIN" biti "set" edilmiş ve veri kısmı olmayan "TCP" paketleri gönderip, "ACK" biti "set" edilmiş ve veri kısmı olmayan "TCP" paketleri almıştır. >>> Üçlü El Sıkışması: "FIN" ve "ACK" paketleri tek bir paket olarak gönderilirse üçlü el sıkışma gerçekleşmiş olur. Üçlü el sıkışma ile bağlantının kopartılmasında bir taraf tek bir pakette hem "FIN" biti "set" edilmiş hem de "ACK" biti "set" edilmiş paket göndermektedir. Şöyleki: Peer - 1 Peer - 2 +-----------------+ +-----------------+ | ESTABLISHED | | ESTABLISHED | +-----------------+ +-----------------+ ------- FIN -----> +-----------------+ +-----------------+ | FIN-WAIT-1 | | CLOSE_WAIT | +-----------------+ +-----------------+ <--- FIN + ACK --- +-----------------+ +-----------------+ | FIN-WAIT-2 | | LAST-ACK | +-----------------+ +-----------------+ ------- ACK -----> +-----------------+ +-----------------+ | TIME-WAIT | | CLOSED | +-----------------+ +-----------------+ +-----------------+ | CLOSED | +-----------------+ Burada özetle bir taraf önce karşı tarafa "FIN" paketi yollamıştır. Karşı taraf buna "ACK+FIN" ile karşılık vermiştir. Diğer taraf da son olarak karşı tarafa "ACK" yollamıştır. Ancak bağlantıyı kopartmak isteyen taraf bu "ACK" yollama işinden sonra "MSL (Maximum Segment Life)" denilen bir zaman aralığının iki katı kadar beklemektedir (Tipik olarak 2 dakika). >>>> "MSL" : Bir paketin kaybolduğuna karar verilmesi için gereken zamanı belirtmektedir. Eğer alıcı taraf beklemeden hemen "CLOSED" duruma geçseydi bu durumda gönderici taraf yeniden bağlantı kurduğunda henüz alıcı taraf paketi almamışsa sanki eski bağlantı devam ettiriliyormuş gibi bir durum olabilirdi. Öte taraftan soket programlamada bağlantı "shutdown" ile "SHUT_RD" kullanılarak kopartıldığında yine yukarıdaki 3'lü el sıkışma gerçekleşmektedir. Bağlantının koparılması "yarım biçimde de (half close") yapılabilir. Bu durumda bir taraf diğer tarafa "FIN" paketi gönderir. Karşı taraf da buna "ACK" paketi ile karşılık verir. Bundan sonra artık "FIN" gönderen taraf veri gönderemez ama alabilir, "ACK" gönderen taraf ise veri alamaz ama gönderebilir. Şöyleki: Peer - 1 Peer - 2 +-----------------+ +-----------------+ | ESTABLISHED | | ESTABLISHED | +-----------------+ +-----------------+ ------- FIN -----> +-----------------+ +-----------------+ | FIN-WAIT-1 | | CLOSE_WAIT | +-----------------+ +-----------------+ <------ ACK ------ +-----------------+ | CLOSING | +-----------------+ +-----------------+ | TIME_WAIT | +-----------------+ Bunun tersi de şöyle söz konusu olabilir: Peer - 1 Peer - 2 <------ FIN ------ +-----------------+ +-----------------+ | CLOSE_WAIT | | FIN-WAIT-1 | +-----------------+ +-----------------+ ------- ACK -----> +-----------------+ | CLOSING | +-----------------+ +-----------------+ | TIME_WAIT | +-----------------+ Burada da artık Peer-2 veri gönderemez ama alabilir, Peer-1 ise veri veri alamaz fakat gönderebilir. Pekiyi shutdown fonksiyonunun "half close" işlemindeki etkisi nasıldır? Aslında "shutdown" fonksiyonunun ikinci parametresinde belirtilen "SHUT_WR", "SHUT_RD" ve "SHUT_RDWR" değerlerinin protokoldeki bağlantının kopartılması süreciyle bir ilgisi yoktur. "shutdown" fonksiyonu her durumda "half close" uygulamaktadır. Yani "shutdown" fonksiyonunun ikinci parametresi ne olursa olsun, bu fonksiyonu çağıran taraf karşı tarafa "FIN" paketi yollar, karşı taraf da bu tarafa "ACK" paketi yollar. Zaten protokolün kendisinde "half close" işlemi "SHUT_WR", "SHUT_RD" ya da "SHUT_RDWR" biçiminde bir bilgi taşımamaktadır. "TCP" protokolü tasarlandığında "half close" işleminin "bir tarafı göndermeye kapatıp diğer tarafı almaya kapatmak" gibi bir işlev göreceği düşünülmüştür. Ancak bu "half close" işleminin işletim sistemleri tarafından tam olarak nasıl ele alınacağı "TCP/IP" soket gerçekleştirimini yapanlar tarafından belirlenmektedir. Şimdi Linux sistemlerinde "shutdown" fonksiyonunun muhtemel gerçekleştirimi ve arka planda gerçekleşen muhtemel işlemler konusunda bilgi verelim. * Örnek 1, Bir taraf "shutdown" fonksiyonunu "SHUT_WR" parametresiyle aşağıdaki gibi çağırmış olsun. Ancak karşı taraf "shutdown" fonksiyonunu çağırmamış olsun. Şöyleki: shutdown(sock, SHUT_WR); Burada "SHUT_WR" uygulayan taraf diğer tarafa "FIN" paketi gönderir, diğer taraf da buna "ACK" ile yanıt verir ve "half close" işlemi gerçekleşir. Artık "SHUT_WR" uygulayan taraf bundan sonra diğer tarafa veri göndermemeli diğer taraf da karşı taraftan veri almamalıdır. Bir taraf "SHUT_WR" ile "half close" uyguladığında karşı taraf "recv" işlemi yaparsa, sanki soket kapatılmış gibi, recv fonksiyonu "0" ile geri dönecektir. "SHUT_WR" yapan taraf "send" fonksiyonunu kullandığında ise "SIGPIPE" sinyali oluşacaktır. * Örnek 2, Şimdi de bir taraf "shutdown" fonksiyonunu "SHUT_RD" ile çağırmış olsun. Şöyleki: shutdown(sock, SHUT_RD); Bu durumda yine "SHUT_RD" uygulayan taraf karşı tarafa "FIN" paketi gönderir ve karşı taraftan "ACK" paketi alır. Böylece "half close" işlemi gerçekleşir. Artık "SHUT_RD" uygulayan taraf veri almayacak fakat veri gönderebilecektir. Karşı taraf ise veri alabilecek ancak veri gönderemeyecektir. Tabii aslında karşı taraf "shutdown" fonksiyonunun hangi parametreyle çağrıldığını bilmemektedir. Dolayısıyla aslında soket fonksiyonlarıyla veri göndermeye devam edebilecektir. Karşı taraf eğer "send" işlemi yaparsa burada işletim sistemi değişik davranışlar gösterebilmektedir. Karşı tarafın gönderdiği paketler karşı tarafa ulaştığında, "SHUT_RD" yapan taraftaki işletim sistemi bu paketleri hiç dikkate almayabilir. Böylece "SHUT_RD" yapan taraf "recv" fonksiyonunu çağırsa bile fonksiyon "0" ile geri döner. Ya da işletim sistemi böylesi bir durumda karşı taraf veri gönderdiğinde ona "RST" bayrağı "set" edilmiş paket gönderip ki buna "connection reset" denilmektedir, karşı tarafın artık "send" işlemlerinde "SIGPIPE" sinyali üretmesini sağlayabilir. * Örnek 3, Şimdi de "shutdown" fonksiyonunun "SHUT_RDWR" parametresi ile çağrıldığını düşünelim. Bu en çok kullanılan parametredir. Bu durumda yine fonksiyonu çağıran taraf karşı tarafa "FIN" paketi gönderir, karşı taraftan "ACK" paketi alır. Yine "half close" işlemi gerçekleşir. Ancak artık "SHUT_RDWR" uygulayan taraf "recv" ve "send" işlemlerini yapamayacaktır. "SHUT_RDWR" uygulayan taraf "recv" fonksiyonunu çağırırsa fonksiyon "0" ile geri dönecek, "send" fonksiyonunu çağırırsa doğrudan "SIGPIPE" sinyali oluşacaktır. Bu durumda "SHUT_RDWR" uygulayan tarafın karşı tarafı, artık "send" işlemi yaparsa yine davranış yukarıda "SHUT_RD" fonksiyonunda belirtildiği gibi gerçekleşecektir. Tabii normal olarak iki tarafın da aslında ayrı ayrı "shutdown" fonksiyonunu çağırması gerekir. Bu durumda dörtlü el sıkışma gerçekleşecektir. "TCP/IP" soket programlamada önce bir taraf "shutdown" uygulayıp "half close" oluşturabilir. Diğer taraf da bunu anlayıp o da "shutdown" uygulayarak dörtlü el sıkışma oluşturabilir. Diğer yandan TCP'de akış kontrolü için "acknowledgement" yani "alındı" bildirimi kullanılmaktadır. Bir taraf bir tarafa bir paket veri gönderirken verinin yanı sıra aynı zamanda paketin "Flags" kısmındaki "PSH" bitini "1" yapar. Karşı taraf da paketi aldığını diğer tarafa haber vermek için diğer tarafa "ACK" biti "set" edilmiş bir paket gönderir. "TCP" de her gönderilen paket için bir "alındı" bilgisinin alınması gerekir. Eğer paketi gönderen taraf bu paketi içeren bir "ACK" paketi alamazsa bu durumda "paketin karşı tarafa ulaşmadığından" şüphelenmektedir. Bu durumda paketi gönderen taraf, belli bir algoritma ile belirli zaman aralıklarıyla, aynı paketi yeniden göndermektedir. Böylesi bir durumda paketi alan taraf aynı paketi birden fazla kez de alabilir. Bu durumda bu paketlerden yalnızca tek bir kopyasının işleme sokulması, alan tarafın sorumluluğundadır. Tabii gönderen tarafın paketi yolda kaybolabileceği gibi alan tarafın "ACK" paketi de yolda kaybolabilir. Bu durumda yine gönderen taraf "ACK" alamadığına göre göndermeye devam edecektir. Bu durumda alıcı taraf bunun için yine "ACK" gönderecektir. Öte yandan, yukarıda da belirttiğimiz gibi, paketin "data" kısmı dolu olmak zorunda değildir. Bağlantı sağlanırken ve sonlandırırken gönderilen "SYN" ve "FIN" paketleri "data" içermemektedir. "ACK" paketi ayrı bir paket olarak gönderilmek zorunda da değildir. Bilgiyi alan taraf hem bilgi gönderirken hem de "ACK" işlemi yapabilir. Şöyleki: +---------+ | PSH | +---------+ --- data ---> +---------------+ | PSH + ACK | +---------------+ <--- data --- +---------+ | ACK | +---------+ ------------> Aslında izleyen paragraflarda da ele alınacağı gibi "ACK" biti yalnızca "alındığını bildirme için değil", pencere genişliklerinin ayarlanması için de kullanılmaktadır. Yani bir taraf karşı taraftan bilgi almadığı halde yine "ACK" gönderebilir. Diğer yandan "TCP" de kümülatif bir "acknowledgement" sistemi kullanılmaktadır. Yani paketi gönderen taraf bu paket için "ACK" almadan başka paketleri gönderebilir. Paketleri alan taraf, birden fazla paket için, tek bir "ACK" yollayabilir. Kümülatif "ACK" işlemi için "sıra numarası (sequence number)" denilen bir değerden faydalanılmaktadır. "Sıra numarası (sequence number)", gönderilen paketin içerisindeki kaçıncı bayttan başladığını belirten bir değerdir. Bunu dosyalardaki dosya göstericisine benzetebiliriz. Sıra numarası, "TCP" başlığında "32-bit" lik bir alanda tutulmaktadır. Sıra numarası bu alanın sonuna geldiğinde yeniden başa dönmektedir ki buna "wrapping" denir. Yine sıra numarası bağlantı kurulduğunda sıfırdan başlatılmaz, rastgele bir değerden başlatılmaktadır. * Örnek 1, Belli bir anda bir tarafın sıra numarası "1552" olsun. Şimdi bu taraf karşı tarafa "300" bayt göndersin. Artık bu gönderimden sonra sıra numarası "1852" olacaktır. Yani bir sonraki gönderimde bu taraf, sıra numarası olarak, "1852" yi kullanacaktır. Sıra numarası her bilgi gönderiminde bulundurulmak zorundadır. Bilgiyi alan taraf "ACK" paketini gönderirken paketteki sıra numarasını "talep ettiği sonraki sıra numarası" olarak paketin sıra numarasını belirten kısmına yerleştirir. Şöyleki: Peer-1 Peer-2 300 byte (sequence Number: 3560) -----> 100 byte (sequence Number: 3860) -----> <---- ACK (Acknowledgement Number: 3960) 50 byte (sequence Number: 3960) ------> <---- ACK (Acknowledgement Number: 4010) 10 byte (sequence Number: 4010) ------> Buradaki gönderimde gönderen taraf önce "300" baytlık bir paketi, sonra "100" baytlık bir paketi karşı tarafa göndermiştir. Karşı taraf ise bu iki paket için tek bir "ACK" göndermiştir. Karşı tarafın gönderdiği "ACK" aslında diğer taraftan yeni talep edeceği sıra numarasındaki bilgiyi belirtmektedir. İki paketi gönderen taraf karşı taraftan gelen "ACK" içerisindeki bu sıra numarasına baktığında bu iki paketinde alındığını anlamaktadır. Görüldüğü gibi her paket için ayrı bir "ACK" yollanmak zorunda değildir. Buna "kümülatif alındı (cumulative acknowledgment)" bildirimi denilmektedir. * Örnek 2, Bir tarafın karşı tarafa peş peşe beş paket gönderdiğini düşünelim. Karşı taraftan bir adet "ACK" gelmiş olsun. Gönderen taraf bu "ACK" paketine bakarak gönderdiği bilginin ne kadarının karşı taraf tarafından alındığını anlayabilmektedir. Pekiyi bir "TCP" paketi, yani ("TCP segment"), gönderici ("sender") tarafından gönderildikten sonra alıcı ("receiver") bunu alamamışsa ne olacaktır? Çünkü "TCP" nin güvenli bir protokol olması demek, bir biçimde böyle bir durumda bir telafinin yapılması demektir. İşte yukarıda da belirttiğimiz gibi "TCP" protokolü şöyle yöntem izlemektedir: -> Gönderen taraf her gönderdiği paket ("TCP segment") için bir zamanlayıcı kurar ki zamanlayıcıya "retransmission timer" denilmektedir. -> Eğer belli süre içerisinde gönderilen "TCP" paketini kapsayan bir "ACK" gelmediyse, gönderici taraf aynı paketi yeniden göndermektedir. Böylece aslında gönderilen paket henüz onun için "ACK" gelmedikçe gönderme tamponundan atılmaz. "Retransmission timer" bazı değerlere göre dinamik bir biçimde oluşturulmaktadır. Bunun detayları için önerilen kaynaklara bakılabilir. Tabii böyle bir sistemde alıcı taraf aynı paketi birden fazla kez alabilmektedir. Yukarıda da belirttiğimiz gibi bu durumda bu paketlerin yalnızca tek bir kopyasını alıp diğerlerini atmak alıcı tarafın sorumluluğundadır. Diğer taraftan "TCP" protokolünün bir "akış kontrolü (flow control)" oluşturduğunu belirtmiştik. Akış kontrolünün amacı tampon taşmasının engellenmesidir. Bağlantı sağlandıktan sonra bir tarafın diğer tarafa sürekli bilgi gönderdiğini düşünelim. Bu bilgileri işletim sistemi alacak ve bekletecektir. Pekiyi ya ilgili proses soketten okuma yapmazsa? Bu durumda hala karşı taraf bilgi gönderirse işletim sisteminin ayırdığı tampon taşabilir. Tipik olarak işletim sistemleri bağlantı yapılmış her soket için iki tampon bulundurmaktadır: "Gönderme tamponu (send buffer)" ve "alma tamponu (receive buffer)". Biz "send" fonksiyonunu kullandığımızda, göndermek istediğimiz bilgiler gönderme tamponuna yazılır ve hemen "send" fonksiyonu geri döner. Gönderme tamponundaki bilgilerin paketlenerek gönderilmesi belli bir zaman sonra işletim sistemi tarafından yapılmaktadır. Eğer "send" işlemi sırasında zaten gönderme tamponu doluysa "send" fonksiyonu gönderilecek olanları tamamen tampona yazana kadar blokeye yol açmaktadır. Alma tamponu "(receive buffer)" karşı tarafın gönderdiği bilgilerin alınması için kullanılan tampondur. Karşı tarafın gönderdiği bilgiler alındığında işletim sistemi bu bilgileri alma tamponuna yerleştirir. Aslında "recv" fonksiyonu bu tampondan bilgileri almaktadır. send ---> [gönderme tamponu] ---> işletim sistemi gönderiyor ---> ||||| <--- işletim sistemi alıyor ---> [alma tamponu] <--- recv Akış kontrolünün en önemli unsurlarından biri, alma tamponunun taşmasını engellemektir. * Örnek 1, Gönderici taraf sürekli bilgi gönderirse fakat alıcı taraftaki proses "recv" işlemiyle hiç okuma yapmazsa, alıcı taraftaki işletim sisteminin alıcı tamponu dolabilir ve sistem çökebilir. İşte akış kontrolü sayesinde alıcı taraf gönderici tarafa "artık gönderme, benim tamponum doldu" diyebilmektedir. * Örnek 2, Şimdi bir taraftaki prosesin diğer tarafa bir döngü içerisinde "send" fonksiyonuyla bilgi gönderdiğini ancak diğer taraftaki prosesin bu bilgiyi almadığını varsayalım. Akış kontrolünün uygulandığı durumda ne olacaktır? İşte önce "send" ile gönderilenler karşı tarafa iletilecektir. Karşı tamponu dolduğunda karşı taraf, gönderen tarafa "artık gönderme" diyecektir. Bu durumda göndermeyi kesen taraftaki proses hala send işlemi yapacağına göre o tarafın da gönderme tamponu dolacak ve "send" fonksiyonu blokeye yol açacaktır (Linux sistemlerinde tek bir "send" ile gönderme tamponundan daha büyük bir bilgiyi göndermek istediğimizde tüm bilgi yine tampona yerleştirilene kadar bloke oluşmaktadır.) * Örnek 3, Aşağıdaki "client" program bağlantı kurduktan sonra bir döngü içerisinde "server" programa "send" fonksiyonu ile bilgi göndermektedir. Ancak "server" program bu bilgiyi "recv" ile okumamaktadır. Yukarıda da belirttiğimiz gibi bu durumda "server" programın tamponu dolacak ve "server" program karşı tarafa "artık gönderme" diyecek. Bu kez de karşı tarafın gönderme tamponu dolacak dolayısıyla "send" fonksiyonu da bir süre sonra blokede bekleyecektir. Bu programları farklı terminallerden, "./server 55555" "./client localhost 55555" gibi çalıştırabilirsiniz: /* server.c */ #include #include #include #include #include #include #include #include #define BUFFER_SIZE 1024 void exit_sys(const char *msg); int main(int argc, char *argv[]) { int sock_server, sock_client; struct sockaddr_in sinaddr, sinaddr_client; socklen_t sinaddr_len; char ntopbuf[INET_ADDRSTRLEN]; in_port_t port; if (argc != 2) { fprintf(stderr, "wrong number of arguments!..\n"); exit(EXIT_FAILURE); } port = (in_port_t)strtoul(argv[1], NULL, 10); if ((sock_server = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); sinaddr.sin_family = AF_INET; sinaddr.sin_port = htons(port); sinaddr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(sock_server, (struct sockaddr *)&sinaddr, sizeof(sinaddr)) == -1) exit_sys("bind"); if (listen(sock_server, 8) == -1) exit_sys("listen"); printf("Waiting for connection...\n"); sinaddr_len = sizeof(sinaddr_client); if ((sock_client = accept(sock_server, (struct sockaddr *)&sinaddr_client, &sinaddr_len)) == -1) exit_sys("accept"); printf("Connected: %s : %u\n", inet_ntop(AF_INET, &sinaddr_client, ntopbuf, INET_ADDRSTRLEN), (unsigned)ntohs(sinaddr_client.sin_port)); printf("Press any key to EXIT...\n"); getchar(); shutdown(sock_client, SHUT_WR); close(sock_client); close(sock_server); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } /* client.c */ #include #include #include #include #include #include #include #include #include #define BUFFER_SIZE 1024 void exit_sys(const char *msg); int main(int argc, char *argv[]) { int sock; struct addrinfo *ai, *ri; struct addrinfo hints = {0}; char buf[BUFFER_SIZE] = {0}; int result; ssize_t sresult; ssize_t stotal; if (argc != 3) { fprintf(stderr, "wrong number of arguments!..\n"); exit(EXIT_FAILURE); } if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) exit_sys("socket"); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; if ((result = getaddrinfo(argv[1], argv[2], &hints, &ai)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(result)); exit(EXIT_FAILURE); } for (ri = ai; ri != NULL; ri = ri->ai_next) if (connect(sock, ri->ai_addr, ri->ai_addrlen) != -1) break; if (ri == NULL) exit_sys("connect"); freeaddrinfo(ai); printf("Connected...\n"); stotal = 0; for (;;) { printf("send calls...\n"); if ((sresult = send(sock, buf, BUFFER_SIZE, 0)) == -1) exit_sys("send"); stotal += sresult; printf("bytes sent: %jd, total bytes sent: %jd\n", sresult, stotal); } close(sock); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } Şimdi de bu akış kontrolünün detaylarını inceleyelim. "TCP" de bunun için "pencere (window)" kavramı kullanılmaktadır. Pencerenin bir büyüklüğü ("window size") vardır. Pencere büyüklüğü "TCP" başlığında belirtilmektedir. Pencere büyüklüğü demek, "hiç 'ACK' gelmediği durumda göndericinin en fazla gönderebileceği byte sayısı" demektir. * Örnek 1, pencere genişliğinin "8K" olması demek, "alıcı 'ACK' göndermedikten sonra göndericinin en fazla 8K gönderebilmesi" demektir. Pencere genişliği, alıcı taraf tarafından gönderici tarafa bildirilir. * Örnek 1, pencere genişliği alıcı taraf için "8K" olsun. Bu durumda gönderici taraf sırasıyla, "1K + 1K + 1K + 1K + 1K" uzunluğunda toplam "5K" lık bilgiyi karşı tarafa göndermiş olsun. Eğer henüz "ACK" gelmemişse gönderici taraf en fazla "3K" kadar daha bilgi gönderebilir. Diğer yandan "TCP" de her "ACK" sırasında yeni pencere genişliği de karşı tarafa gönderilmek zorundadır. Yani "ACK" paketi gönderilirken aynı zamanda yeni pencere genişliği de gönderilmektedir. "ACK" paketi yalnızca alındı bilgisini göndermek için değil, pencere genişliğini ayarlamak için de gönderilebilmektedir. Başka bir deyişle bir taraf, "yalnızca bilgi aldığı için" "ACK" göndermek zorunda değildir. Hiç bilgi almadığı halde yeni pencere genişliğini karşı tarafa bildirmek için de "ACK" gönderebilir. Pencere genişliği en fazla "64K" olabilir. Çünkü bunun için "TCP" başlığında "16-bit" yer ayrılmıştır. * Örnek 1, Şimdi bir tarafın diğer tarafa "send" fonksiyonu ile sürekli bilgi gönderdiğini ancak diğer tarafın bilgiyi "recv" ile okumadığını düşünelim. İşletim sisteminin alıcı taraf için oluşturduğu alma tamponunun "1MB" olduğunu düşünelim. Alıcı taraf muhtemelen bilgi geldikçe "ACK" yaparken "64K" lık pencere genişliğini karşı tarafa bildirecektir. Ancak zamanla alma tamponu dolduğu için bu pencere genişliğini düşürecek en sonunda "ACK" ile pencere genişliğini "0" yapacak ve karşı tarafa "artık gönderme" diyecektir. Pencere genişliği ile alma tamponunun genişliği birbirine karıştırılmamalıdır; Alma tamponu gelen bilgilerin yerleştirildiği tampondur. Pencere genişliği karşı tarafın "ACK" almadıktan sonra gönderebileceği maksimum bayt sayısıdır. Pekiyi pencere genişlikleri ve sıra numaraları bağlantı sırasında nasıl karşı tarafa bildirilmektedir? İşte bağlantı kurulurken "client" taraf "SYN" paketi içerisinde kendi başlangıç sıra numarasını karşı tarafa iletmektedir. "Server" da bağlantıyı kabul ederken yine "SYN" (ya da "SYN + ACK") paketinde kendi sıra numarasını karşı tarafa bildirmektedir. Pencere genişliği de aslında ilk kez bağlantı yapılırken "ACK" paketlerinde belirtilmektedir. "TCP/IP" de "stack" gerçekleştirimleri "ACK" stratejisi için bazı yöntemler uygulamaktadır. * Örnek 1, Eğer gönderilecek paket varsa bununla birlikte "ACK" paketinin gönderilmesi, ACK'ların iki paket biriktirildikten sonra gönderilmesi vb. * Örnek 2, Pencere genişliklerinin ayarlanması için de bazı stratejiler izlenebilmektedir. Bunun için "TCP/IP Protocol Suite" kitabının 466'ıncı sayfasına başvurabilirsiniz. "TCP" paketindeki önemli "Flag"'lerden birisi de "RST" bitidir. Buna "reset isteği" denilmektedir. Bir taraf "RST" bayrağı "set" edilmiş paket alırsa artık karşı tarafın "abnormal" bir biçimde bağlantıyı kopartıp yeniden bağlanma talep ettiği anlaşılır. Normal sonlanma el sıkışarak başarılı bir biçimde yapılırken, "RST" işlemi anormal sonlanmaları temsil eder. * Örnek 1, soket kütüphanelerinde hiç "shutdown" yapmadan soket "close" edilirse "close" eden taraf karşı tarafa "RST" paketi göndermektedir. Halbuki önce "shutdown" yapılırsa el sıkışmalı sonlanma gerçekleştirilir. O halde her zaman aktif soketler "shutdown" yapıldıktan sonra "close" edilmelidir. >> "UDP/IP" Protokolünün Alt Seviye İşlemleri: "UDP" protokolü aslında saf "IP" protokolüne çok benzerdir. "UDP" yi "IP" den ayıran iki önemli farklılık şudur: -> "UDP", port numarası kavramına sahiptir. -> "UDP" nin hata için bir "checksum" mekanizması vardır. Yani bir taraf diğer tarafa "UDP" paketi gönderirken, gönderdiği veri için, "checksum" bilgisini de "UDP" başlık kısmına iliştirmektedir. Bir "UDP" paketi yine aslında "IP" paketinin veri kısmında bulunmaktadır. "UDP header" ı "8" bayttan oluşmaktadır ve yapısı aşağıdaki gibidir. <------- Byte 1 -------><------- Byte 2 -------><------- Byte 3 -------><------- Byte 4 -------> +----------------------------------------------+-----------------------------------------------+ ^ | Source Port | Destination Port | (4 bytes) | | (16 bits) | (16 bits) | | +----------------------------------------------+-----------------------------------------------+ | 8 bytes | Length | Checksum | (4 bytes) | | (16 bits) | (16 bits) | | +----------------------------------------------+-----------------------------------------------+ v | Application Layer Data | | (Size Varies) | +----------------------------------------------------------------------------------------------+ Burada "UDP" paketinin toplam uzunluğunun bulunması aslında gereksizdir. Çünkü uzunluk "TCP" de olduğu gibi aslında "IP" paketinin başlığına bakılarak tespit edilebilmektedir. Ancak hesaplama kolaylığı oluşturmak için bu uzunluk "UDP" başlığında ayrıca bulundurulmuştur. Ayrıca "checksum", "UDP" paketlerinde bulunmak zorunda değildir. Eğer gönderici "checksum" kontrolü istemiyorsa burayı "0" bitleriyle doldurur. (Eğer zaten "checksum" "0" ise burayı "1" bitleriyle doldurmaktadır.) Alan taraf "checksum" hatasıyla karşılaşırsa "TCP" de olduğu gibi paketi yeniden talep etmez. Yalnızca onu atar. > "UNIX Domain Socket" : Aynı makinadaki prosesler arasında "socket" fonksiyonları kullanılarak haberleşme yapılmasıdır. Aynı makinadaki "Client-Server" uygulamalarında, birden çok "client" bağlnacaksa, "socket" fonksiyonları kullanılarak gerçekleştirim sağlanması daha kolaydır ve haberleşme daha hızlıdır. Çünkü "IP" protokol ailesinin kullanılması oldukça yavaş bir haberleşme sağlamaktadır. İşte bu amaçla oluşturulan soket nesnelerine "UNIX Domain Socket" adı verilir. Buradaki soket nesneleri, prosesler arasında kullanılan soket nesnelerinden farklıdır. Yine bu "UNIX Domain Socket" pekala Windows sistemleri ve macOS sistemleri tarafından da desteklenmektedir. Bir "UNIX Domain Socket" oluşturabilmek için "socket" fonksiyonunun birinci parametresi, yani "(protocol family)", için "AF_UNIX" geçilmelidir. "UNIX Domain Socket" kullanırken şu noktalara da dikkat etmeliyiz; -> "UNIX Domain Socket", "TCP/IP" ya da "UDP/IP" soketlerle bir ilgisi yoktur. Bu soketler UNIX/Linux sistemlerinde oldukça etkin bir biçimde gerçekleştirilmektedir. Dolayısıyla aynı makinenin prosesleri arasında haberleşmede borulara, mesaj kuyruklarına, paylaşılan bellek alanlarına bir seçenek olarak kullanılabilmektedir. Hatta bazı UNIX türevi sistemlerde (ama Linux'ta böyle değil) aslında çekirdek tarafından önce bu protokol gerçekleştirilip, daha sonra da boru mekanizması bu protokol kullanılarak, gerçekleştirilmektedir. Böylece örneğin aynı makinedeki iki prosesin haberleşmesi için "UNIX Domain Socket", "TCP/IP" ve "UDP/IP" soketlerine göre çok daha hızlı çalışmaktadır. -> Aynı makine üzerinde çok "client" lı uygulamalar için "UNIX Domain Socket", boru haberleşmesine ve mesaj kuyruklarına göre organizasyonel avantaj bakımından tercih edilebilmektedir. Çünkü çok "client" lı boru uygulamalarını ve mesaj kuyruğu uygulamalarını yazmak daha zahmetlidir. Programcılar "TCP/IP" ve "UDP/IP" soket haberleşmesi yaparken kullandıkları fonksiyonların aynısını "UNIX Domain Socket" de de kullanabilmektedir. Böylece örneğin elimizde bir "TCP/IP" ya da "UDP/IP" bir "Client-Server" program varsa, bu programı kolaylıkla "UNIX Domain Socket" kullanılacak biçimde değiştirebiliriz. -> "UNIX Domain Socket" kullanımı ise en çok boru kullanımına benzemektedir. Ancak "UNIX Domain Socket" in, borulara olan bir üstünlüğü "full duplex" haberleşme sunmasıdır. Bilindiği gibi borular "half duplex" bir haberleşme sunmaktadır. Ancak genel olarak boru haberleşmeleri, "UNIX Domain Socket" haberleşmelere göre, daha hızlı olma eğilimindedir. -> "UNIX Domain Socket", kullanım olarak daha önce görmüş olduğumuz "TCP/IP" ve "UDP/IP" soketlerine çok benzemektedir. Yani işlemler sanki "TCP/IP" ya da "UDP/IP" tip "Client-Server" program yazılıyormuş gibi yapılır. Başka bir deyişle "UNIX Domain Socket" de, "client" ve "server" programların genel yazım adımları "TCP/IP" ve "UDP/IP" ile aynıdır. -> "UNIX Domain Socket", "client" ın "server" a bağlanması için gereken adres, bir dosya ismi yani yol ifadesi biçimindedir. Kullanılacak yapı "sockaddr_in" değil, "sockaddr_un" yapısıdır. Bu yapı "" dosyası içerisinde bildirilmiştir ve en azından şu elemanlara sahip olmak zorundadır: #include struct sockaddr_un { sa_family_t sun_family; char sun_path[108]; }; Bu yapının "sun_family" elemanı "AF_UNIX" biçiminde, "sun_path" elemanı da soketi temsil eden dosyanın yol ifadesi biçiminde girilmelidir. Burada yol ifadesiyle belirtilen dosya "bind" işlemi tarafından yaratılmaktadır. Yaratılan bu dosyanın türü "ls -l" komutunda "(s)ocket" biçiminde görüntülenmektedir. Eğer bu dosya zaten varsa "bind" fonksiyonu başarısız olur. Dolayısıyla bu dosyanın varsa silinmesi gerekmektedir. O halde "client" ve "server" programlar işin başında bir isim altında anlaşmalıdır. Önemli bir nokta da şudur: "sockaddr_un" yapısının kullanılmadan önce sıfırlanması gerekmektedir. Öte yandan "bind" tarafından yaratılan bu soket dosyaları, normal bir dosya değildir. Yani "open" fonksiyonuyla açılamamaktadır. -> "UNIX Domain Socket", port numarası biçiminde bir kavramın olmadığına dikkat ediniz. Port numarası kavramı "IP" ailesinin aktarım katmanına ilişkin bir kavramdır. "UNIX Domain Socket", "AF_UNIX" protokol ailesi ismiyle oluşturulan başka bir ailenin soketleridir. -> "UNIX Domain Socket", "server" programı "accept" uyguladığında "client" a ilişkin "sockaddr_un" yapısından, aslında bu protokolde bir "port" kavramı olmadığına göre "server" program bağlantıdan, bir bilgi elde etmeyecektir. Fakat yine de "client" program da "bind" uygulayıp ondan sonra sokete bağlanabilir. Bu durumda "server", "client" bağlantısından sonra "sockaddr_un" yapısından "client" ın "bind" ettiği soket dosyasının yol ifadesini elde eder. -> "UNIX Domain Socket", aynı makinenin prosesleri arasında haberleşme sağladığına göre, bunlarda "send" işlemi ile gönderilen bilginin tek bir "recv" işlemi ile alınması beklenir. Gerçekten de Linux sistemlerinde tasarım bu biçimde yapılmıştır. Ancak POSIX standartları bu konuda bir garanti vermemektedir. Ancak Linux sistemlerinde tıpkı borularda olduğu gibi bu işlem için ayrılan tampon büyüklüğünden fazla miktarda bayt, "send" (ya da "write") ile gönderildiğinde, parçalı okuma gerçekleşebilmektedir. Aşağıda "UNIX Domain Socket" için aşağıdaki örnekleri inceleyelim. * Örnek 1, Aşağıdaki "server" programda "thread" modeli kullanılmıştır. Yani "server" her "client" bağlantısında bir "thread" yaratmaktadır. Bu programların her ikisinde de komut satırı argümanı olarak soket dosyasının yol ifadesi alınmaktadır. Bir soket dosyası zaten var ise "bind" işleminin başarısız olacağını anımsayınız. /* uds-server.c */ #include #include #include #include #include #include #include #include #define BUFFER_SIZE 4096 typedef struct tagCLIENT_INFO { int sock; struct sockaddr_un sun; } CLIENT_INFO; void *client_thread_proc(void *param); char *revstr(char *str); void exit_sys(const char *msg); int main(int argc, char *argv[]) { int server_sock, client_sock; struct sockaddr_un sun_server, sun_client; socklen_t sun_len; CLIENT_INFO *ci; ssize_t result; pthread_t tid; if (argc != 2) { fprintf(stderr, "wrong number of arguments!..\n"); exit(EXIT_FAILURE); } if ((server_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) exit_sys("socket"); memset(&sun_server, 0, sizeof(sun_server)); sun_server.sun_family = AF_UNIX; strcpy(sun_server.sun_path, argv[1]); if (bind(server_sock, (struct sockaddr *)&sun_server, sizeof(sun_server)) == -1) exit_sys("bind"); if (listen(server_sock, 8) == -1) exit_sys("listen"); for (;;) { printf("waiting for connection...\n"); sun_len = sizeof(sun_client); if ((client_sock = accept(server_sock, (struct sockaddr *)&sun_client, &sun_len)) == -1) exit_sys("accept"); printf("Connected new client\n"); if ((ci = (CLIENT_INFO *)malloc(sizeof(CLIENT_INFO))) == NULL) { fprintf(stderr, "cannot allocate memory!...\n"); exit(EXIT_FAILURE); } ci->sock = client_sock; ci->sun = sun_client; if ((result = pthread_create(&tid, NULL, client_thread_proc, ci)) != 0) { fprintf(stderr, "pthread_create: %s\n", strerror(result)); exit(EXIT_FAILURE); } if ((result = pthread_detach(tid)) != 0) { fprintf(stderr, "pthread_detach: %s\n", strerror(result)); exit(EXIT_FAILURE); } } close(server_sock); return 0; } void *client_thread_proc(void *param) { char buf[BUFFER_SIZE + 1]; // BUFFER_SIZE is enough CLIENT_INFO *ci = (CLIENT_INFO *)param; ssize_t result; for (;;) { if ((result = recv(ci->sock, buf, BUFFER_SIZE, 0)) == -1) exit_sys("recv"); if (result == 0) break; buf[result] = '\0'; if (!strcmp(buf, "quit")) break; printf("%jd byte(s) received: %s\n", (intmax_t)result, buf); revstr(buf); if (send(ci->sock, buf, result, 0) == -1) exit_sys("send"); } printf("client disconnected...\n"); shutdown(ci->sock, SHUT_RDWR); close(ci->sock); free(ci); return NULL; } char *revstr(char *str) { size_t i, k; char temp; for (i = 0; str[i] != '\0'; ++i) ; for (--i, k = 0; k < i; ++k, --i) { temp = str[k]; str[k] = str[i]; str[i] = temp; } return str; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } /* uds-client.c */ #include #include #include #include #include #include #include #define BUFFER_SIZE 4096 void exit_sys(const char *msg); int main(int argc, char *argv[]) { int client_sock; struct sockaddr_un sun_server; ssize_t result; char buf[BUFFER_SIZE + 1]; char *str; if (argc != 2) { fprintf(stderr, "wrong number of arguments!..\n"); exit(EXIT_FAILURE); } if ((client_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) exit_sys("socket"); memset(&sun_server, 0, sizeof(sun_server)); sun_server.sun_family = AF_UNIX; strcpy(sun_server.sun_path, argv[1]); if (connect(client_sock, (struct sockaddr *)&sun_server, sizeof(sun_server)) == -1) exit_sys("connect"); for (;;) { printf("Yazı giriniz:"); if (fgets(buf, BUFFER_SIZE, stdin) == NULL) continue; if ((str = strchr(buf, '\n')) != NULL) *str = '\0'; if ((send(client_sock, buf, strlen(buf), 0)) == -1) exit_sys("send"); if (!strcmp(buf, "quit")) break; if ((result = recv(client_sock, buf, BUFFER_SIZE, 0)) == -1) exit_sys("recv"); if (result == 0) break; buf[result] = '\0'; printf("%ld bytes received: %s\n", (long)result, buf); } close(client_sock); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2, Aşağıdaki örnekte ise "client" program da "bind" işlemi uygulamaktadır. Böylece "server", "client" ın soket ismini "accept" fonksiyonundan elde edebilmektedir. Ancak uygulamada client'ın bu biçimde "bind" yapması genellikle tercih edilmemektedir. Eğer "client" a bir isim verilecekse, sonraki paragrafta açıklanacağı gibi, "soyut bir isim" verilmelidir. Buradaki örneğimizde yine "server" program soket dosyasının yol ifadesi ile çalıştırılmalıdır. "client" program da hem "server" soketin hem de "client" soketin yol ifadesi ile ./uds-server serversock ./uds-client serversock clientsock biçiminde çalıştırılmalıdır. /* uds-server.c */ #include #include #include #include #include #include #include #include #define BUFFER_SIZE 4096 typedef struct tagCLIENT_INFO { int sock; struct sockaddr_un sun; } CLIENT_INFO; void *client_thread_proc(void *param); char *revstr(char *str); void exit_sys(const char *msg); int main(int argc, char *argv[]) { int server_sock, client_sock; struct sockaddr_un sun_server, sun_client; socklen_t sun_len; CLIENT_INFO *ci; ssize_t result; pthread_t tid; if (argc != 2) { fprintf(stderr, "wrong number of arguments!..\n"); exit(EXIT_FAILURE); } if ((server_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) exit_sys("socket"); memset(&sun_server, 0, sizeof(sun_server)); sun_server.sun_family = AF_UNIX; strcpy(sun_server.sun_path, argv[1]); if (bind(server_sock, (struct sockaddr *)&sun_server, sizeof(sun_server)) == -1) exit_sys("bind"); if (listen(server_sock, 8) == -1) exit_sys("listen"); for (;;) { printf("waiting for connection...\n"); sun_len = sizeof(sun_client); if ((client_sock = accept(server_sock, (struct sockaddr *)&sun_client, &sun_len)) == -1) exit_sys("accept"); printf("Connected new client: %s\n", sun_client.sun_path); if ((ci = (CLIENT_INFO *)malloc(sizeof(CLIENT_INFO))) == NULL) { fprintf(stderr, "cannot allocate memory!...\n"); exit(EXIT_FAILURE); } ci->sock = client_sock; ci->sun = sun_client; if ((result = pthread_create(&tid, NULL, client_thread_proc, ci)) != 0) { fprintf(stderr, "pthread_create: %s\n", strerror(result)); exit(EXIT_FAILURE); } if ((result = pthread_detach(tid)) != 0) { fprintf(stderr, "pthread_detach: %s\n", strerror(result)); exit(EXIT_FAILURE); } } close(server_sock); return 0; } void *client_thread_proc(void *param) { char buf[BUFFER_SIZE + 1]; // BUFFER_SIZE is enough CLIENT_INFO *ci = (CLIENT_INFO *)param; ssize_t result; for (;;) { if ((result = recv(ci->sock, buf, BUFFER_SIZE, 0)) == -1) exit_sys("recv"); if (result == 0) break; buf[result] = '\0'; if (!strcmp(buf, "quit")) break; printf("%jd byte(s) received from \"%s\": %s\n", (intmax_t)result, ci->sun.sun_path, buf); revstr(buf); if (send(ci->sock, buf, result, 0) == -1) exit_sys("send"); } printf("\"%s\" client disconnected...\n", ci->sun.sun_path); shutdown(ci->sock, SHUT_RDWR); close(ci->sock); free(ci); return NULL; } char *revstr(char *str) { size_t i, k; char temp; for (i = 0; str[i] != '\0'; ++i) ; for (--i, k = 0; k < i; ++k, --i) { temp = str[k]; str[k] = str[i]; str[i] = temp; } return str; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } /* uds-client.c */ #include #include #include #include #include #include #include #define BUFFER_SIZE 4096 void exit_sys(const char *msg); int main(int argc, char *argv[]) { int client_sock; struct sockaddr_un sun_server, sun_client; ssize_t result; char buf[BUFFER_SIZE + 1]; char *str; if (argc != 3) { fprintf(stderr, "wrong number of arguments!..\n"); exit(EXIT_FAILURE); } if ((client_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) exit_sys("socket"); memset(&sun_client, 0, sizeof(sun_client)); sun_client.sun_family = AF_UNIX; strcpy(sun_client.sun_path, argv[2]); if (bind(client_sock, (struct sockaddr *)&sun_client, sizeof(sun_client)) == -1) exit_sys("bind"); memset(&sun_server, 0, sizeof(sun_server)); sun_server.sun_family = AF_UNIX; strcpy(sun_server.sun_path, argv[1]); if (connect(client_sock, (struct sockaddr *)&sun_server, sizeof(sun_server)) == -1) exit_sys("connect"); for (;;) { printf("Yazı giriniz:"); if (fgets(buf, BUFFER_SIZE, stdin) == NULL) continue; if ((str = strchr(buf, '\n')) != NULL) *str = '\0'; if ((send(client_sock, buf, strlen(buf), 0)) == -1) exit_sys("send"); if (!strcmp(buf, "quit")) break; if ((result = recv(client_sock, buf, BUFFER_SIZE, 0)) == -1) exit_sys("recv"); if (result == 0) break; buf[result] = '\0'; printf("%ld bytes received: %s\n", (long)result, buf); } close(client_sock); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 3, Aslında Linux sistemlerinde "client" ın "server" a kendini tanıtması için "soyut (abstract)" bir adres de oluşturulabilmektedir. "client" program "sockaddr_un" yapısındaki "sun_path" elemanının ilk baytını "NULL" karakter olarak geçip, diğer baytlarına bir bilgi girebilir. "client", bu biçimde "bind" işlemi yaptığında, artık soket dosyası yaratılmaz. Ancak bu isim "accept" ile karşı tarafa iletilir. Dolayısıyla "client" ın girmiş olduğu yol ifadesi aslında soyut bir yol ifadesi olarak "client" ı tespit etmek amacıyla kullanılabilir. (Bu özelliğin POSIX standartlarında bulunmadığını, yalnızca Linux sistemlerine özgü olduğunu bir kez daha anımsatmak istiyoruz.) /* uds-server.c */ #include #include #include #include #include #include #include #include #define BUFFER_SIZE 4096 typedef struct tagCLIENT_INFO { int sock; struct sockaddr_un sun; } CLIENT_INFO; void *client_thread_proc(void *param); char *revstr(char *str); void exit_sys(const char *msg); int main(int argc, char *argv[]) { int server_sock, client_sock; struct sockaddr_un sun_server, sun_client; socklen_t sun_len; CLIENT_INFO *ci; ssize_t result; pthread_t tid; if (argc != 2) { fprintf(stderr, "wrong number of arguments!..\n"); exit(EXIT_FAILURE); } if ((server_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) exit_sys("socket"); memset(&sun_server, 0, sizeof(sun_server)); sun_server.sun_family = AF_UNIX; strcpy(sun_server.sun_path, argv[1]); if (bind(server_sock, (struct sockaddr *)&sun_server, sizeof(sun_server)) == -1) exit_sys("bind"); if (listen(server_sock, 8) == -1) exit_sys("listen"); for (;;) { printf("waiting for connection...\n"); sun_len = sizeof(sun_client); if ((client_sock = accept(server_sock, (struct sockaddr *)&sun_client, &sun_len)) == -1) exit_sys("accept"); printf("Connected new client: %s\n", sun_client.sun_path + 1); if ((ci = (CLIENT_INFO *)malloc(sizeof(CLIENT_INFO))) == NULL) { fprintf(stderr, "cannot allocate memory!...\n"); exit(EXIT_FAILURE); } ci->sock = client_sock; ci->sun = sun_client; if ((result = pthread_create(&tid, NULL, client_thread_proc, ci)) != 0) { fprintf(stderr, "pthread_create: %s\n", strerror(result)); exit(EXIT_FAILURE); } if ((result = pthread_detach(tid)) != 0) { fprintf(stderr, "pthread_detach: %s\n", strerror(result)); exit(EXIT_FAILURE); } } close(server_sock); return 0; } void *client_thread_proc(void *param) { char buf[BUFFER_SIZE + 1]; // BUFFER_SIZE is enough CLIENT_INFO *ci = (CLIENT_INFO *)param; ssize_t result; for (;;) { if ((result = recv(ci->sock, buf, BUFFER_SIZE, 0)) == -1) exit_sys("recv"); if (result == 0) break; buf[result] = '\0'; if (!strcmp(buf, "quit")) break; printf("%jd byte(s) received from \"%s\": %s\n", (intmax_t)result, ci->sun.sun_path + 1, buf); revstr(buf); if (send(ci->sock, buf, result, 0) == -1) exit_sys("send"); } printf("\"%s\" client disconnected...\n", ci->sun.sun_path + 1); shutdown(ci->sock, SHUT_RDWR); close(ci->sock); free(ci); return NULL; } char *revstr(char *str) { size_t i, k; char temp; for (i = 0; str[i] != '\0'; ++i) ; for (--i, k = 0; k < i; ++k, --i) { temp = str[k]; str[k] = str[i]; str[i] = temp; } return str; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } /* uds-client.c */ #include #include #include #include #include #include #include #define BUFFER_SIZE 4096 void exit_sys(const char *msg); int main(int argc, char *argv[]) { int client_sock; struct sockaddr_un sun_server, sun_client; ssize_t result; char buf[BUFFER_SIZE + 1]; char *str; if (argc != 3) { fprintf(stderr, "wrong number of arguments!..\n"); exit(EXIT_FAILURE); } if ((client_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) exit_sys("socket"); memset(&sun_client, 0, sizeof(sun_client)); sun_client.sun_family = AF_UNIX; strcpy(sun_client.sun_path + 1, argv[2]); if (bind(client_sock, (struct sockaddr *)&sun_client, sizeof(sun_client)) == -1) exit_sys("bind"); memset(&sun_server, 0, sizeof(sun_server)); sun_server.sun_family = AF_UNIX; strcpy(sun_server.sun_path, argv[1]); if (connect(client_sock, (struct sockaddr *)&sun_server, sizeof(sun_server)) == -1) exit_sys("connect"); for (;;) { printf("Yazı giriniz:"); if (fgets(buf, BUFFER_SIZE, stdin) == NULL) continue; if ((str = strchr(buf, '\n')) != NULL) *str = '\0'; if ((send(client_sock, buf, strlen(buf), 0)) == -1) exit_sys("send"); if (!strcmp(buf, "quit")) break; if ((result = recv(client_sock, buf, BUFFER_SIZE, 0)) == -1) exit_sys("recv"); if (result == 0) break; buf[result] = '\0'; printf("%ld bytes received: %s\n", (long)result, buf); } close(client_sock); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 4, Aslında Linux sistemlerinde "server" program da "bind" işlemini yaparken soyut isim kullanabilir. Yani "server" program da aslında "sockaddr_un" yapısındaki "sun_path" elemanının ilk karakterini "NULL" karakter yapıp diğer karakterlerine soketin ismini yerleştirebilir. Bu durumda haberleşme sırasında gerçekte hiçbir soket dosyası yaratılmayacaktır. Tabii soket dosyalarının önemli bir işlevi erişim haklarına sahip olmasıdır. Soyut isimler kullanıldığında böyle bir erişim hakkı kontrolü yapılmamaktadır. (Aşağıdaki örnekte server program da soyut bir isim kullanmaktadır. Buradaki haberleşmede hiç soket dosyasının yaratılmayacağına dikkat ediniz.) /* uds-server.c */ #include #include #include #include #include #include #include #include #define BUFFER_SIZE 4096 typedef struct tagCLIENT_INFO { int sock; struct sockaddr_un sun; } CLIENT_INFO; void *client_thread_proc(void *param); char *revstr(char *str); void exit_sys(const char *msg); int main(int argc, char *argv[]) { int server_sock, client_sock; struct sockaddr_un sun_server, sun_client; socklen_t sun_len; CLIENT_INFO *ci; ssize_t result; pthread_t tid; if (argc != 2) { fprintf(stderr, "wrong number of arguments!..\n"); exit(EXIT_FAILURE); } if ((server_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) exit_sys("socket"); memset(&sun_server, 0, sizeof(sun_server)); sun_server.sun_family = AF_UNIX; strcpy(sun_server.sun_path + 1, argv[1]); if (bind(server_sock, (struct sockaddr *)&sun_server, sizeof(sun_server)) == -1) exit_sys("bind"); if (listen(server_sock, 8) == -1) exit_sys("listen"); for (;;) { printf("waiting for connection...\n"); sun_len = sizeof(sun_client); if ((client_sock = accept(server_sock, (struct sockaddr *)&sun_client, &sun_len)) == -1) exit_sys("accept"); printf("Connected new client: %s\n", sun_client.sun_path + 1); if ((ci = (CLIENT_INFO *)malloc(sizeof(CLIENT_INFO))) == NULL) { fprintf(stderr, "cannot allocate memory!...\n"); exit(EXIT_FAILURE); } ci->sock = client_sock; ci->sun = sun_client; if ((result = pthread_create(&tid, NULL, client_thread_proc, ci)) != 0) { fprintf(stderr, "pthread_create: %s\n", strerror(result)); exit(EXIT_FAILURE); } if ((result = pthread_detach(tid)) != 0) { fprintf(stderr, "pthread_detach: %s\n", strerror(result)); exit(EXIT_FAILURE); } } close(server_sock); return 0; } void *client_thread_proc(void *param) { char buf[BUFFER_SIZE + 1]; // BUFFER_SIZE is enough CLIENT_INFO *ci = (CLIENT_INFO *)param; ssize_t result; for (;;) { if ((result = recv(ci->sock, buf, BUFFER_SIZE, 0)) == -1) exit_sys("recv"); if (result == 0) break; buf[result] = '\0'; if (!strcmp(buf, "quit")) break; printf("%jd byte(s) received from \"%s\": %s\n", (intmax_t)result, ci->sun.sun_path + 1, buf); revstr(buf); if (send(ci->sock, buf, result, 0) == -1) exit_sys("send"); } printf("\"%s\" client disconnected...\n", ci->sun.sun_path + 1); shutdown(ci->sock, SHUT_RDWR); close(ci->sock); free(ci); return NULL; } char *revstr(char *str) { size_t i, k; char temp; for (i = 0; str[i] != '\0'; ++i) ; for (--i, k = 0; k < i; ++k, --i) { temp = str[k]; str[k] = str[i]; str[i] = temp; } return str; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } /* uds-client.c */ #include #include #include #include #include #include #include #define BUFFER_SIZE 4096 void exit_sys(const char *msg); int main(int argc, char *argv[]) { int client_sock; struct sockaddr_un sun_server, sun_client; ssize_t result; char buf[BUFFER_SIZE + 1]; char *str; if (argc != 3) { fprintf(stderr, "wrong number of arguments!..\n"); exit(EXIT_FAILURE); } if ((client_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) exit_sys("socket"); memset(&sun_client, 0, sizeof(sun_client)); sun_client.sun_family = AF_UNIX; strcpy(sun_client.sun_path + 1, argv[2]); if (bind(client_sock, (struct sockaddr *)&sun_client, sizeof(sun_client)) == -1) exit_sys("bind"); memset(&sun_server, 0, sizeof(sun_server)); sun_server.sun_family = AF_UNIX; strcpy(sun_server.sun_path + 1, argv[1]); if (connect(client_sock, (struct sockaddr *)&sun_server, sizeof(sun_server)) == -1) exit_sys("connect"); for (;;) { printf("Yazı giriniz:"); if (fgets(buf, BUFFER_SIZE, stdin) == NULL) continue; if ((str = strchr(buf, '\n')) != NULL) *str = '\0'; if ((send(client_sock, buf, strlen(buf), 0)) == -1) exit_sys("send"); if (!strcmp(buf, "quit")) break; if ((result = recv(client_sock, buf, BUFFER_SIZE, 0)) == -1) exit_sys("recv"); if (result == 0) break; buf[result] = '\0'; printf("%ld bytes received: %s\n", (long)result, buf); } close(client_sock); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 5, "UNIX Domain Socket", "datagram" haberleşme de yapılabilir. Bu haberleşme mesaj kuyruklarına bir seçenek oluşturmaktadır. "UNIX Domain Socket", datagram haberleşmede gönderilen "datagram" ların aynı sırada alınması garanti edilmiştir. Yani gönderim "UDP/IP" de olduğu gibi güvensiz değil, güvenlidir. Anımsanacağı gibi "UDP/IP" de gönderilen "datagram" lar hedefe farklı sıralarda ulaşabiliyordu. Aynı zamanda bir "datagram" ağda kaybolursa bunun bir telafisi söz konusu değildi. "UNIX Domain Socket" her şey aynı makinede ve işletim sisteminin kontrolü altında gerçekleştirildiği için böylesi bir durum söz konusu olmayacaktır. Aşağıda "UNIX Domain Socket" kullanılarak bir "datagram" haberleşme örneği verilmiştir. Burada "server" hiç bağlantı sağlamadan herhangi bir "client" tan paketi alır, oradaki yazıyı ters çevirip ona geri gönderir. Hem "client" hem de "server" ayrı ayrı iki dosya ismi ile "bind" işlemi yapmaktadır. "server" program komut satırı argümanı olarak kendi "bind" edeceği soket dosyasının yol ifadesini, "client" program ise hem kendi "bind" edeceği soket dosyasının yol ifadesini hem de "server" soketin yol ifadesini almaktadır. (Bu örnekte "server" ve "client" programları, önce "remove" fonksiyonu ile daha önce yaratılan soket dosyasını aynı zamanda silmektedir.) Bu örnekte "client", "server" a bir "datagram" mesaj göndermekte ve "server" da onu ters çevirip "client" a geri yollamaktadır. "server" program, "server" soketin yol ifadesini komut satırı argümanı olarak almaktadır. "client" program da hem "server" soketin yol ifadesini hem de "client" soketin yol ifadesini komut satırı argümanı olarak almaktadır. Programları, ./uds-dg-server serversock ./uds-dg-client serversock clientsock komutlarıyla çalıştırabiliriz. /* uds-dg-server.c */ #include #include #include #include #include #include #include #define BUFFER_SIZE 4096 char *revstr(char *str); void exit_sys(const char *msg); int main(int argc, char *argv[]) { int sock; struct sockaddr_un sun_server, sun_client; socklen_t sun_len; ssize_t result; char buf[BUFFER_SIZE + 1]; if (argc != 2) { fprintf(stderr, "wrong number of arguments!..\n"); exit(EXIT_FAILURE); } if ((sock = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) exit_sys("socket"); memset(&sun_server, 0, sizeof(sun_server)); sun_server.sun_family = AF_UNIX; strcpy(sun_server.sun_path, argv[1]); if (remove(argv[1]) == -1 && errno != ENOENT) exit_sys("remove"); if (bind(sock, (struct sockaddr *)&sun_server, sizeof(sun_server)) == -1) exit_sys("bind"); printf("Waiting for client data...\n"); for (;;) { sun_len = sizeof(sun_client); if ((result = recvfrom(sock, buf, BUFFER_SIZE, 0, (struct sockaddr *)&sun_client, &sun_len)) == -1) exit_sys("recvfrom"); buf[result] = '\0'; printf("%ld bytes received from \"%s\": %s\n", (long)result, sun_client.sun_path, buf); revstr(buf); if (sendto(sock, buf, strlen(buf), 0, (struct sockaddr *)&sun_client, sizeof(sun_client)) == -1) exit_sys("sendto"); } close(sock); return 0; } char *revstr(char *str) { size_t i, k; char temp; for (i = 0; str[i] != '\0'; ++i) ; for (--i, k = 0; k < i; ++k, --i) { temp = str[k]; str[k] = str[i]; str[i] = temp; } return str; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } /* uds-dg-client.c */ #include #include #include #include #include #include #include #define BUFFER_SIZE 4096 void exit_sys(const char *msg); int main(int argc, char *argv[]) { int sock; struct sockaddr_un sun_client, sun_server, sun_response; socklen_t sun_len; char buf[BUFFER_SIZE]; char *str; ssize_t result; if (argc != 3) { fprintf(stderr, "wrong number of arguments!..\n"); exit(EXIT_FAILURE); } if ((sock = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) exit_sys("socket"); memset(&sun_client, 0, sizeof(sun_client)); sun_client.sun_family = AF_UNIX; strcpy(sun_client.sun_path, argv[2]); if (remove(argv[2]) == -1 && errno != ENOENT) exit_sys("remove"); if (bind(sock, (struct sockaddr *)&sun_client, sizeof(sun_client)) == -1) exit_sys("bind"); memset(&sun_server, 0, sizeof(sun_server)); sun_server.sun_family = AF_UNIX; strcpy(sun_server.sun_path, argv[1]); for (;;) { printf("Yazı giriniz:"); fgets(buf, BUFFER_SIZE, stdin); if ((str = strchr(buf, '\n')) != NULL) *str = '\0'; if (!strcmp(buf, "quit")) break; if (sendto(sock, buf, strlen(buf), 0, (struct sockaddr *)&sun_server, sizeof(sun_server)) == -1) exit_sys("sendto"); sun_len = sizeof(sun_server); if ((result = recvfrom(sock, buf, BUFFER_SIZE, 0, (struct sockaddr *)&sun_response, &sun_len)) == -1) exit_sys("recvfrom"); buf[result] = '\0'; printf("%ld bytes received from \"%s\": %s\n", (long)result, sun_response.sun_path, buf); } close(sock); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 6, "UNIX Domain Socket", "isimsiz boru haberleşmesine" benzer biçimde de kullanılabilmektedir. Anımsanacağı gibi isimsiz borularla yalnızca üst ve alt proseslerer arasında haberleşme yapılabiliyordu (yine anımsayacağınız gibi "pipe" fonksiyonu bize iki betimleyici veriyordu. Biz de "fork" işlemi ile bu betimleyicileri alt prosese geçiriyorduk). İşte isimsiz borularla yapılan şeylerin benzeri soketlerle de yapılabilmektedir (isimsiz soketlere İngilizce "unbound sockets" de denilmektedir). "İsimsiz (unbound)" soket yaratımı "socketpair" isimli fonksiyonla yapılmaktadır. Fonksiyonun prototipi şöyledir: #include int socketpair(int domain, int type, int protocol, int sv[2]); Fonksiyonun birinci parametresi protokol ailesinin ismini alır. Her ne kadar fonksiyon genel olsa da pek çok işletim sistemi bu fonksiyonu yalnızca "UNIX Domain Socket" için gerçekleştirmektedir (gerçekten de üst ve alt prosesler arasında "UNIX Domain Socket" varken, örneğin, TCP/IP soketleriyle haberleşmenin zarardan başka bir faydası olmayacaktır.) Linux sistemleri isimsiz soket olarak yalnızca "UNIX Domain Socket" desteklemektedir. Dolayısıyla bu birinci parametre Linux sistemlerinde "AF_UNIX" biçiminde geçilmelidir. Fonksiyonun ikinci parametresi kullanılacak soketin türünü belirtir. Bu parametre yine "SOCK_STREAM" ya da "SOCK_DGRAM" biçiminde girilmelidir. Üçüncü parametre kullanılacak "Transport Layer" belirtmektedir. Bu parametre "0" olarak geçilebilir. Son parametre bir çift soket betimleyicisinin yerleştirileceği iki elemanlı "int" türden dizinin başlangıç adresini almaktadır. Fonksiyon başarı durumunda "0" değerine, başarısızlık durumunda "-1" değerine geri dönmektedir. Fonksiyonun kullanımı, //... int socks[2]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, socks) == -1) exit_sys("socketpair"); //... biçimindedir, tipik olarak. "socketpair" fonksiyonu "SOCK_STREAM" soketler için zaten bağlantı sağlanmış iki soketi bize vermektedir. Yani bu fonksiyon çağrıldıktan sonra "listen", "accept" ve "connect" gibi fonksiyonların çağrılması gereksizdir. Dolayısıyla tipik haberleşme şöyle gerçekleştirilmektedir: -> "socketpair" fonksiyonu ile soket çifti yaratılır. -> Soket çifçi yaratıldıktan sonra "fork" ile alt proses yaratılır. -> İki taraf da kullanmayacakları soketleri kapatırlar. Hangi prosesin, "socketpair" fonksiyonunun son parametresine yerleştirilen hangi soket betimleyicisini kullanacağının bir önemi yoktur. -> Haberleşme soket fonksiyonlarıyla gerçekleştirilir. Pekiyi isimsiz borularla, "socketpair" fonksiyonuyla oluşturulan isimsiz "UNIX Domain Socket" arasında ne fark vardır? Aslında bu iki kullanım benzer etkilere sahiptir. Ancak en önemli farklılık "UNIX Domain Socket" "çift yönlü (full duplex)" bir haberleşme sağlamasıdır. Normalde isimsiz mesaj kuyrukları olmadığına dikkat ediniz. Halbuki isimsiz "UNIX Domain Socket"sanki isimsiz mesaj kuyrukları gibi de kullanılabilmektedir. Aşağıdaki programda tıpkı isimsiz boru haberleşmesinde olduğu gibi üst ve alt prosesler birbirleri arasında isimsiz "UNIX Domain Socket" yoluyla haberleşmektedir. Buradaki soketlerin çift yönlü haberleşmeye olanak verdiğini anımsayınız. /* uds-socketpair.c */ #include #include #include #include #include #include #define BUFFER_SIZE 1024 char *revstr(char *str); void exit_sys(const char *msg); int main(void) { int socks[2]; char buf[BUFFER_SIZE + 1]; char *str; ssize_t result; pid_t pid; if (socketpair(AF_UNIX, SOCK_STREAM, 0, socks) == -1) exit_sys("socketpair"); if ((pid = fork()) == -1) exit_sys("fork"); if (pid != 0) { /* parent */ close(socks[1]); for (;;) { if ((result = recv(socks[0], buf, BUFFER_SIZE, 0)) == -1) exit_sys("recv"); if (result == 0) break; buf[result] = '\0'; if (!strcmp(buf, "quit")) break; revstr(buf); if (send(socks[0], buf, strlen(buf), 0) == -1) exit_sys("send"); } if (waitpid(pid, NULL, 0) == -1) exit_sys("waitpid"); close(socks[0]); exit(EXIT_SUCCESS); } else { /* child */ close(socks[0]); for (;;) { printf("Yazı giriniz:"); if (fgets(buf, BUFFER_SIZE, stdin) == NULL) continue; if ((str = strchr(buf, '\n')) != NULL) *str = '\0'; if ((send(socks[1], buf, strlen(buf), 0)) == -1) exit_sys("send"); if (!strcmp(buf, "quit")) break; if ((result = recv(socks[1], buf, BUFFER_SIZE, 0)) == -1) exit_sys("recv"); if (result == 0) break; buf[result] = '\0'; printf("%ld bytes received: %s\n", (long)result, buf); } close(socks[1]); exit(EXIT_SUCCESS); } return 0; } char *revstr(char *str) { size_t i, k; char temp; for (i = 0; str[i] != '\0'; ++i) ; for (--i, k = 0; k < i; ++k, --i) { temp = str[k]; str[k] = str[i]; str[i] = temp; } return str; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } > Hatırlatıcı Notlar: >> "Wireshark" Programı: Yerel ağımızda aslında "router" tarafından gönderilip alınan paketlerin hepsi yerel ağdaki tüm bilgisayarlara ulaşmaktadır. "Ethernet" ve "Wireless" kartları yalnızca kendilerini ilgilendiren paketleri alıp işletim sistemini haberdar edebilmektedir. Ancak bu kartlar için yazılmış özel programlar sayesinde bilgisayarımıza ulaşan tüm paketler incelenebilmektedir. Bu tür yardımcı programlara ise "network sniffer" da denilmektedir. En yaygın kullanılan "network sniffer" program "wireshark" isimli "open-source" programdır. Bu programın eskiden ismi "Ethereal" biçimindeydi. Aslında "wireshark" programı "libpcap" isimli "open-source" kütüphane kullanılarak yazılmıştır. Yani asıl işlevsellik bu kütüphanededir. "Wireshark" adeta "libpcap" kütüphanesinin bir "önyüzü (frontend)" gibidir. Bu kütüphanenin Windows versiyonuna "npcap" denilmektedir. Linux Debian türevi sistemlerde kütüphane aşağıdaki gibi indirilebilir: sudo apt-get install libpcap-dev Benzer biçimde Linux'ta "wireshark" programını da "GUI" arayüzü yazılım yöneticisinden yüklenebileceği gibi komut satırından Debian türevi sistemlerde aşağıdaki gibi yüklenebilir: sudo apt-get install wireshark Wireshark programının kullanımına ilişkin pek çok "tutorial" bulunmaktadır. Kursumuzun "E-Books" klasöründe de birkaç kitap bulunmaktadır. >> "TCP/IP" için yazmış olduğumuz "Server-Client" program aslında UNIX ve türevi sistemler içindir. Windows sistemlerindeki soket kütüphanesine "Winsock" denilmektedir. Şu anda bu kütüphanenin 2'inci versiyonu kullanılmaktadır. Winsock API fonksiyonları "UNIX/Linux uyumlu" fonksiyonlar ve Windows'a özgü fonksiyonlar olmak üzere iki biçimde kullanılabilmektedir. Ancak Winsock'un UNIX/Linux uyumlu fonksiyonlarında da birtakım değişiklikler söz konusudur. Bir UNIX/Linux ortamında yazılmış soket uygulamasının Windows sistemlerine aktarılması için şu düzeltmelerin yapılması gerekir: -> POSIX'in soket sistemine ilişkin tüm başlık dosyaları kaldırılır. Onun yerine "winsock2" başlık dosyası "include" edilir. -> "xxx_t" biçimindeki tür eş isimleri silinir ve onların yerine ki bu konuda ilgili dokümanlara da bakabilirsiniz, "int", "short", "unsigned int", "unsigned short" türleri kullanılır. Örneğin "ssize_t" türü ve "socklen_t" türleri yerine "int" türleri kullanılmalıdır. -> Windows'ta soket sisteminin başlatılması için "WSAStartup" fonksiyonu işin başında çağrılır ve işin sonunda da bu işlem "WSACleanup" fonksiyonuyla geri alınır. Bu fonksiyonları şöyle kullanbilirsiniz: WSADATA wsadata; ... if ((result = WSAStartup(MAKEWORD(2, 2), &wsadata)) != 0) exit_sys("WSAStartup", EXIT_FAILURE, result); ... WSACleanup(); -> Windows'ta dosya betimleyicisi kavramı yoktur. Onun yerine "handle" kavramı vardır. Dolayısıyla soket türü de "int" değil, "SOCKET" isimli bir tür eş ismidir. -> "shutdown" fonksiyonunun ikinci parametresi "SD_RECEIVE", "SD_SEND" ve "SD_BOTH" biçimindedir. -> "close" fonksiyonu yerine "closesocket" fonksiyonu ile soket kapatılır. -> Windows'ta soket fonksiyonları başarısızlık durumunda -1 değerine geri dönmezler. "socket" fonksiyonu başarısızlık durumunda "INVALID_SOCKET" değerine, diğerleri ise "SOCKET_ERROR" değerine geri dönmektedir. -> "Visual Studio IDE" sinde varsayılan durumda "deprecated" durumlar "error" e yükseltilmiştir. Bunlar için bir makro "define" edilebilmektedir. Ancak proje ayarlarından "sdl check", "disable" da edilebilir. Benzer biçimde proje ayarlarından "Unicode" değeri "not set" yapılmalıdır. -> Projenin "linker" ayarlarından "Input/Additional Dependencies > Edit" alanına "Winsock" kütüphanesi olan "Ws2_32.lib" kütüphanesi eklenir. -> Windows'ta son soket API fonksiyonlarının başarısızlık nedenleri "WSAGetLastError" fonksiyonuyla elde edilmektedir. Yani Windows sistemlerinde "errno" değişkeni set edilmemektedir. Belli bir hata kodunun yazıya dönüştürülmesi de biraz ayrıntılıdır. Bunun için aşağıdaki fonksiyonu kullanabilirsiniz: void ExitSys(LPCSTR lpszMsg, DWORD dwLastError) { LPTSTR lpszErr; if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwLastError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpszErr, 0, NULL)) { fprintf(stderr, "%s: %s", lpszMsg, lpszErr); LocalFree(lpszErr); } exit(EXIT_FAILURE); } Aşağıda bu yapılanları gösteren bir örnek verilmiştir. * Örnek 1, /* client.c */ #include #include #include #include #define SERVER_NAME "192.168.153.131" #define SERVER_PORT 55555 #define BUFFER_SIZE 4096 void ExitSys(LPCSTR lpszMsg, DWORD dwLastError); int main(void) { WSADATA wsadata; SOCKET client_sock; struct sockaddr_in sin_server; struct hostent *hent; char buf[BUFFER_SIZE]; char *str; int result; if ((result = WSAStartup(MAKEWORD(2, 2), &wsadata)) != 0) ExitSys("WSAStartup", result); if ((client_sock = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) ExitSys("socket", WSAGetLastError()); /* { struct sockaddr_in sin_client; sin_client.sin_family = AF_INET; sin_client.sin_port = htons(50000); sin_client.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(client_sock, (struct sockaddr *)&sin_client, sizeof(sin_client)) == SOCKET_ERROR) ExitSys("bind", WSAGetLastError()); } */ sin_server.sin_family = AF_INET; sin_server.sin_port = htons(SERVER_PORT); if ((sin_server.sin_addr.s_addr = inet_addr(SERVER_NAME)) == SOCKET_ERROR) { if ((hent = gethostbyname(SERVER_NAME)) == NULL) ExitSys("gethostbyname", WSAGetLastError()); memcpy(&sin_server.sin_addr.s_addr, hent->h_addr_list[0], hent->h_length); } if (connect(client_sock, (struct sockaddr *)&sin_server, sizeof(sin_server)) == SOCKET_ERROR) ExitSys("connect", WSAGetLastError()); printf("connected server...\n"); for (;;) { printf("csd>"); fflush(stdout); if (fgets(buf, BUFFER_SIZE, stdin) == NULL) continue; if ((str = strchr(buf, '\n')) != NULL) *str = '\0'; if (send(client_sock, buf, (int)strlen(buf), 0) == SOCKET_ERROR) ExitSys("send", WSAGetLastError()); if (!strcmp(buf, "quit")) break; } shutdown(client_sock, SD_BOTH); closesocket(client_sock); WSACleanup(); return 0; } void ExitSys(LPCSTR lpszMsg, DWORD dwLastError) { LPTSTR lpszErr; if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwLastError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpszErr, 0, NULL)) { fprintf(stderr, "%s: %s", lpszMsg, lpszErr); LocalFree(lpszErr); } exit(EXIT_FAILURE); } /* server.c */ #include #include #include #include #include #define SERVER_PORT 55555 #define BUFFER_SIZE 4096 void ExitSys(LPCSTR lpszMsg, DWORD dwLastError); int main(void) { WSADATA wsadata; SOCKET server_sock, client_sock; struct sockaddr_in sin_server, sin_client; int sin_len; char buf[BUFFER_SIZE + 1]; int result; if ((result = WSAStartup(MAKEWORD(2, 2), &wsadata)) != 0) ExitSys("WSAStartup", result); if ((server_sock = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) ExitSys("socket", WSAGetLastError()); sin_server.sin_family = AF_INET; sin_server.sin_port = htons(SERVER_PORT); sin_server.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(server_sock, (struct sockaddr *)&sin_server, sizeof(sin_server)) == SOCKET_ERROR) ExitSys("bind", WSAGetLastError()); if (listen(server_sock, 8) == SOCKET_ERROR) ExitSys("listen", WSAGetLastError()); printf("waiting for connection...\n"); sin_len = sizeof(sin_client); if ((client_sock = accept(server_sock, (struct sockaddr *)&sin_client, &sin_len)) == SOCKET_ERROR) ExitSys("accept", WSAGetLastError()); printf("connected client ===> %s:%d\n", inet_ntoa(sin_client.sin_addr), ntohs(sin_client.sin_port)); for (;;) { if ((result = recv(client_sock, buf, BUFFER_SIZE, 0)) == SOCKET_ERROR) ExitSys("recv", WSAGetLastError()); if (result == 0) break; buf[result] = '\0'; if (!strcmp(buf, "quit")) break; printf("%jd byte(s) received: \"%s\"\n", (intmax_t)result, buf); } shutdown(client_sock, SD_BOTH); closesocket(client_sock); closesocket(server_sock); WSACleanup(); return 0; } void ExitSys(LPCSTR lpszMsg, DWORD dwLastError) { LPTSTR lpszErr; if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwLastError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpszErr, 0, NULL)) { fprintf(stderr, "%s: %s", lpszMsg, lpszErr); LocalFree(lpszErr); } exit(EXIT_FAILURE); }