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.