Standart C Programlama Dili


B.5.2. İkinci Örnek—unix System V Mesaj Kuyrukları

Bu altkısımda unix System V mesaj kuyruklarına bakacağız. Eski olmalarına rağmen, bir unix sistemde çalışan süreçler arasında iletişim için yaygın olarak kullanılan bu System V iletişim yöntemleri arasında, mesaj kuyrukları dışında, semaforlar ve paylaşılan bellek de bulunmaktadır.

#include <stdio.h>      /* perror, printf, fgets */
#include <stdlib.h>     /* exit */
#include <string.h>     /* strlen */
#include <signal.h>     /* signal */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>    /* msg... */
#include <unistd.h>     /* usleep, fork */
#include <sys/wait.h>   /* waitpid */

#define KUYRUK ((key_t)0x1234)
#define MAKSM  81
#define ISTEK  1
#define HATA   (-1)

struct kmesaji {
  long tip;
  long yanitno;
  char mesaj[MAKSM];
};

#define MESUZ  (sizeof(struct kmesaji)-sizeof(long))

void istemci (void)
{
  struct kmesaji km;
  int mid = msgget(KUYRUK,0);
  if (mid == HATA) {
    perror ("msgget");
    exit (EXIT_FAILURE);
  }
  printf ("Isteginizi girin: ");
  while (fgets (km.mesaj,MAKSM,stdin) != NULL) {
    char *ss = km.mesaj+strlen(km.mesaj)-1;
    if (*ss == '\n')
      *ss = '\0';
    km.tip = ISTEK;
    km.yanitno = (long)getpid();
    if (msgsnd (mid,&km,MESUZ,IPC_NOWAIT) == HATA) {
      perror ("msgsnd");
      exit (EXIT_FAILURE);
    }
    printf ("%s ==> ", km.mesaj);
    if (msgrcv (mid,&km,MESUZ,km.yanitno,0) == HATA) {
      perror ("msgrcv");
      exit (EXIT_FAILURE);
    }
    printf ("%s\nBir sonraki isteginizi girin: ", km.mesaj);
  }
} /* istemci */

void terscevir (char * str)
{
  char *b, *s;
  for (b=str, s=str+strlen(str)-1; b<s; b++, s--) {
    char t = *b;
    *b = *s;
    *s = t;
    usleep (500*1000);
  }
} /* terscevir */

void sinyal_yakala (int sig)
{
  signal (sig, SIG_IGN);
  printf ("Sunucu bitti.\n");
  exit (EXIT_SUCCESS);
} /* sinyal_yakala */

int sunucu (void)
{
  struct kmesaji km;
  int mid = msgget(KUYRUK,0640|IPC_CREAT);
  if (mid == HATA) {
    perror ("msgget");
    exit (EXIT_FAILURE);
  }
  signal (SIGTERM, sinyal_yakala); /* kill için */
  signal (SIGINT, sinyal_yakala);  /* Ctrl+C için */
  printf ("Sunucu basladi.\n");
  while (1) {
    int fhata;
    if (msgrcv (mid,&km,MESUZ,ISTEK,0) == HATA) {
      perror ("msgrcv");
      exit (EXIT_FAILURE);
    }
    while (waitpid (-1,NULL,WNOHANG) > 0)
      ; /* bitmiş olan alt süreçler için */
    fhata = fork ();
    if (fhata == HATA) {
      perror ("fork");
      exit (EXIT_FAILURE);
    } else if (fhata == 0) {    /* alt süreç */
      printf ("%ld : %s ==> ", km.yanitno, km.mesaj);
      terscevir (km.mesaj);
      km.tip = km.yanitno;
      if (msgsnd (mid,&km,MESUZ,IPC_NOWAIT) == HATA) {
        perror ("msgsnd");
        exit (EXIT_FAILURE);
      }
      printf ("%s\n", km.mesaj);
      exit (EXIT_SUCCESS);
    }
  }
} /* sunucu */

int main (int argc, char **argv)
{
  int khata = 0;
  if (argc != 2)
    khata = 1;
  else
    switch (argv[1][0]) {
      case 'i' :
        istemci ();
        break;
      case 's' :
        sunucu ();
        break;
      case 't' :
        if (msgctl (msgget(KUYRUK,0),IPC_RMID,NULL) == HATA) {
          perror ("msgctl");
          return EXIT_FAILURE;
        }
        break;
      default :
        khata = 1;
    }
  if (khata) {
    fprintf (stderr, "Kullanim: kuyruk i|s|t\n");
    return EXIT_FAILURE;
  }
  return EXIT_SUCCESS;
} /* main */

