7.5. Temel Soket Fonksiyonları

Tipik olarak soket tabanlıveri haberleşmesinin uçlarından biri sunucu diğeri ise istemcidir.

7.5.1. Ortak Elemanlar

7.5.1.1. Soket

İstemciler ve sunucularda kullanılan fonksiyon

socket(2)
dir. Şu şekilde deklare edilir:

int socket(int domain, int type, int
          protocol);

Bu fonksiyonun dönüşdeğeri

open()
da olduğu gibi integer'dır. FreeBSD onun değerini dosyaların tutulduğu gibi aynı havuzdan alır. Bu soketlerin neden dosyalar gibi işlem gördüğünün cevabıdır. Domain argümanısisteme hangi protokol ailesini kullanmak istediğimizi anlatır. Bazılarımevcuttur, bazılarısatıcıtarafından belirlenmişolabilir, bazılarıise çok yaygındır. "sys/socket.h" dosyasında deklare edilmişlerdir. UDP, TCP ve diğer internet protokolleri (IPv4) için PF_INET kullanınız. sys/socket.h da bulunan beş değer type argümanıiçin tanımlanmıştır. Hepsi "SOCK_" katarıile başlar. En yaygın olanısistemden güvenilir akışiletim servisi (PF_INET ile kullanılırsa TCP dir) isteyen SOCK_STREAM'dir. Eğer SOCK_DGRAM olarak belirlersen bağlantısız veri iletim servisi (Bizim durumumuzda UDP) istemişolursun. Eğer daha alt seviyedeki protokolleri(örneğin IP) kullanmak isterseniz veya ağ arabirimlerini(örneğin Ethernet) kullanmak isterseniz SOCK_RAW kullanmalısınız. Son olarak protocol argümanıönceki iki argümana bağlıdır, ve her zaman anlamlıdeğildir. Bu duruma bağlıolarak bu değer için 0 kullanınız.

BağlanmamışSoket: Burada Soket fonksiyonun hiçbir yerinde bağlanmamız gereken sistemi belirtmedik. Yeni oluşturduğumuz soket bağlanmamıştır.

Bu niyete bağlıdır: Telefonla benzerlik kurarsak, telefon için sadece bir modemi telefon kablosuna bağlarız. Daha sonra ise ya telefonla bir yeri ararız ya da telefon çaldığında cevaplarız.

7.5.2. sockaddr

Çeşitli soket fonksiyonlarıbelleğin küçük bir alanının adresini (C terminolojisinde işaretçi (pointer)) parametre olarak alırlar. sys/socket.h daki çeşitli deklarasyonlar bu adresi struct sockaddr olarak belirtir. Bu yapıaynıdosyada şu şekilde deklare edilir:

/*
 * Structure used by kernel to store most
 * addresses.
 */
struct sockaddr {
	unsigned char	sa_len;		/* total length */
	sa_family_t	sa_family;	/* address family */
	char		sa_data[14];	/* actually longer; address value */
};
#define	SOCK_MAXADDRLEN	255		/* longest possible addresses */

14 baytlık diziden oluşan sa_data alanındaki açıklamadaki "gerçekten daha uzun" ibaresiyle bu alanın daha büyük olabileceği belirsizliğine dikkat edin. Bu belirsizlik kastidir. Soketler çok güçlü arayüzlerdir. Çoğu insanlar soketin muhtemelen internet arayüzünden başka bir şey olmadığınıdüşünselerde çoğu uygulama günümüzde onu her türlü prosesler arasıhaberleşmede kullanmaktadır ve internet arayüzü bunun sadece bir özelliğidir. sys/socket.h sockaddr tanımlamasından önce gelen soketlerin adres aileleri ve listeler halinde tuttuğu çeşitli protokolleri barındırmaktadır:

/*
 * Address families.
 */
