Standart C Programlama Dili


B.5.1. Birinci Örnek—unix Yuvaları

Bu altkısımda verilen örnek programda ağ girdi/çıktısı için kullanılan kütüphane yordamları sadece unix-benzeri sistemlerde kullanılabilir. unix-benzeri sistemlerde kullanılan kütüphane fonksiyonları ve sistem çağrılarının çoğu unistd.h başlık dosyasının programa #include edilmesini gerektirir. Bazı çağrılar başka bazı özel başlık dosyalarını (örneğin, sys/types.h) gerektirebilir. Daha ayrıntılı bilgi için unix programlama el kitaplarına veya unix man komutuna (örneğin, man 2 socket) başvurulabilir.

#include <stdio.h>      /* perror, ...printf */
#include <stdlib.h>     /* exit, EXIT_SUCCESS, EXIT_FAILURE */
#include <string.h>     /* str..., mem... */
#include <signal.h>     /* signal */
#include <time.h>       /* time, localtime, strftime */
#include <unistd.h>     /* gethostname, close */
#include <netdb.h>      /* gethostbyname, struct hostent */
#include <sys/param.h>  /* MAXHOSTNAMELEN */
#include <sys/types.h>
#include <sys/socket.h> /* socket, bind, listen, accept */
#include <netinet/in.h> /* struct sockaddr_in, htons */
#include <arpa/inet.h>  /* htons, inet_ntoa */

#define PROGISMI  "dtp"
#define KULLANIM  ("Kullanim: " PROGISMI " [host_ismi [port_no]]\n")
#define HATA      (-1)

char * tarih_saat (void)
{
  static char cikti[30];
  time_t z = time (NULL);
  struct tm * yzg = localtime (&z);
  strftime (cikti, sizeof(cikti)-1, "%Y-%m-%d %H:%M:%S %Z", yzg);
  return cikti;
} /* tarih_saat */

int dinleme_icin_hazirlan (char * host, unsigned short port)
 /* Verilen port'u dinlemeye başla ve açılan soketi döndür. **/
{
  int s;
  struct hostent * hp;
  struct sockaddr_in sin = { 0 };
  if (host[0]=='\0') {
    if (gethostname (host, MAXHOSTNAMELEN)) {
      perror ("gethostname");
      exit (EXIT_FAILURE);
    }
  }
  hp = gethostbyname (host);  /* Host ismini ağ numarasına çevir. */
  if (hp == NULL) {
    fprintf (stderr, "%s: Host adresi bilinmiyor (%d).\n", host, h_errno);
    exit (EXIT_FAILURE);
  }
  s = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);  /* IPv4 */
  if (s == HATA) {
    perror ("socket");
    exit (EXIT_FAILURE);
  }
  sin.sin_port = htons (port);
  sin.sin_family = AF_INET;
  memcpy (&sin.sin_addr, hp->h_addr, hp->h_length);
  if (bind (s, (struct sockaddr *)&sin, sizeof(sin)) == HATA) {
    perror ("bind");
    exit (EXIT_FAILURE);
  }
  if (listen (s, 15 /* backlog */) == HATA) {
    perror ("listen");
    exit (EXIT_FAILURE);
  }
  fprintf (stderr, "%s - %s programi %s:%d uzerinde basladi.\n",
    tarih_saat(), PROGISMI, host, port);
  return s;
} /* dinleme_icin_hazirlan */

void yanit_gonder (int s, struct in_addr ist_addr)
{
  char * yanit = tarih_saat ();
  size_t yboy = strlen (yanit);
  send (s, yanit, yboy, 0);
  send (s, "\r\n", 2, 0);
  fprintf (stderr, "%s - [%s] - \"%s\" %zu\n", tarih_saat(),
    inet_ntoa(ist_addr), yanit, yboy);
} /* yanit_gonder */

int sock;       /* sinyal_yakala ve main tarafından kullanılıyor */

void sinyal_yakala (int sig)
{
  signal(sig, SIG_IGN);
  fprintf (stderr, "%s - %s programi %d sinyali alarak sonlandi.\n",
    tarih_saat(), PROGISMI, sig);
  close (sock);
  exit (EXIT_SUCCESS);
} /* sinyal_yakala */