Yukarıdaki program, çalıştırılırken komut satırında verilen argümana göre istemci (i) veya sunucu (s) modunda çalışabilir. Bir üçüncü seçenek (t) daha önceden yaratılmış mesaj kuyruğunu sistemden tamamen silmek için kullanılır.

İstemci modunda çalışan bir program, kullanıcıdan aldığı bir karakter dizisini kuyruğa atar ve sunucu program tarafından işlenip, yanıtının geri gönderilmesini bekler. Yanıt olarak gelen karakter dizisini kullanıcıya görüntüler. Aynı anda birden fazla istemci program sistemde (paralel olarak) çalıştırılabilir. İsteklerin karışmaması için, istemci program kendine ait süreç numarasını mesajın içine (yanitno üyesine) ekler; sunucu da yanıt mesajını kuyruğa atarken mesajın tipi olarak bu numarayı kullanır. Bu şekilde yanıt mesajı ilgili istemci tarafından okunabilir.

Programı sunucu modunda çalıştırdığımızda, kuyruktan ISTEK tipindeki mesajları alır, mesaj isimli üyedeki karakter dizisini ters çevirir ve geri yollar. Mesajı geri gönderirken, mesaj tipi olarak yanitno üyesindeki sayıyı kullanır. Bu şekilde, aynı anda gelen ve işlenen mesajların yanıtları yanlış istemciye gitmez. Dikkat ederseniz, sunucunun yaptığı terscevir işlemi karakter dizisinin büyüklüğü ile doğru orantılı olarak zaman alan bir işlemdir. Bu fonksiyon içindeki her bir usleep çağrısı sunucunun yarım saniye kadar beklemesini sağlar ve ters çevrilecek karakter dizisi ne kadar uzunsa bekleme süresi de doğru orantılı olarak artar. Bu yüzden, uzun mesajların yanıtlarının geri gönderilmesi daha uzun sürer. Sunucu bir mesajı işlerken, başka istemcilerden gelen diğer mesajların kuyrukta beklememesi için fork sistem çağrısı yapar. Bu şekilde, program ikiye dallanmış olur; yani aynı anda programın bir kopyası daha çalışmaya başlar. fork işleminden 0 dönüş değeri alan sürece çocuk (veya alt) süreç adı verilir. Bu süreç mesajın dönüşüm ve geri gönderme işlemini yaptıktan sonra kendini sonlandırır (Satır 102). Ana (yada üst) süreç ise çocuk süreçten bağımsız olarak döngüye devam eder ve hemen yeni bir mesaj okumaya geçer. Bu şekilde tek bir sunucu birden fazla istemci tarafından gönderilen mesajlara zamanında yanıt gönderebilir.

Programı -std=gnu17 seçeneği ile örneğin kuyruk isminde yürütülebilir bir dosyaya derledikten sonra, üç adet komut satırı penceresi açın. Bir pencerede ./kuyruk s komutunu girerek programı sunucu modunda çalıştırın. Diğer iki pencerede ise ./kuyruk i komutlarını girerek iki adet istemci başlatın. İstemcilerde arka arkaya uzun ve kısa karakter dizileri girerek sunucunun her iki istemciye gecikmeden yanıt verdiğini test edip görün. Dilediğiniz sayıda istemci çalıştırıp testlere devam edebilirsiniz. Hatta, sunucu programlarından da birden fazla çalıştırabilirsiniz. Sunucular bir şekilde kendilerine düşen mesajları yanıtlayacaktır ve işleyişte herhangi bir değişiklik olmayacaktır. Ancak, sunucu adedini artırmanın performansa herhangi bir katkısı olmayacağına da dikkat edin. Zira, önceki paragrafta anlatılan çalışma yöntemi sayesinde, tek bir sunucu bile tüm mesajlara zamanında yanıt verebilir.

Programın detaylı açıklamasına girmeden sadece birkaç ipucu vereceğiz: Mesaj kuyruklarına bağlanmak için msgget çağrısının; belirli bir mesaj kuyruğundan, belirli bir tipte bir mesaj almak için msgrcv çağrısının; belirli bir mesaj kuyruğuna mesaj göndermek için de msgsnd çağrısının kullanıldığında dikkat edin. Önceki altkısımda da kullanıldığı gibi, signal çağrısını ise, sonsuz döngüde çalışan sunucuyu Ctrl+C veya kill komutu ile sonlandırırken normal bir sonlanma için kullanıyoruz. Sistemsel bir gereklilik olarak; yaptığı her fork işleminden sonra, ana sürecin her bir alt süreç için bir wait... işlemi yapması gerekir. Satır 87’deki while (waitpid... işlemi bu amaç içindir.