#define	AF_UNSPEC	0		/* unspecified */
#define	AF_LOCAL	1		/* local to host (pipes, portals) */
#define	AF_UNIX		AF_LOCAL	/* backward compatibility */
#define	AF_INET		2		/* internetwork: UDP, TCP, etc. */
#define	AF_IMPLINK	3		/* arpanet imp addresses */
#define	AF_PUP		4		/* pup protocols: e.g. BSP */
#define	AF_CHAOS	5		/* mit CHAOS protocols */
#define	AF_NS		6		/* XEROX NS protocols */
#define	AF_ISO		7		/* ISO protocols */
#define	AF_OSI		AF_ISO
#define	AF_ECMA		8		/* European computer manufacturers */
#define	AF_DATAKIT	9		/* datakit protocols */
#define	AF_CCITT	10		/* CCITT protocols, X.25 etc */
#define	AF_SNA		11		/* IBM SNA */
#define AF_DECnet	12		/* DECnet */
#define AF_DLI		13		/* DEC Direct data link interface */
#define AF_LAT		14		/* LAT */
#define	AF_HYLINK	15		/* NSC Hyperchannel */
#define	AF_APPLETALK	16		/* Apple Talk */
#define	AF_ROUTE	17		/* Internal Routing Protocol */
#define	AF_LINK		18		/* Link layer interface */
#define	pseudo_AF_XTP	19		/* eXpress Transfer Protocol (no AF) */
#define	AF_COIP		20		/* connection-oriented IP, aka ST II */
#define	AF_CNT		21		/* Computer Network Technology */
#define pseudo_AF_RTIP	22		/* Help Identify RTIP packets */
#define	AF_IPX		23		/* Novell Internet Protocol */
#define	AF_SIP		24		/* Simple Internet Protocol */
#define	pseudo_AF_PIP	25		/* Help Identify PIP packets */
#define	AF_ISDN		26		/* Integrated Services Digital Network*/
#define	AF_E164		AF_ISDN		/* CCITT E.164 recommendation */
#define	pseudo_AF_KEY	27		/* Internal key-management function */
#define	AF_INET6	28		/* IPv6 */
#define	AF_NATM		29		/* native ATM access */
#define	AF_ATM		30		/* ATM */
#define pseudo_AF_HDRCMPLT 31		/* Used by BPF to not rewrite headers
					 * in interface output routine
					 */
#define	AF_NETGRAPH	32		/* Netgraph sockets */
#define	AF_SLOW		33		/* 802.3ad slow protocol */
#define	AF_SCLUSTER	34		/* Sitara cluster protocol */
#define	AF_ARP		35
#define	AF_BLUETOOTH	36		/* Bluetooth sockets */
#define	AF_MAX		37

IP için kullanılan AF_INET dir. Bu değeri 2 olan bir semboldür. O sa_data daki belirsizliğin nasıl kullanılacağına karar veren sockaddr sin sa_family alanında listelenmişadres ailesidir. Hususi olarak, adres ailesi olarak AF_INET kullanıldığında sockaddr beklenen yerlerde

netinet/in.h
da tanımlısockaddr_in yapısınıkullanabiliriz:

/*
 * Socket address, internet style.
 */
struct sockaddr_in {
	uint8_t		sin_len;
	sa_family_t	sin_family;
	in_port_t	sin_port;
	struct	in_addr sin_addr;
	char	sin_zero[8];
};

Organizasyonunu şu şekilde gösterebiliriz:

Üç önemli alan yapının 1. baytında bulunan

sin_family
, 16 bit uzunluğunda 2 ve 3. baytta bulunan
sin_port
, ve 32 bit uzunluğunda 4-7 baytlarıarasında bulunan ip adresini barındıran
sin_addr
'dir.