int main (int argc, char **argv)
{
  char hostismi [MAXHOSTNAMELEN] = "";
  unsigned short port = 1313; /* varsayılan TCP dinleme portu */
  int khata = 0;
  if (argc > 1 && sscanf(argv[1],"%s",hostismi) != 1)
    khata = 1;
  if (argc > 2 && sscanf(argv[2],"%hu",&port) != 1)
    khata = 1;
  if (argc > 3)
    khata = 1;
  if (khata) {
    fprintf (stderr, KULLANIM);
    return EXIT_FAILURE;
  }
  sock = dinleme_icin_hazirlan (hostismi, port);
  signal (SIGTERM, sinyal_yakala); /* kill için */
  signal (SIGINT, sinyal_yakala);  /* Ctrl+C için */
  while (1) {
    struct sockaddr_in fsin;
    int ns;
    socklen_t flen = sizeof(fsin);
    ns = accept (sock, (struct sockaddr *)&fsin, &flen);
    if (ns == HATA) {
      perror ("accept");
      close (sock);
      return EXIT_FAILURE;
    }
    yanit_gonder (ns, fsin.sin_addr);
    if (close (ns) == HATA)
      perror ("close");
  }
} /* main */

Yukarıda verilen program unix yuvaları kullandığı için Standart C programı değildir. Bu örnekte ağ yuvalarını işleyen standart-dışı fonksiyon ve başlık dosyaları kullanmaktayız. Dikkat edeceğiniz üzere, programın başında #include edilen ilk beş başlık dosyası Standart C’ye aittir. Geri kalan başlık dosyaları ise birçok POSIX ortamlarda (yani unix ve Ek D’de göreceğimiz Cygwin ortamlarında) standartlaşmış dosyalardır. Bu dosyaların hangi fonksiyon ve veri yapı tanımları için kullanıldığı, #include satırının sonunda, açıklama kısmında verilmeye çalışılmıştır. Bu programı derlerken, normalde kullandığımız -std=c17 komut satırı seçeneği yerine -std=gnu17 seçeneğinin kullanılması önerilir.

Burada verilen programın işlevi tipik bir Daytime Protokolüne uygun davranan bir ağ servisine denk gelmektedir ve en basit ağ yazılımı olarak değerlendirilebilir. RFC 867’de detayları verilen böyle bir servisin yaptığı şey çok basittir: TCP tabanlı bir daytime servisi 13 nolu portu dinler. Bu porta bir bağlantı isteği geldiğinde, bağlantı ile birlikte servise iletilen veriye bakmaksızın, geriye günün tarih ve saatini bir ASCII karakter dizisi şeklinde gönderir ve bağlantıyı hemen kapatır. Gönderilecek karakter dizisinin biçimi belirtilmemiştir, ancak sistem tarih ve saatini içeren ve \r (satırbaşı) ile \n (satır ilerletme) karakterleri ile sonlanan tek bir satırdan oluşması beklenir. İnternet üzerindeki böyle bir servise bağlanmak için, İnternet erişimi olan bir sistemde, örneğin telnet time.nist.gov 13 komutunu girebilirsiniz.

Yukarıdaki örnekte, varsayılan TCP dinleme portu olarak 13 yerine 1313 seçilmiştir. Bunun nedeni unix-benzeri sistemlerde böyle bir programı normal bir kullanıcı ile çalıştırdığınızda, 1024’ten düşük portları dinleme yetkisi olmayacağıdır. Program birinci argüman olarak dinleme yapacağı host ismini veya IP adresini bekler; ikinci argüman olarak da varsayılandan farklı bir TCP port numarası verilebilir. Bu argümanların varsayılan değerleri, sistemin host isminden elde edilen yerel IP adresi ve 1313 TCP portudur. Bir sistemde birden fazla yerel IP adresi olabilir. Bu program AF_INET (yani IPv4) ağ etki alanını kullandığından, en azından 127.0.0.1 adresini kullanabilirsiniz.

Programı yukarıda anlatıldığı gibi derledikten sonra, argümanlı veya argümansız çalıştırabilirsiniz. Programın servisini test etmek için, başka bir pencerede telnet localhost 1313 veya başka bir bilgisayarda telnet servis_ip_addresi 1313 komutunu kullanabilirsiniz. Programı sonlandırmak için, çalıştığı pencerede Ctrl+C veya başka bir pencerede kill process_no komutunu girebilirsiniz.

Programın detaylı açıklaması bu ekin kapsamı dışındadır. Ancak, programı dikkatlice inceleyip anlamaya çalışmanızı öneririz. Özellikle, programı derleyebileceğiniz bir ortam (Linux veya Cygwin) temin edip denemeniz yararlı olacaktır.