Şimdi bu alanlarıdolduralım. Basit olarak o anki tarihi ve zamanımetin katarışeklinde port 13 e yazan daytime sunucusu için istemci yazmaya çalıştığımızıdüşünelim. TCP/IP yi kullanmak isteriz. Dolayısıyla adres ailesi alanınıAF_INET olarak belirleriz. AF_INET 2 olarak tanımlanmıştır. IP adresi olarak Birleşik Devletler Federal Yönetiminin zaman sunucusu olan 192.43.244.18 i kullandığımızıdüşünelim (time.nist.gov).

Ayrıca

sin_addr
alanı net/inet.h da tanımlı
in_addr
yapısışeklinde tanımlanmıştır.

/* * Internet adresi (Geçmişten gelen bir yapı) */
          

struct in_addr { 

in_addr_t s_addr;

};

Ek olarak

in_addr_t 32
bit integerdır.

192.43.244.18 sadece klasik olarak kullanılan 32 bit integer değerinin her 8 bitinin "." lar arasında gösterilmiş şeklidir.

Şimdiye kadar sockaddr yapısına soyut olarak baktık. Bilgisayarımız short integer larıtek 16 bit şeklinde tutmaz, ardışıl 2 bayt şeklinde tutar. Benzer olarak 32-bit integerları ardışıl 4 bayt olarak tutar.

Şu şekilde bir kodlama yaptığımızıdüşünelim:

sa.sin_family = AF_INET;

sa.sin_port = 13;

sa.sin_addr.s_addr = (((((192 <<8) | 43) <<
          8) | 244) <<8) | 18;

Sonuç nasıl olur?

Sistemden sisteme değişir. Pentium(R) veya diğer x86 tabanlı bilgisayarlarda şu şekilde olur:

Başka bir sistemde şu şekilde gözükebilir:

Ve bir PDP de başka şekilde olabilir. Ama yukarda olan iki farklıdurum en yaygın olan şeklidir.

Alışılmışolarak, taşınabilir kod yazmak için programcılar bu farklılıklar yokmuşgibi davranırlar. Ve bu farklılıklardan kurtulurlar (makina dilindeki kodlamalar hariç). Yazık ki soketler için kod yazarken kolaylıkla bu farklılıklardan kurtulamazsınız.

Neden? Çünkü başka bilgisayarla haberleşirken onun veriyi en yüksek anlamlıbite göre mi yoksa en düşük anlamlıbite göre mi sakladığınıgenelde bilemezsin. Belki merak ediyorsundur, "Dolayısıyla, soketler bu durumu benim için halletmez mi?" Halletmez. Cevap ilk başta sende süpriz oluştururken, soket arayüzünün sadece

sockaddr
yapısının
sa_len
ve
sa_family
alanlarınıanladığını hatırla. Bayt sıralamasıiçin endişelenmene gerek yok (tabii ki, FreeBSD de
sa_family
sadece 1 bayt, ama bazıdiğer UNIX sistemlerinde
sa_len
yoktur ve
sa_family
2 bayttır, ve burada baytların sıralamasının bilgisayara bağlıolarak değişmesi beklenir). Soketlerin gidebildiği kadarıyla geri kalan veri sadece
sa_data[14]
'dır. Adres ailesine göre soketler bu veriyi hedefine yollar. Gerçekten port numarası yazmamızın nedeni diğer bilgisayara ne istediğimizi bildirmek istememizdir. Ve biz sunucu iken port numarasınıokuruz ve diğer bilgisayarın bizden nasıl hizmet beklediğini biliriz. Soketler sadece port numaralarınıveri şeklinde iletirler. Herhangi bir şekilde yorumlama yapmazlar. Benzer şekilde IP adresini diğer bilgisayarlara veri nereye yoladığımızıanlatmak için gireriz. Yine soketler IP adresini veri olarak iletirler. İşte biz programcıların (soketlerin değil) bilgisayarımızın kullandığıbayt sıralamasıile veriyi göndermek için kullanılan klasik bayt sıralamasınıayırt etmesinin nedeni budur. Bilgisayarımızın kullandığıbayt sıralamasını"ev bayt sıralaması(host byte order)" veya "ev sıralaması(host order)" olarak isimlendireceğiz. Çok baytlı verileri IP üzerinden gönderirken MSB(yüksek anlamlıbit) yi ilk göndermek klasik olan durumdur. Buna "ağ bayt sıralaması(network byte order)" diyoruz veya kısaca "ağ sıralaması(network order)". Şimdi yukarıdaki kodu Intel tabanlıbir bilgisayarda derlersek, ev bayt sıralamamız (host byte order) şu şekilde olur:

Ama ağ bayt sıralaması(network byte order) MSB nin önce saklanmasınıgerektirir.

Şanssız olarak, ev sıralamamız (host order) ağ sıralamasının tam tersidir. Bu sorunla başa çımanın birkaç yolu vardır. Biri değerleri kodumuzda değiştirmektir:

sa.sin_family = AF_INET;

sa.sin_port = 13 <<8;

sa.sin_addr.s_addr = (((((18 <<8) | 244) <<
          8) | 43) <<8) | 192;

Bu bilgisayarımızın veriyi ağ sıralamasında tutmasını sağlayacaktır. Bazıdurumlarda bu problemin üstesinden gelmenin kesin yoludur. (Örneğin makine dilinde programlama yaparken). Ancak neredeyse bütün durumlarda bu çözüm problem oluşturabilir.

C de soket tabanlıprogram yazdığınıdüşünelim. Programın bir Pentium da çalışacağınıbiliyorsun, dolayısıyla bütün sabitleri ters şekilde yaz ve ağ sıralamasışeklinde kullan. Çalışacaktır.

Daha sonra, güvendiğin eski Pentium'unun modasıgeçecek ve onu ev sıralaması(host order) ağ sıralamasıile aynıolan bir sistemle değiştireceksin. Bütün yazılımınıtekrar derlemen gerekecek. Bütün yazılımının çalışmasıeskisi gibi güzel olacak ama ağ ile ilgili yazdığın kısım olmayacak.

Çünkü bütün sabitleri ev sıralamasının tersine çevirdiğini unutmuşolacaksın. Tabii bu noktada hata tesbiti zor olacağından vaktinizi gereksiz yere zayi etmişolacaksınız ve daha sonra iyi bir şekilde çalışan programının aniden bozulmasının nedenini anlamak için her türlü klasik yöntemi deneyeceksin.

Nihayet, anlayacaksın, biraz söveceksin ve yeniden kod yazmaya başlayacaksın.

Şanslıolarak problemle karşılaşan ilk kişi sen değilsin. Birisi

htons(3)
ve
htonl(3)
C fonksiyonlarınıev sıralamasından ağ sıralamasına çevirmek için,
ntohs(3)
ve
ntohl(3)
fonksiyonlarınıda tam tersi işlev için yazdı.

MSB si başta olan sistemlerde fonksiyon birşey değiştirmez, LSB si başta olan sistemlerde ise gerekli çevirme yapılır.

Dolayısıyla eğer bu fonksiyonlarıkullanırsan yazılımının derlendiği sistemden bağımsız olarak veriler doğru sıralamada iletilirler

7.5.3. İstemci Fonksiyonları

Tipik olarak istemciler sunucuya olan bağlantıyıbaşlatırlar. İstemci hangi sunucuya bağlanacağınıbilir: İstemci sunucunun IP adresini ve sunucunun açtığıport numarasınıbilir. Bu telefonu açıp numarayı(ağda IP adresi) aramanıza ve daha sonra birinin kime bağlanacağınızı(ağda port numarası) sormak için cevaplamasıolayına benzer.

7.5.3.1. connect

Bir istemci bir defa soket oluşturdu mu uzaktaki sistemin belirli bir portuna bağlanmasıgerekir. Bunun için

connect(2)
yi kullanır:

int connect(int s, const struct sockaddr *name,
          socklen_t namelen);

s argümanısoket fonksiyonunda dönen değer olmalıdır.

name
argümanıdaha önceden bahsettiğimiz
sockaddr
yapısına işaretçidir.
namelen
argümanıise sisteme
sockaddr
yapısının kaç bayt uzunluğunda olduğunu belirtir.

Eğer connect başarılıolursa 0 değerini döndürür. Diğer durumlarda -1 döndürür ve hata kodunu errno değişkenine atar.

connect in başarılıolamama durumu için birçok neden vardır. Örneğin bağlanmak isteenen IP adresi belki yoktur, veya kapalıdır, veya sadece yoğundur, veya ilgili portu dinleyen sunucusu yoktur. Veya belki bazıdurumlarda bağlantıyıreddediyordur.

7.5.3.2. İlk İstemcimiz

Şimdi 192.43.244.18 adresinden o anki zamanıalıp standart çıktıya gönderen basit bir istemci yazacak kadar bilgimiz var.

/*
 * daytime.c
 *
 * Programmed by G. Adam Stanislav
 */
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main() {
  register int s;
  register int bytes;
  struct sockaddr_in sa;
  char buffer[BUFSIZ+1];
  if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
    perror("socket");
    return 1;
  }
  bzero(&sa, sizeof sa);
  sa.sin_family = AF_INET;
  sa.sin_port = htons(13);
  sa.sin_addr.s_addr = htonl((((((192 << 8) | 43) << 8) | 244) << 8) | 18);
  if (connect(s, (struct sockaddr *)&sa, sizeof sa) < 0) {
    perror("connect");
    close(s);
    return 2;
  }
  while ((bytes = read(s, buffer, BUFSIZ)) > 0)
    write(1, buffer, bytes);
  close(s);
  return 0;
}

Haydi, editörünüzde kodu yazın, daytime.c olarak kaydedin, daha sonra derleyin ve çalıştırın:

% cc -03 -o daytime daytime.c 

% ./daytime 52079 01-06-19 02:29:25 50 0 1 543.9
          UTC(NIST) * 

% 

Bu durumda tarih 19 Haziran 2001 di ve saat 02:29:25 UTC idi. Doğal olarak sizdeki sonuç farklıolacak.

7.5.3.3. Sunucu Fonksiyonları

Sıradan bir sunucu bağlantıyıbaşlatmaz. Yerine onu arayacak ve ondan hizmet isteyecek bir istemci için bekler. İstemcinin ne zaman arayacağınıve kaç tane istemcinin arayacağınıbilmez. Sadece orada oturur, sabırlıca bekler, biraz zaman geçer, biraz daha geçer, derken sunucu aynıanda onu arayan birçok istemci ile karşıkarşıya kalabilir. Soket arayüzü bunu başarabilmek için 3 basit fonksiyon sunar.

7.5.3.3.1. bind

Portlar telefon hatlarındaki dahiliye numaralarıgibidir: Numarayıgirdikten sonra ilgili kişiye ulaşmak için o kişinin bulunduğu dahiliye numarasınıgirersin.

Mevcut 65536 port vardır, ama bir sunucu bunlardan sadece birinden gelen istekleri işleme alırlar. Bu telefon operatörüne biz şu anda işteyiz ve şu dahiliye numarasından ulaşılabiliriz demek gibidir. Bunun gibi biz bind(2) yi sokete hangi portu servise açacağımızıbelirtmek için kullanırız.

int bind(int s, const struct sockaddr *addr, socklen_t
            addrlen);

addr yapısında portu belirtmekten başka sunucu IP adresini ekleyebilir. Ayrıca IP adresinden bağımsız olarak sadece belirli porta gelen istekleri karşılamak için INADDR_ANY sabitini kullanabilir. Bu sembol benzerleri gibi netinet/in.h da tanımlıdır:

 #define INADDR_ANY
            (u_int32_t)0x00000000

TCP/IP üzerinde tarih(daytime) protokolü için sunucu yazdığımızıdüşünelim. Port 13 ü kullandığınıhatırlayın. socaddr_in yapımız şöyle gözükecektir:

7.5.3.3.2. listen

Ofis telefonu benzetmemize dönersek, hangi dahiliye olacağınızımerkez operatöre söyledikten sonra ofisinize gideceksiniz, ve telefonunuzun bağlantısınıve sesini kontrol edeceksiniz Artıolarak telefon beklemenizin aktif olduğuna emin olacaksınız, dolayısıyla biriyle konuşsanız bile telefonun sesini duyabileceksiniz.

Sunucu da bunların hepsini listen(2) fonksiyonuyla yapar.

 int listen(int s, int backlog);

Burada backlog değişkeni son istekle meşgulken kaç tane isteği kabul edebileceğinizi sokete bildirir. Başka deyişle kuyrukta bekleyen bağlantıların maksimum sayısınıifade eder.

7.5.3.3.3. accept

Telefonun çaldığınıduyduktan sonra telefona bakarak aramayı kabul edersin. Şimdi istemcin ile bir bağlantıoluşturdun. Bu bağlantısen veya istemcin telefonu kapatmadığısürece devam eder.

Sunucu da bağlantıyıaccept(2) fonksiyonu ile kabul eder.

 int accept(int s, struct sockaddr *addr, socklen_t
            *addrlen);

Dikkat edin bu sefer addrlen argümanıbir işaretçi. Bu gereklidir çünkü bu durumda sockaddr_in yapısında olan addr bilgisini dolduracak olan sokettir.

Dönüşdeğeri integer'dır. Gerçekten accept yeni bir soket döndürür. Bu yeni soketi istemci ile iletişime geçmek için kullanacaksın.

Peki eski sokete ne olur? O biz kapatana kadar yeni istekler (listen fonksiyonundaki backlog değişkenini hatırlayın) için dinlemeye devam eder.

Şimdi yeni soket sadece haberleşmeler içindir. Tamamen bağlıdır. Onu ek bağlantılarıkabul etmeye çalışarak tekrar dinleme moduna alamayız.

7.5.3.3.4. İlk Sunucumuz

İlk sunucumuz ilk istemcimizden biraz daha karmaşık olacak: Sadece daha fazla soket fonksiyonu kullanmakla kalmayacağız, aynı zamanda onu daemon olarak yazacağız.

Bu en iyi şekilde portu bind ettikten sonra çoçuk prosesi oluşturmakla olur. Ana proses daha sonra çıkar ve kabuğun(shell) kontrolune girer (veya onu oluşturan programın).

/*
 * daytimed - a port 13 server
 *
 * Programmed by G. Adam Stanislav
 * June 19, 2001
 */
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define BACKLOG 4
int main() {
    register int s, c;
    int b;
    struct sockaddr_in sa;
    time_t t;
    struct tm *tm;
    FILE *client;
    if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket");
        return 1;
    }
    bzero(&sa, sizeof sa);
    sa.sin_family = AF_INET;
    sa.sin_port   = htons(13);
    if (INADDR_ANY)
        sa.sin_addr.s_addr = htonl(INADDR_ANY);
    if (bind(s, (struct sockaddr *)&sa, sizeof sa) < 0) {
        perror("bind");
        return 2;
    }
    switch (fork()) {
        case -1:
            perror("fork");
            return 3;
            break;
        default:
            close(s);
            return 0;
            break;
        case 0:
            break;
    }
    listen(s, BACKLOG);
    for (;;) {
        b = sizeof sa;
        if ((c = accept(s, (struct sockaddr *)&sa, &b)) < 0) {
            perror("daytimed accept");
            return 4;
        }
        if ((client = fdopen(c, "w")) == NULL) {
            perror("daytimed fdopen");
            return 5;
        }
        if ((t = time(NULL)) < 0) {
            perror("daytimed time");
            return 6;
        }
        tm = gmtime(&t);
        fprintf(client, "%.4i-%.2i-%.2iT%.2i:%.2i:%.2iZ\n",
            tm->tm_year + 1900,
            tm->tm_mon + 1,
            tm->tm_mday,
            tm->tm_hour,
            tm->tm_min,
            tm->tm_sec);
        fclose(client);
    }
}

Soketi oluşturarak başladık. Daha sonra sockaddr_in yapısını sa nın içine doldurduk. INADDR_ANY nin if bloğunda kullanılmasına dikkat edin:

if (INADDR_ANY) 

sa.sin_addr.s_addr = htonl(INADDR_ANY);

Değeri 0 dır. bzero ile henüz bütün yapıyısıfırladığımızdan bu değeri tekrar 0 yapmak gereksiz olacaktır. Ama eğer kodumuzu INADDR_ANY nin sıfır olmayacağıbir sisteme port ettiğimizde bu değeri

sa.sin_addr.s_addr
'e atamalıyız. Çoğu modern C derleyicileri INADDR_ANY'nin bir sabit olduğunu kavrayacak kadar zekidir. Dolayısıyla sıfır olduğu sürece if bloğu kod dışında olmuşolacak ve optimize edilmişolacak.

bind'ıbaşarılıbir şekilde çağırdıktan sonra bir daemon olmak için hazır durumdayız: Çocuk prosesi oluşturmak için fork kullandık. Hem anne proseste hem de çocuk proseste "s" değişkeni bizim soketimizdir. Anne prosesi bu sokete ihtiyaç duymayacaktır. Dolayısıyla kapatmak için close fonksiyonunu çağırır, daha sonra kendi annesine başarılıbir şekilde sonlandığınıbildirmek için 0 döndürür.

Bu arada çocuk proses arka planda çalışmaya devam eder. listen'ıçağırır ve backlog'unu 4 e ayarlar. Büyük bir değer kullanmasına gerek yok çünkü daytime istemcilerin sıklıkla başvurduğu bir protokol değildir, ve ayrıca sunucumuz her bir isteği anında cevaplamaktadır.

Son olarak daemon aşağıdaki adımlarıgerçekleyen sonsuz döngüye girer:

  1. accept i çağır. Bir istemci bağlanıncaya kadar burada bekler. Bu noktada, c isimli bağlanan istemciyle haberleşmek için kullanılacak yeni bir soket alır.

  2. Sonra soketi düşük seviye dosya tanımlayıcısından C dili formatında bir dosya işaretçisi döndürmek için fdopen C fonksiyonunu kullanır. Bu daha sonra fprintf'i kullanmasını sağlayacaktır.

  3. Daha sonra zamanıkontrol eder, ve ISO 8601 formatında istemci "dosyasına" basar. Sonra dosyayıkapatmak için fclose u kullanır. Bu otamatik olarak soketi de kapatacaktır.

Bunu genelleştirebiliriz, ve başka sunucular için bir model olarak kullanabiliriz:

Bu akışşemasıbizim daytime sunucumuz gibi bir anda bir istemciye cevap verebilen ardışıl sunucular için güzeldir.

Bu sadece istemci ve sunucu arasında gerçek bir görüşme olmuyorsa mümkündür: Sunucu bir bağlantısaptar saptamaz biraz veri yollar ve bağlantıyıkapatır. Bütün operasyon belki nanosaniyeler cinsinden bir sürede tamamlanır. Bu akışşemasının avantajlarından biri de annenin fork etmesi ve sonra sonlanması hariç geri kalan zamanlarda sadece tek bir prosesin aktif olmasıdır: Bizim sunucumuz dolayısıyla sistem kaynaklarınıve hafızayıfazla kullanmaz.

Akışşemasına daemon yüklenmesini de eklediğimizi not edin. Kendi daemonumuzu yüklememize gerek yok ama burasıprogramın akışında signal handler fonksiyonlarınıkurmamız ve herhangi bir dosyayıaçmamız için güzel bir yerdir.

Literatürde bu akışşemasında kullanılan hemen hemen her şey bazıfarklısunucularda da kullanılabilir. "serve" girdisi istisnadır. Onu siyah kutu olarak düşünüyoruz. O oluşturduğunuz sunucuya özeldir

Bütün protokoller bu kadar basit değildir. Bazıları istemciden bir istek alır, onu cevaplar, sonra aynıistemciden başka bir istek alır. Bundan dolayıtam olarak ne kadar süre istemci ile bağlantıda olacaklarınıbilmezler. Bu tip sunucular genellikle her bir istemci için yeni bir proses başlatırlar. Yeni proses istemcisi ile ilgilenirken, daemon da daha fazla bağlantı için beklemeye devam eder. Şimdi gidin ve yukarıdaki kaynak kodu daytimed.c olarak kaydedin (daemon isimlerinin sonunun d ile bitmesi gelenektir.) Derledikten sonra çalıştırmaya çalışın:

 % ./daytimed bind:
			Permission denied
			%

Ne oldu burada? Tekrar çalıştıracakken, daytime protokolü port 13 ü kullanır. Ama 1024 ün altındaki bütün portlar superuser'lara ayrılmıştır (diğer türlü, herkes ortak olarak kullanılan bir portu sunuyor gibi yapan bir daemonu çalıştırabilir, buda güvenlik açığına neden olacaktır).

Bu sefer superuser olarak tekrar deneyin:

# ./daytimed 
            # 

Ne... Hiçbir şey? Haydi tekrar deneyelim:

# ./daytimed bind:
            Address already in use 
			# 

Her port bir anda sadece bir program tarafından kullanılabilir. İlk girişimimiz gerçekten başarılıydı: Çocuk daemonu başlattıve sessizce sonlandı. Hala çalışıyor ve sen onu öldürürsen ya da sistem çağrılarından birinde hata olursa ya da sistemi yeniden başlatırsan sonlanır.

Güzel, biliyoruz ki arka planda çalışıyor. Ama çalışıyor mu? Uygun bir daytime sunucusu olduğunu nasıl bileceğiz? Çok basit:

% telnet localhost 13
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
2001-06-19T21:04:42Z
Connection closed by foreign host.
%

telnet yeni IPv6 yıdenedi ama başaramadı. IPv4 ile tekrar denedi ve başardı. Dolayısıyla daemon çalışıyor.

Eğer telnet ile başka bir UNIX sistemine erişiminiz varsa onu sunucuya uzaktan erişimi test etmede kullanabilirsiniz. Benim bilgisayarım statik ip ye sahip değil. Dolayısıyla ben şunları yaptım:

% who
whizkid          ttyp0   Jun 19 16:59   (216.127.220.143)
xxx              ttyp1   Jun 19 16:06   (xx.xx.xx.xx)
% telnet 216.127.220.143 13
Trying 216.127.220.143...
Connected to r47.bfm.org.
Escape character is '^]'.
2001-06-19T21:31:11Z
Connection closed by foreign host.
%

Yine çalıştıacaba alan adıkullandığımızda çalışacak
            mı?

% telnet r47.bfm.org 13
Trying 216.127.220.143...
Connected to r47.bfm.org.
Escape character is '^]'.
2001-06-19T21:31:40Z
Connection closed by foreign host.
%

Ayrıca burada telnet "Connection closed by foreign host" (Bağlantıyabancıbilgisayar tarafından kapatıldı) mesajınıbizim daemon'umuz soketi kapattıktan sonra yazar. Bu gösterir ki gerçekten kodumuzdaki fclose(client);beklendiği gibi çalışıyor.