> Boru Haberleşmeleri: İki ana gruba ayrılırlar. Bunlar İsimsiz("Unnamed/Anonymus") ve İsimli("Named/FIFO") Boru Haberleşmeleridir. İsimsiz Boru Haberleşmesi "parent" proses ve "child" proses arasındayken, İsimli Boru Haberleşmesi herhangi iki proses arasında kullanılır. Pekiyi nedir bu boru? Boru aslında bir ortam olup, iki proses arasındaki bir kuyruk sistemidir. Hangi sırada baytlar yazılmışsa, o sırada okunurlar. Yani FIFO biçimindedir. Arka planda tabii fiziksel bir sayfa vardır. Boru Haberleşmesi iki ana ayak üzerine kuruludur. Bir ayak boru sisteminin oluşturulması, diğer ayak ise yazma ve okuma işlemlerinde senkronizasyonun sağlanması üzerinedir. Çünkü okuma ve yazma yaparken "ezme" diye tabir edilen "overwrite" oluşmaması gerekmektedir. Açık ara en çok kullanılan yöntem, boru haberleşmesi yöntemidir. Boruya yazmak ve borudan okumak için oluşturulan özel fonksiyonlar yoktur. Dosya işlemlerinde kullanılan "read" ve "write" fonksiyonları kullanılmaktadır. Özetle borular birer dosya gibi ele alınmaktadır. Boruların belli bir uzunlukları vardır. Varsayılan uzunluk 4096 bayt iken, günümüzde Linux sistemlerinde 65536 bayta yükseltmiştir. Bir sayfa uzunluğunu 4096 bayt ise 65536 bayt ise 16 sayfaya denk gelmektedir. UNIX/Linux sistemlerde borular tek yönlü, Windows sistemlerde ise iki yönlü olarak kullanılmaktadır. Pekiyi boruların tek yönlü olması ne demektir? Baştan bizler proseslere roller atıyoruz; hangisinin yazacağını, hangisinin okuyacağına dair. Eğer yazan taraf okuma yaparsa, kendi yazdığını okuyacaktır. Fakat iki yönlü borularda yazan taraf okuma da yapabilir. Dolayısıyla karşılıklı okuma yapmak için iki boru kullanmamız gerekiyor. >> Boruya yazma işlemi: Daha önce de belirtildiği gibi "write" fonksiyonu kullanılır ve argüman olarak geçilen "fd" değişkeni borulara ilişkindir. "write" fonksiyonu yazabildiği kadar karakter ile değil, yazması gereken bütün karakterleri yazdıktan sonra geri döner. Pekiyi yazma işlemi yaparken boru dolarsa ne olur? Bu durumda ilgili boru, karşı taraftaki "read" yapana kadar, "write" fonksiyonunca bloke edilir. "read" yapılıp da ilgili boruda yer açılırsa bloke kaldırılır. Boruların kapasitesi de Linux sistemlerde 65K iken bazı sistemlerde 4K bayt büyüklüğündedir. Fakat FARKLI PROSESLER İLE AYNI boruya yazma yaparken, proseslerden birisinin tek kalemde yazacağı uzunluk "PIPE_BUF" sembolik sabitinden daha fazlaysa, İÇ İÇE GEÇME MEYDANA GELEBİLİR. Dolayısıyla bir boruya yazarken "PIPE_BUF" sembolik sabitinden daha küçük boyutlarda yazma yapmalıyız. Öte yandan "write" fonksiyonu ile boruya yazma yaparken bir sinyal alırsak, "write" fonksiyonu "-1" ile geri döner ve boruya hiç bir şey yazmaz, başarısız olur. Dolayısıyla "errno" değişkeni de "EINTR" değerini alır. Eskiden "Partial Write" yapmaktaydı yani yazabildiği kadarını yazıyordu. Fakat artık hiç yazmadan, direkt olarak "-1" ile geri dönmektedir. Benzer biçimde boruda yeteri kadar yer yoksa, bu durumda ilgili "thread" blokede bekleyecektir, ve o anda bir sinyal gelirse yine başarısız olup "-1" ile geri dönecektir. Yine boruya BİR ŞEY YAZMAYACAKTIR. Özetle; blokeli boru yazma işlemlerinde, POSIX standartlarına göre, Kısmi Yazım yapılmamaktadır. Öte yandan, eğer borunun toplam kapasitesinden daha büyük bir değeri yazmaya kalkarsak ya yazabildiği kadarı ile geri dönülür ya da "errno" değişkeni uygun bir değere çekilip yazma işlemi direkt engellenebilir. Örneğin, Linux sistemlerde borunun toplam uzunluğu 64K fakat "PIPE_BUF" 4K uzunlukta. Son olarak borulara yazma işlemi yine atomik düzeydedir. >> Borudan okuma işlemi: Boruya yazma işleminden farklıdır. Örneğin, borudan 50 byte okumak isteyelim fakat boruda 10 bayt olsun. Bu 10 bayt okunur ve bu değer ile geri dönülür. Eğer bu aşamada tekrardan okuma yapmak istersek, ilgili "thread" bloke edilecektir. En az 1 bayt'lık bilgi boruda olduğunda bloke kalkar. Pekiyi boru haberleşmesi nasıl sonlanır? İlkin yazma yapan boruyu sonlandırır. Daha sonra okuma yapan taraf okuma yapmaya devam eder. Ta ki boruda okunacak bayt kalmayana denk. Normal şartlarda okunacak bayt kalmadığında ilgili "thread" bloke edilmekteydi fakat burada yazan taraf da boruyu kapattığı için "read" fonksiyonu "0" ile geri döner. Daha sonra okuyan taraf da boruyu kapatır. Böylelikle boru haberleşmesi sonlanmış olur. >> İsimsiz Boru Haberleşmeleri: Sıradan iki proses arasında yapamadığımız ama alt ve üst proses arasında yapabildiğimiz haberleşme yöntemidir. Böylesi bir haberleşmenin sağlanabilmesi için aşağıdaki adımların atılması gerekmektedir: -> Üst proses, henüz "fork" fonksiyonunu çağırmadan evvel, "pipe" isimli POSIX fonksiyonunu çağırmalı ve isimsiz bir boru hayata getirmelidir. "pipe" fonksiyonu aşağıdaki imza yapısına sahiptir; #include int pipe(int fildes[2]); Fonksiyon argüman olarak elemanları "int" türden olan bir dizinin başlangıç adresini istemektedir. Aldığı bu dizinin ilk iki elemanına "fd" değişkenleri yerleştirmektedir. Yani aslında Dosya Betimleyici Tablosundaki "file" türden nesnelerin indislerini argüman olarak aldığı diziye yerleştiriyor fakat buradaki "file" türden nesneler borular ile ilişkilidir. Bu dizinin 0. indisi borudan okuma yapmak, 1. indisi ise boruya yazma yapmak için kullanılır. Fonksiyon, başarısızlık durumunda "-1" ile geri döner ve "errno" değişkeni uygun bir değere çekilir. Başarı durumunda "0" ile geri döner. -> Boru hayata geldikten sonra "fork" fonksiyon çağrısı yapılır. Artık bu aşamada üst ve alt prosesler aynı "fd" değişkenleri ile aynı "file" türden nesneleri gösterir durumdadır. -> Bu aşamada alt ve üst proses ikişer adet "fd" dosya betimleyicilerine sahiptir. Proseslere görev atamalarını yaptıktan sonra kullanılmayan dosya betimleyicilerini kapatmamız gerekmektedir. Bunun sebebine ileride değineceğiz. Bizler üst prosese yazma görevini, alt prosese ise okuma görevini verelim. Görev atamalarından sonra kullanılmayan "fd" değişkenlerini kapatmalıyız. * Örnek 1, #include "stdio.h" #include #include void exit_sys(const char*); int main(int argc, char** argv) { /* * İlk aşamada boru hayata getirilir. */ int fdpipe[2]; if(pipe(fdpipe) == -1) exit_sys("pipe"); /* * İkinci aşamada "fork" yapılır. */ int pid; if((pid = fork()) == -1) exit_sys("fork"); /* * Üçüncü aşamada alt ve üst prosese * görev dağılımı yapılır. */ if(pid != 0) { /* * Üst prosese yazma görevini atayalım. * Dolayısıyla okumak için kullanılan "fd" * değişkenini geri vermemiz gerekiyor. */ close(fdpipe[0]); //... } else { /* * Alt prosese okuma görevini atayalım. * Dolayısıyla yazma için kullanılan "fd" * değişkenini geri vermemiz gerekiyor. */ close(fdpipe[1]); //... } puts("Ok!.."); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } -> Artık okuma ve yazma işlemleri "read" ve "write" POSIX fonksiyonları ile gerçekleştirilebilir. Bu işlemlerden sonra borunun ilk olarak yazan taraf tarafınca kapatılması gerekiyor. Daha sonra boruda kalanlar da okunur. En son okuyan taraf boruyu kapatır. Eğer boru haberleşmesinde borunun kapatılması ilk olarak okuyan tarafından kapatılırsa ve yazan taraf boruya yazmaya devam ederse "SIGPIPE" isimli bir sinyal oluşur. Bu sinyal de prosesin sonlanmasına yol açar. Yani yazma tarafı kapatılan bir borudan okuma yapmakta bir sorun yoktur fakat okuma tarafı kapatılan bir boruya yazma işlemi yapmakta SORUN VARDIR. * Örnek 1, #include "stdio.h" #include #include #include void exit_sys(const char*); int main(int argc, char** argv) { /* # OUTPUT # 0 1 2 3 4 5 6 7 8 9 Ok!.. Ok!.. */ /* * İlk aşamada boru hayata getirilir. */ int fdpipe[2]; if(pipe(fdpipe) == -1) exit_sys("pipe"); /* * İkinci aşamada "fork" yapılır. */ int pid; if((pid = fork()) == -1) exit_sys("fork"); /* * Üçüncü aşamada alt ve üst prosese * görev dağılımı yapılır. */ if(pid != 0) { /* * Üst prosese yazma görevini atayalım. * Dolayısıyla okumak için kullanılan "fd" * değişkenini geri vermemiz gerekiyor. */ close(fdpipe[0]); /* * Dördüncü aşamada artık okuma ve yazma işlemlerini * gerçekleştireceğiz. Burada bizler boruya "i" * değişkeninin adresinden itibaren, "int" büyüklüğünde, * 10 adet veriyi boruya yazıyoruz. Çünkü burada çalışan * mekanizma aşağı yukarı şu şekildedir; * "i" değişkeninin değerini bizler boruya yazıyoruz. * Alt proses de borudan okuma yapıp, okuduklarını başka * bir değişkenin adresine yazıyor. */ for(int i = 0; i < 10; ++i) if((write(fdpipe[1], &i, sizeof(int))) == -1) exit_sys("write"); /* * Boruyu ilk kapatan yazan taraf olmalıdır. */ close(fdpipe[1]); /* * Tabii yazma tarafını kapattıktan sonra * alt prosesi YİNE BEKLEMELİYİZ. */ if(wait(NULL) == -1) exit_sys("wait"); } else { /* * Alt prosese okuma görevini atayalım. * Dolayısıyla yazma için kullanılan "fd" * değişkenini geri vermemiz gerekiyor. */ close(fdpipe[1]); /* * Dördüncü aşamada artık okuma ve yazma işlemlerini * gerçekleştireceğiz. Burada bizler borudan okuma * yapacağız. Her okumada da "int" büyüklüğünde bir * bayt okunup, "read_value" değişkeninin adresine * yazılacaktır. Aksi bir durum olmadıkça, okunan * değer kadar "read" fonksiyonu geri dönecektir. * Normal şartlarda borunun ilk olarak yazan taraf * tarafından kapatılması gerekiyor. Eğer böyleyse * ve boruda da okunacak bir şey kalmadıysa, "read" * fonksiyonu "0" ile geri dönecektir. */ int read_value; int read_result; while((read_result = read(fdpipe[0], &read_value, sizeof(int))) > 0) { printf("%d ", read_value); /* * Yine ekrana basarken "\n" kullanmadığımız için * "fflush" çağrısı yapmalıyız. */ fflush(stdout); } /* * Eğer "-1" ile geri dönmüş ise programı direkt * sonlandırmalıyız. */ if(read_result == -1) exit_sys("read"); printf("\n"); /* * Yazma tarafı kapatıldıktan sonra okuma tarafı * kapatılmalıdır. */ close(fdpipe[0]); } puts("Ok!.."); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } Aşağıda kullanıma ilişkin bir örnek verilmiştir: * Örnek 1, #include "stdio.h" #include #include void exit_sys(const char*); int main(int argc, char** argv) { /* * İlk aşamada boru hayata getirilir. */ int fdpipe[2]; if(pipe(fdpipe) == -1) exit_sys("pipe"); /* * İkinci aşamada "fork" yapılır. */ int pid; if((pid = fork()) == -1) exit_sys("fork"); if(pid != 0) { /* Parent process. */ } else { /* Child process. */ } puts("Ok!.."); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } Pekala bizler alt prosesi "sleep" fonksiyonu ile bekletsek ne olur? Alt proses ilgili süre boyunca bloke olacaktır. Aynı zamanda da üst proses boruya yazacaktır. Boru dolduğunda üst proses de bloke olacaktır. Alt prosesin blokesi kalktığında borudan okumaya başlayacaktır. Boruda yer açıldığı için üst prosesin de blokesi kalkacaktır. Eğer bizler üst prosesi "sleep" fonksiyonu ile bekletseydik ne olacaktı? Boruya ilgili süre boyunca bir şey yazılmayacağı için alt proses beklemede kalacak. İlgili süre sonunda üst proses yazmaya başlayacak ve alt proses de okumaya. Buradan da diyebiliriz ki senkronizasyon kendi içerisinde sağlanmaktadır. Eğer üst proses abnormal bir biçimde sonlanırsa, bütün "fd" değişkenleri de kapatılacağı için, alt proses boruda kalanları okuyacaktır. Eğer yazma sırasında bir sinyal gelirse, "write" fonksiyonu ilgili baytların tamamını yazamayabilir. Bu duruma da "partial write" denmektedir. Bir sinyal gelme olasılığının olduğu durumlarda, "write" fonksiyonunun geri dönüş değerini "-1" ile birlikte "sizeof(int)" ile de karşılaştırmalıyız. Aşağıdaki gibi bir kodu kullanabiliriz: //... int result; if((result = write(fdpipe[1], &i, sizeof(int))) == -1) exit_sys("write"); if(result != sizeof(int)) { fprintf(stderr, "partial write error!..\n"); exit(EXIT_FAILURE); } Fakat bir prosese ya "root" proses sinyal gönderebilir ya bir kullanıcı terminal üzerinden bir sinyal gönderebilir ya da ilgili proses ile aynı Kullanıcı ID değerine sahip başka bir proses sinyal gönderebilir. Öte yandan alt ve üst prosesin kullanmadıkları dosya betimleyicisi "fd" değişkenlerini kapatmaları gerektiğini söylemiştik. Yani okuma yapacak olan proses, yazmaya ait olanı; yazma yapacak olan da okumaya ait olanı. Bunun iki sebebi vardır. İlki, "read" fonksiyonu "0" ile geri dönebilmesi için boruya yazma potansiyeli olan hiç bir "fd" değişkeni OLMAMASI GEREKMEKTE ve borunun da boş olması gerekmektedir. Eğer en az bir tane yazma potansiyeline sahip "fd" değişkeni varsa, "read" fonksiyonu "0" ile geri dönmeyecektir. Böylesi bir durumda haberleşmenin sonlandırılması sorunlu hale gelecektir. Tabii okuma potansiyeli olan "fd" değişkenlerinin birden fazla olması, yukarıdaki gibi bir sonuç doğurmaz sadece dosya betimleyici tablosunda fazladan betimleyici tutmuş oluruz. İsrafa ne gerek var. Özetle; -> OKUMA YAPAN TARAFIN YAZMA BETİMLEYİCİSİNİ KAPATMAMASI KRİTİK ÖNEMLİ BİR ŞEY. -> YAZMA YAPAN TARAFIN OKUMA BETİMLEYİCİSİNİ KAPATMAMASI O KADAR DA KRİTİK ÖNEMLİ BİR ŞEY DEĞİL, TUTUMLULUK AÇISINDAN KAPATMASI GEREKİYOR. Pekiyi böylesine bir haberleşme aracına neden ihtiyaç duyalım? Sonuç olarak alt ve üst prosesin kodlarını bizler yazmaktayız. "thread" kavramı işletim sistemlerine girmeden evvel bir işi hızlandırmak için alt prosesler oluşturulur ve bu alt prosesleri birlikte çalıştırarak bir işi daha erken bitirmeye çalışırdık. Prosesler arasında da bir izolasyon olduğu için, iki prosesin haberleşmesi için böylesi bir teknik kullanılmaktaydı. Fakat "thread" ler hayatımıza girdikten sonra artık böylesi bir kullanıma artık gerek kalmadı. Artık "thread" ler daha sık kullanılmaktadır. Bizler buraya kadarki kısımda sadece "fork" işlemi uyguladık. Peki "fork" ve "exec" işlemlerinden sonra da yine prosesler arası haberleşme sağlanabilir mi? Bunun sağlanabilmesi için "exec" yapan alt prosesin borulara ilişkin "fd" değişkenlerini bilmesi gerekmektedir. Anımsanacağız üzere dosya betimleyici tablosunun ilk üç indisinde bulunan "stdin", "stdout" ve "stderr" isimli dosyalar sabitti. Bu üç indisi kullanarak bizler yine prosesler arası haberleşmeyi sağlayabiliriz. Örneğin, aşağıdaki "shell" kabuk komutunu ele alalım: a | b Burada "a" prosesinin "stdout" a yazdıklarını, "b" prosesi "stdin" den okuyordu. Yani arka planda "a" prosesinin "stdout" dosyası bir boruya yönlendirildiği için, o boruya yazmaktadır. Benzer şekilde "b" prosesinin "stdin" i de boruya yönlendirildiği için, o da borudan okuma yapmaktadır. Arka planda "pipe", "fork", "exec" ve "dup2" gibi fonksiyonlar koşmaktadır. Aşağıda bu işlevi gören bir program kodları mevcuttur. * Örnek 1, #include "stdio.h" #include #include #include #include #define MAX_ARGV 1024 void exit_sys(const char*); int main(int argc, char** argv) { /* # Command Line Arguments # "ls -l | wc" */ /* # OUTPUT # 3 20 110 */ /* * Programın devam edebilmesi için sadece 1 adet * komut satırı argümanın sahip olması gerekmektedir. */ if(2 != argc) { fprintf(stderr, "wrong number of arguments!...\n"); exit(EXIT_FAILURE); } /* * Girilen komut satırı argümanı içerisinde "|" karakteri * aranır. Bulunması halinde yerine "\0" yazılır ki ayrıştırma * rahat yapılsın. */ char* ppos; if((ppos = strchr(argv[1], '|')) == NULL) { fprintf(stderr, "invalid argument: %s\n", argv[1]); exit(EXIT_FAILURE); } *ppos = '\0'; int n; char* cmdl[MAX_ARGV + 1], *cmdr[MAX_ARGV + 1]; char* str; /* * Girilen komut satırı argümanları ayrıştırılır. Yazının ortasında * "\0" karakteri olduğu için, yazının başından bu karakter görülene * kadar döngü devam eder ve her bir komut da "cmdl" dizisine yazılır. */ n = 0; for(str = strtok(argv[1], " \t"); str != NULL; str = strtok(NULL, " \t")) cmdl[n++] = str; cmdl[n] = NULL; /* * Girilen komut satırı argümanları ayrıştırılır. Yazının ortasında * "\0" karakteri olduğu için, bu noktadan yazının sonuna kadar * kadar döngü devam eder ve her bir komut da "cmdr" dizisine yazılır. */ n = 0; for(str = strtok(ppos + 1, " \t"); str != NULL; str = strtok(NULL, " \t")) cmdr[n++] = str; cmdr[n] = NULL; int pipefds[2]; /* Boruyu hayata getirmiş olduk. */ if(pipe(pipefds) == -1) exit_sys("pipe"); pid_t pidl; /* * İlk alt prosesimizi hayata getiriyoruz. */ if((pidl = fork()) == -1) exit_sys("fork"); /* * İlk alt process "exec" çağrısı yapacaktır. Dolayısıyla * onun akışı bu "if" bloğunun aşağısına akmayacaktır. */ if(pidl == 0) { /* * Bu prosese yazma görevi verdik. Gereksiz dosya * betimleyicilerini kapatmaları gerekmektedir. */ close(pipefds[0]); /* * Bu alt prosesin "stdout" dosyasını boruya * yönlendirelim. Artık ekrana değil, boruya * yazacaktır. Başarısızlık durumunda "_exit" * kullandık çünkü "stdout" vb. tamponlar üst * prosesten kopyalandığı için iki defa "flush" * işlemi olsun istemedik. */ if(dup2(pipefds[1], 1) == -1) _exit(EXIT_FAILURE); /* * Burada bizler çiftleme sonucunda orjinal olanı kapatıyoruz. * Her ne kadar "exec" sonrasında bu kapatılsa bile, o ana * kadar boş yere tutmanın bir anlamı yoktur. */ close(pipefds[1]); /* * Artık alt prosesimiz "exec" uygulayacaktır. */ if(execvp(cmdl[0], cmdl) == -1) _exit(EXIT_FAILURE); // Unreachable code... } pid_t pidr; /* * Şimdi de ikinci alt prosesimizi hayata * getiriyoruz. */ if((pidr = fork()) == -1) exit_sys("fork"); /* * İkinci alt process de "exec" çağrısı yapacaktır. Dolayısıyla * onun akışı bu "if" bloğunun aşağısına akmayacaktır. */ if(pidr == 0) { /* * Bu prosese okuma görevi verdik. Gereksiz dosya * betimleyicilerini kapatmaları gerekmektedir. */ close(pipefds[1]); /* * Bu alt prosesin "stdout" dosyasını boruya * yönlendirelim. Artık ekrana değil, boruya * yazacaktır. Başarısızlık durumunda "_exit" * kullandık çünkü "stdout" vb. tamponlar üst * prosesten kopyalandığı için iki defa "flush" * işlemi olsun istemedik. */ if(dup2(pipefds[0], 0) == -1) _exit(EXIT_FAILURE); /* * Burada bizler çiftleme sonucunda orjinal olanı kapatıyoruz. * Her ne kadar "exec" sonrasında bu kapatılsa bile, o ana * kadar boş yere tutmanın bir anlamı yoktur. */ close(pipefds[0]); /* * Artık alt prosesimiz "exec" uygulayacaktır. */ if(execvp(cmdr[0], cmdr) == -1) _exit(EXIT_FAILURE); // Unreachable code... } /* * Bu programda iki alt proses de kendi arasında * haberleşme yapacağı için, üst prosesin boru * betimleyicilerini kapatmamız gerekmektedir. */ close(pipefds[0]); close(pipefds[1]); /* * Alt prosesleri beklememiz gerekmektedir. */ if(waitpid(pidl, NULL, 0) == -1) exit_sys("waitpid"); if(waitpid(pidr, NULL, 0) == -1) exit_sys("waitpid"); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } Ayrıca yukarıda yaptıklarımızı gerçekleştiren bazı POSIX fonksiyonları da vardır. Bu fonksiyonlar "popen" ve "pclose" fonksiyonlarıdır. Bu fonksiyonlar da yine "stdin" ve "stdout" tamponlarını yönlendirmektedir. "popen" ile açtığımız boru mekanizması "pclose" ile kapatılmalıdır. "fdopen" fonksiyonu, arka planda çağrılan bir fonksiyondur. Şimdi de bu fonksiyonları irdeleyelim: >>> "popen" fonksiyonu: aşağıdaki parametrik yapıya sahiptir: #include FILE *popen(const char *command, const char *mode); Bu fonksiyonun birinci parametresi, "shell" programını interaktif olmayan bir biçimde çalıştırırken, kullanılacak "shell" komutudur. Yani buradaki komut, "shell" programı tarafından interaktif olmayan bir biçimde çalıştırılır. Bu fonksiyonun ikinci parametresi ise haberleşme için kullanılacak boruda yapılacak işlemi belirtmektedir. Bu parametre ya "r" ya da "w" olabilir. "r" olması durumunda, birinci parametreye geçilen programın "stdout" a yazdıkları boruya yönlendirilmiş olacak. Bizler de, bu fonksiyonun geri dönüş değerini kullanarak, bu borudan okuma yapabileceğiz. Öte yandan ikinci parametre "w" olursa, birinci parametreyle geçilen programın "stdin" i boruya yönlendirilmiş olacak. Bizler, yine bu fonksiyonun geri dönüş değerini kullanarak, boruya yazdığımız zaman birinci parametredeki program borudan okudukları üzerinde işlem yapacaktır. Açıkça görüldüğü üzere, programın geri dönüş türü "FILE*" olması hasebiyle bu değeri standart C dosya fonksiyonlarıyla birlikte kullanabiliriz. Tabii yine unutmamak gerekir ki "buffered" mekanizma burada da geçerlidir. Bir takım işlemler sonrasdında "flush" yapmaya ihtiyaç duyabiliriz. Bu fonksiyon başarısız olduğunda "NULL" değerine dönmektedir. >>> "pclose" fonksiyonu: Aşağıdaki parametrik yapıya sahiptir: #include int pclose(FILE *stream); Parametre olarak, "popen" ile elde ettiğimiz "FILE" türünden nesnenin adresini istemektedir. Başarı durumunda çalıştırılan "shell" komutunun "wait" fonksiyonları ile elde edilen durum bilgisini geri döndürür. Başarısızlık durumunda "-1" ile geri döner. Aşağıda fonksiyonların kullanımına ilişkin örnekler verilmiştir: * Örnek 1, #include "stdio.h" #include #include void exit_sys(const char*); int main(int argc, char** argv) { /* # OUTPUT # total 20 -rwxr-xr-x 1 14061 14061 16208 Mar 26 10:52 a.out -rwxrwxrwx 1 root root 631 Mar 26 10:52 main.c */ FILE* f; /* * "ls" komutu normalde ekrana yazmaktadır. Fakat boruya * yönlendirildiği için boruya yazacaktır. Bizde "r" ile * borudan okuma yapacağız. */ if((f = popen("ls -l", "r")) == NULL) exit_sys("popen"); int ch; while((ch = fgetc(f)) != EOF) putchar(ch); pclose(f); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2, #include "stdio.h" #include #include void exit_sys(const char*); int main(int argc, char** argv) { /* # OUTPUT # cc1: fatal error: mainn.c: No such file or directory compilation terminated. 1 */ FILE* f; if((f = popen("gcc mainn.c -o main", "r")) == NULL) exit_sys("popen"); int ch; while((ch = fgetc(f)) != EOF) putchar(ch); int status; if((status = pclose(f)) == -1) exit_sys("pclose"); if(WIFEXITED(status)) // Normal sonlanmışsa: printf("%d\n", WEXITSTATUS(status)); else // Abnormal sonlanmışsa: printf("shell terminated abnormally!...\n"); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 3, #include "stdio.h" #include #include void exit_sys(const char*); int main(int argc, char** argv) { /* # OUTPUT # 0 1 190 */ FILE* f; if((f = popen("wc", "w")) == NULL) exit_sys("popen"); for(int i = 0; i < 100; ++i) fprintf(f, "%d", i); pclose(f); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } >> İsimli Boru Haberleşmeleri: İsimsiz boru haberleşmesine nazaran, bu haberleşme biçimi herhangi iki proses arasında haberleşme için kullanılabilir. İsimli borular ile haberleşme tipik olarak şu aşağıdaki aşamalardan geçmektedir: -> Bir adet "pipe" dosyası meydana getiriyoruz. Nasıl dizin girişleri "d", normal dosyalar "-" ile gösteriliyorsa, "pipe" dosyaları da "p" ile gösterilmektedir eğer "ls" komutunu çağırırsak. Böylesi bir dosyayı oluşturabilmek için "mkfifo" isimli POSIX fonksiyonunu çağırmamız gerekmektedir. Tabii bu fonksiyon ile oluşturduğumuz dosyaya erişim hakları yine prosesin "umask" değerinden etkilenmektedir. Oluşturulan bu dosya sadece dizin girişi olarak yer kaplamakta, fiziksel bir dosya olarak diskte yer kaplamamaktadır. Ayrıca aynı işlevi gören ve aynı isimde bir "shell" komutu da mevcuttur. Bu komut ile "pipe" dosyası oluştururken, prosesin "umask" değeri DEVREYE GİRMEZ. "mkfifo" fonksiyonu aşağıdaki parametrik yapıya sahiptir: #include int mkfifo(const char *path, mode_t mode); Birinci parametre, oluşturulacak "pipe" dosyasının yol ifadesini belirtir. İkinci parametre ise iş bu dosyanın sahip olacağı erişim haklarını erişir. Fonksiyon başarılı olduğunda "0" ile başarısızlık durumunda "-1" ile geri döner. Boru dosyasını haberleşecek iki prosesten birisinin oluşturması bir zorunluluk değildir. Dışarıdan üçüncü bir proses de bu dosyayı oluşturabilir. * Örnek 1, #include "stdio.h" #include #include #include void exit_sys(const char*); int main(int argc, char** argv) { /* # OUTPUT # Ok... */ if(mkfifo("testFifo", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) == -1) exit_sys("mkfifo"); printf("Ok!...\n"); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } -> Haberleşecek olan iki proses de oluşturulan bu boru dosyasını "open" fonksiyonu ile açar. Açım sırasında tipik olarak "O_RDONLY" ve "O_WRONLY" modları kullanılmaktadır. Her ne kadar "O_RDWR" modu da kullanılsa, bu modu kullanmak uygun değildir. Ek olarak POSIX standartlarınca boru dosyalarının "O_RDWR" olarak açılması "unspecified" olarak belirtilmiştir. Eğer proseslerden birisi ilgili boruyu "O_RDONLY" modunda açmışsa, başka bir proses aynı boruyu "O_WRONLY" ya da "O_RDWR" modunda açana kadar, "open" fonksiyonu tarafından bloke edilir. Benzer biçimde prosesimiz boruyu "O_WRONLY" modunda açmışsa, başka bir proses "O_RDONLY" ya da "O_RDWR" modunda açılana kadar, prosesimiz "open" fonksiyonu tarafından bloke edilir. Eğer proseslerden birisi "O_RDWR" modunda açılmışsa, bloke GERÇEKLEŞMEZ FAKAT BU MODDA BORUNUN AÇILMASI TAVSİYE EDİLMEMEKTEDİR. Özetle "open" fonksiyonu iki proses de boruyu uygun biçimde açana kadar, proseslerin bloke edilmesine neden olmaktadır. * Örnek 1, // Birinci proses: Yazma yapacak olan. #include "stdio.h" #include #include #include #include void exit_sys(const char*); int main(int argc, char** argv) { /* # OUTPUT # */ int fd; if((fd = open("testFifo", O_WRONLY)) == -1) exit_sys("open"); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } // İkinci proses: Okuma yapacak olan. #include "stdio.h" #include #include #include #include void exit_sys(const char*); int main(int argc, char** argv) { /* # OUTPUT # */ int fd; if((fd = open("testFifo", O_RDONLY)) == -1) exit_sys("open"); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } -> Bir biçimde isimli bir boru oluşturmamız gerekmektedir. Bunu sağlamak için "mkfifo" isimli kabuk komutunu kullanabiliriz. Öte yandan haberleşecek iki prosesten birisi de yine "mkfifo" isimli POSIX fonksiyonunu kullanarak da bu dosyayı oluşturabilir. Son olarak üçüncü bir harici program bu boruyu oluşturabilir. Aşağıdaki kabuk komutu ile "mypipe" isminde bir boru oluşturalım. $mkfifo mypipe Kabuk programı üzerinden "ls -l mypipe" komutunu çalıştırdığımız vakit aşağıdaki çıktıyı alacağız: prw-r--r-- 1 14088 14088 0 Mar 29 16:18 mypipe -> Artık haberleşecek olan iki prosesin ilgili boru dosyasını açması gerekmektedir. Burada okuma yapacak olan proses boruyu açtıktan sonra bloke edilir. Ta ki boruya yazma yapacak olan proses ilgili boruyu açana dek. Benzer şekilde yazma yapacak olan proses boruyu ilk açtığında yine bloke edilir, ta ki boruyu okuma amacı taşıyan prosesin boruyu açmasına kadar. Dolayısıyla okuma ve yazma yapacak olan proseslerin birlikte boruyu açması gerekmektedir. Daha önce de belirttiğimiz gibi eğer proseslerden birisi boruyu "O_RDWR" modunda açarsa blokeye düşmeyecektir fakat bu modda açım POSIX standartlarınca "unspecified" olarak belirtilmiştir. -> Artık bu noktada isimsiz boru haberleşmesinden bir farkımız kalmamıştır. "read" ve "write" fonksiyonlarıyla boruya yazma ve okuma işlemleri yapılabilir. -> Haberleşme sonlanacağı zaman, yazan proses tarafında sonlanması uygundur. Böylelikle okuyan taraf boruda kalanları da okuyacak ve borunun yazma tarafının kapatıldığını görüp "read" fonksiyonu "0" ile geri dönecektir. -> Borunun yok edilmesi, boruyu oluşturan kişi tarafından gerçekleştirilmesi uygundur. Komut satırından bir boru dosyasını yok etmek için "remove" ya da "unlink" fonksiyonlarını kullanabiliriz. Tabii gerçekten de boru dosyasının silinebilmesi için bu dosyayı gösteren "fd" sayısının "0" olması gerekmektedir. Aksi halde sadece dizin girişinden kaldırılacaktır. Eğer boru üçüncü parti bir proses tarafından "mkfifo" ile oluşturulmuşsa, "unlink" ve ya "remove" isimli POSIX fonksiyonlarını kullanması gerekmektedir. Öte yandan başka bir haberleşme için boru dosyası silinmeyedebilir. Aşağıda böyle bir kullanıma örnek verilmiştir: if(unlink("mypipe") == -1) exit_sys("unlink"); Aşağıda örnek bir kullanım verilmiştir: * Örnek 1, Aşağıdaki örnekteki "mypipe" isimli boru dosyasının bir şekilde oluşturulduğu varsayılmıştır. Dolayısıyla ilk önce hangi programın çalıştırıldığının bir önemi yoktur. "write" isimli program klavyeden girilenleri bu boruya yazmakta, "read" ise boruya yazılanları ekrana basmaktadır. Unutmamalıyız ki "read" isimli program borudan kaç bayt okuması gerektiğini bilmemektedir. Dolayısıyla 4096 bayt kadarlık bilgiyi okumak isteyecektir. Fakat okuyabildiği kadar bilgiyi okuyacak. Önceki örnekte "sizeof(int)" kadar bilginin yazıldığı bilindiği için "sizeof(int)" kadarlık bilgi okundu. Bu örnekte boruya kaç bayt yazıldığı bilinmediği için 4096 bayt okunmak istenmiştir. Yüksek bir değer girilerek, tek seferde okunmak amaçlanmıştır. /* write */ #include "stdio.h" #include #include #include #include #define BUFFER_SIZE 4096 void exit_sys(const char*); int main(int argc, char** argv) { /* * Aşağıdaki program, değişik uzunluktaki yazıları * boruya yazmaktadır. */ int fdpipe; if((fdpipe = open("mypipe", O_RDONLY)) == -1) exit_sys("open"); char buf[BUFFER_SIZE]; char* str; for(;;) { /* * Diğer prosese göndermek istediğimiz yazı beklenmektedir. */ printf("Text: "); /* * Genel olarak "stdin" den okuma yapan fonksiyonlar * "stdout" tamponunu da "flush" etmektedir. Fakat bu * garanti edilmemiştir. */ fflush(stdout); /* * "stdin" tamponundakileri "buf" adresinden itibaren * 4096 karakterlik alana alıyoruz. Fakat burada yazının * sonundaki '\n' karakteri de alınmaktadır. */ if(fgets(buf, BUFFER_SIZE, stdin) != NULL) /* * Dolayısıyla bu '\n' karakterini '\0' ile değiştirmeliyiz. * Çünkü C'nin standart fonksiyonları '\0' karakterine göre * hareket etmektedir. */ if((str = strchr(buf, '\n')) != NULL) *str = '\0'; /* * POSIX standartlarınca bir yere "0" bayt yazmak "unspecified" * olarak betimlenmiştir. */ if(*buf == '\0') continue; /* * Eğer "stdin" tamponuna "quit" girilmişse program * direkt sonlanacaktır. Karşı taraf ise boruda kalanları okuduktan * sonra "read" fonksiyonu "0" ile geri döneceğinden döngüden çıkar. * Böylece karşı taraf da sonlanır. */ if(!strcmp(buf, "quit")) break; /* * Blokeli modda, "write" fonksiyonu parçalı yazım * yapmayacaktır. "buf" adresinden itibaren, "strlen(buf)" * kadarlık karakter artık boruya yazılacaktır. */ if(write(fdpipe, buf, strlen(buf)) == -1) exit_sys("write"); } /* * Açtığımız boru dosyası kapatıldı, silinmedi. */ close(fdpipe); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } /* read */ #include "stdio.h" #include #include #include #define BUFFER_SIZE 4096 void exit_sys(const char*); int main(int argc, char** argv) { /* * Aşağıdaki program ise okuyabildiği maksimum karakteri * okumaya çalışmaktadır. Eğer karşı taraf iki defa "write" * yaparsa, her ikisi de okunacaktır. */ int fdpipe; if((fdpipe = open("mypipe", O_WRONLY)) == -1) exit_sys("open"); ssize_t result; char buf[BUFFER_SIZE + 1]; /* * Borudan 4096 karakteri "buf" adresinden itibaren * yazacağız. Eğer karşı taraf boruyu kapatmışsa ve boruda * okunacak başka bir şey kalmadıysa, "0" ile geri dönecektir. * Eğer bir "IO" hatası olmuşsa da "-1" ile. */ while((result = read(fdpipe, buf, BUFFER_SIZE)) > 0) { buf[result] = '\0'; puts(buf); } /* * Bir "IO" hatası olduğu için programı sonlandırıyoruz. */ if(result == -1) exit_sys("read"); /* * Her şey yolunda gittiği için boruyu biz de kapatıyoruz. */ close(fdpipe); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } Peki bizler yukarıdaki bu örnekte klavyeden sadece "Enter" tuşuna bassaydık ne olurdu? "write" programındaki "fgets" fonksiyonu ilgili adrese sadece '\n' karakterini yazardı. Bizler de bu karakteri '\0' karakterine dönüştürürdük. "strlen(buf)" değeri "0" olacağından boruya bir şey yazmayacaktır. Boruya bir şey yazılmadığından, "read" prosesi blokede bekleyecektir. Fakat bu senaryo "0" bayt yazma ve "0" bayt okuma kapsamında özel olarak ele alınmalıdır. >>> Hedefe "0" bayt yazma: Linux'ta "write" POSIX fonksiyonu ile bir "regular" dosyaya "0" bayt yazmak istediğimizde önce bir takım ön kontroller gerçekleştirilir, örneğin ilgili dosyanın yazma modunda açılıp açılmadığınının sorgulanması gibi. Eğer bu kontoller sırasında bir başarısızlık oluşursa, "write" fonksiyonu "-1" ile geri döner. Aksi halde "0" ile geri döner. Her iki durumda da YAZMA İŞLEMİ GERÇEKLEŞMEZ. Fakat POSIX standartlarında bu durum "unspecified" olarak belirtilmiştir. Öte yandan POSIX standartları, "regular" dosyalar dışında "0" bayt yazma işlemini de "unspecified" olarak belirtmiştir. Dolayısıyla boru gibi dosyalara "0" yazıldığında ne olacağı o sisteme bağlı bir durumdur. Özetle; POSIX standartlarınca boruya "0" bayt yazma işlemi "unspecified" olarak betimlenmiştir. >>> Hedeften "0" bayt okuma: POSIX standartlarınca "read" fonksiyonu yine bir takım ön kontrolleri yerine getirir. Örneğin, dosyanın okuma modunda açılıp açılmadığına bakar. Bu kontroller sırasında bir hata olması durumunda "-1" ile geri döner. Aksi halde "0" ile geri döner. Bu noktada "write" fonksiyonundan ayrılmaktadır. HERHANGİ BİR OKUMA İŞLEMİ YAPMAYACAKTIR. Aşağıdaki örneği inceleyelim: * Örnek 1, Aşağıdaki örnekte boru dosyası "write" programı tarafından oluşturulacaktır. /* write */ #include "stdio.h" #include #include #include #include #define BUFFER_SIZE 4096 void exit_sys(const char*); int main(int argc, char** argv) { /* * Aşağıdaki program, değişik uzunluktaki yazıları * boruya yazmaktadır. */ /* * Haberleşme için kullanılacak boruyu iş bu program oluşturacağı için * ilk bu programın çalıştırılması gerekmektedir. */ if(mkfifo("mypipe", S_IRUSR | S_IWUSR | S_IRGRP | S_IRGRP) == -1) exit_sys("mkfifo"); int fdpipe; if((fdpipe = open("mypipe", O_RDONLY)) == -1) exit_sys("open"); char buf[BUFFER_SIZE]; char* str; for(;;) { /* * Diğer prosese göndermek istediğimiz yazı beklenmektedir. */ printf("Text: "); /* * Genel olarak "stdin" den okuma yapan fonksiyonlar * "stdout" tamponunu da "flush" etmektedir. Fakat bu * garanti edilmemiştir. */ fflush(stdout); /* * "stdin" tamponundakileri "buf" adresinden itibaren * 4096 karakterlik alana alıyoruz. Fakat burada yazının * sonundaki '\n' karakteri de alınmaktadır. */ if(fgets(buf, BUFFER_SIZE, stdin) != NULL) /* * Dolayısıyla bu '\n' karakterini '\0' ile değiştirmeliyiz. * Çünkü C'nin standart fonksiyonları '\0' karakterine göre * hareket etmektedir. */ if((str = strchr(buf, '\n')) != NULL) *str = '\0'; /* * POSIX standartlarınca bir yere "0" bayt yazmak "unspecified" * olarak betimlenmiştir. */ if(*buf == '\0') continue; /* * Eğer "stdin" tamponuna "quit" girilmişse program * direkt sonlanacaktır. Karşı taraf ise boruda kalanları okuduktan * sonra "read" fonksiyonu "0" ile geri döneceğinden döngüden çıkar. * Böylece karşı taraf da sonlanır. */ if(!strcmp(buf, "quit")) break; /* * Blokeli modda, "write" fonksiyonu parçalı yazım * yapmayacaktır. "buf" adresinden itibaren, "strlen(buf)" * kadarlık karakter artık boruya yazılacaktır. */ if(write(fdpipe, buf, strlen(buf)) == -1) exit_sys("write"); } /* * Açtığımız boru dosyası kapatıldı, silinmedi. */ close(fdpipe); /* * Öte yandan bu program boruyu oluşturduğu için, * yine bu programın boruyu yok etmesi gerekmektedir. */ if(unlink("mypipe") == -1) exit_sys("mypipe"); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } /* read */ #include "stdio.h" #include #include #include #define BUFFER_SIZE 4096 void exit_sys(const char*); int main(int argc, char** argv) { /* * Aşağıdaki program ise okuyabildiği maksimum karakteri * okumaya çalışmaktadır. Eğer karşı taraf iki defa "write" * yaparsa, her ikisi de okunacaktır. */ int fdpipe; if((fdpipe = open("mypipe", O_WRONLY)) == -1) exit_sys("open"); ssize_t result; char buf[BUFFER_SIZE + 1]; /* * Borudan 4096 karakteri "buf" adresinden itibaren * yazacağız. Eğer karşı taraf boruyu kapatmışsa ve boruda * okunacak başka bir şey kalmadıysa, "0" ile geri dönecektir. * Eğer bir "IO" hatası olmuşsa da "-1" ile. */ while((result = read(fdpipe, buf, BUFFER_SIZE)) > 0) { buf[result] = '\0'; puts(buf); } /* * Bir "IO" hatası olduğu için programı sonlandırıyoruz. */ if(result == -1) exit_sys("read"); /* * Her şey yolunda gittiği için boruyu biz de kapatıyoruz. */ close(fdpipe); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } "stream" tabanlı haberleşme yaparken okuyan program kaç bayt yazıldığını bilmediği için, yazan tarafa bir takım sorumluluklar düşmektedir. Aksi halde boruya yazma işlemi yapılırken bütün yazılar ardıardına yazılacağından, okuma işleminden sonra bütün yazılar ardı ardına gelir. Bu problemi de iki şekilde çözebiliriz. Bunlardan ilki, boruya ilk başta kaç bayt yazılacağının bilgisini yazmak ve ardından da içeriğin kendisini. Diğer yöntem ise yazılacak sonuna özel bir karakter ekleyerek boruya yazmak ve okuma yaparken de bu özel karaktere göre ayrıştırmak. İkinci yöntemde şöyle bir sıkıntı vardır: O özel karakteri nasıl tespit edebiliriz? Birer bayt şeklinde okuma yapmak sağlıklı değil çünkü çok fazla sistem fonksiyonu çağrılmaktadır. Bunun yerine bir blok bilginin borudan alınması ve bu blok bilgi içerisinde ilgili özel karakterin aranması gerekmektedir. Öte yandan bu yönteme benzer şu yöntemi de kullanabiliriz; yazma yapmadan evvel ilgili yazının sonuna '\n' karakteri yerleştirmek. Daha sonra ilgili borunun betimleyicisini "fdopen" fonksiyonuna göndererek bir "FILE*" elde etmek. Son olarak elde ettiğimiz bu değeri "fgets" fonksiyonuna argüman olarak geçmek ve böylelikle ilgili borudan '\n' karakteri görene kadar okuma yapmak. Fakat ilk yöntem tavsiye edilmektedir. * Örnek 1, Aşağıdaki program "/usr/include" içerisindeki dosyaların isimlerini boru üzerinden diğer programa göndermiştir. Boruya yazma yaparken şu şekilde bir kodlama izlemektedir: [dizi_ismi_uzunluğu][dizi_ismi] Yani yukarıdaki yöntemlerden birincisine dair bir örnektir. /* write */ #include "stdio.h" #include #include #include #include #include #include #define BUFFER_SIZE 4096 void exit_sys(const char*); int main(int argc, char** argv) { /* # Command Line Argument # /usr/include */ if(argc != 2) { fprintf(stderr, "wrong number of arguments!...\n"); exit(EXIT_FAILURE); } DIR* fddir; if((fddir = opendir(argv[1])) == NULL) exit_sys("opendir"); int fdpipe; /* * İlgili boru dosyasının halihazırda oluşturulduğu * varsayılmıştır. */ if((fdpipe = open("mypipe", O_WRONLY)) == -1) exit_sys("open"); size_t len; struct dirent* de; /* * "readdir" fonksiyonu "IO" hatası ve dizinin * sonuna gelindiğinde de "NULL" ile geri dönmektedir. * Bunu ayırt edebilmek için "errno" değişkenini baştan * sıfıra çekiyoruz. */ while(errno = 0, (de = readdir(fddir)) != NULL) { /* * İlk önce yazılacak olan karakterin uzunluğunu alıp, * boruya bunun bilgisini yazıyoruz. "len" adresinden * "sizeof(size_t)" bayt kadarlık kısım boruya yazılacak. */ len = strlen(de->d_name); if(write(fdpipe, &len, sizeof(size_t)) == -1) exit_sys("write"); /* * İlgili boruya, "de->d_name" adresinden itibaren * "len" adedince karakter yazılacaktır. */ if(write(fdpipe, de->d_name, len) == -1) exit_sys("write"); } /* * Dizin girişinin sonuna gelinmişse, "errno" hala sıfırdır. */ if(errno != 0) exit_sys("readdir"); /* * En son boruyu açtığımız için ilkin boruyu kapatmalıyız. */ close(fdpipe); closedir(fddir); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } /* read */ #include "stdio.h" #include #include #include #define BUFFER_SIZE 4096 void exit_sys(const char*); int main(int argc, char** argv) { int fdpipe; if((fdpipe = open("mypipe", O_RDONLY)) == -1) exit_sys("open"); size_t len; char buf[BUFFER_SIZE + 1]; ssize_t result; for(;;) { /* * Borudan okuduğumuz "sizeof(size_t)" kadarlık bilgi * "len" adresine işlendi. Artık bir sonraki okumada * bu bayt kadarlık okuma yapmamız gerekiyor. */ if((result = read(fdpipe, &len, sizeof(size_t))) == -1) exit_sys("read"); if(result == 0) break; /* * Şimdi ise uzunluk kadarlık bilgi okundu. */ if(read(fdpipe, buf, len) == -1) exit_sys("read"); buf[len] = '\0'; puts(buf); } close(fdpipe); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2, Aşağıdaki örnekte yukarıda belirtilen yöntemlerden ikincisi kullanılmıştır. Yani her kayıttan sonra özel bir karakter boruya yazılmaktadır. Dolayısıyla boruya yazma yaparken şu şekilde bir kodlama izlemektedir: [dizi_ismi][\n] Burada '\n' kullanılmasının yegane sebebi, standart fonksiyonların bazıları '\n' karakterini göre kadar okuma yapmalarıdır. Bizler burada okuma esnasında borudaki karakterleri tek tek okumak yerine, "fgets" fonksiyonundan faydalanılmıştır. Öte yandan okuma yapmak için "open" yerine "fopen" fonksiyonunu da kullanabilirdik. /* write */ #include "stdio.h" #include #include #include #include #include #include #define BUFFER_SIZE 4096 void exit_sys(const char*); int main(int argc, char** argv) { /* # Command Line Argument # /usr/include */ if(argc != 2) { fprintf(stderr, "wrong number of arguments!...\n"); exit(EXIT_FAILURE); } DIR* fddir; if((fddir = opendir(argv[1])) == NULL) exit_sys("opendir"); int fdpipe; /* * İlgili boru dosyasının halihazırda oluşturulduğu * varsayılmıştır. */ if((fdpipe = open("mypipe", O_WRONLY)) == -1) exit_sys("open"); char delim = '\n'; size_t len; struct dirent* de; /* * "readdir" fonksiyonu "IO" hatası ve dizinin * sonuna gelindiğinde de "NULL" ile geri dönmektedir. * Bunu ayırt edebilmek için "errno" değişkenini baştan * sıfıra çekiyoruz. */ while(errno = 0, (de = readdir(fddir)) != NULL) { /* Boruya ilk önce yazıyı yazıyoruz. */ if(write(fdpipe, de->d_name, strlen(de->d_name)) == -1) exit_sys("write"); /* * Daha sonra da özel karakterimizi direkt boruya yazıyoruz. * Burada '\n' yerin '\0' da kullanabilirdik fakat '\0' görene * kadar giden standart bir C fonksiyonu olmadığından, '\n' daha * mantıklıdır. */ if(write(fdpipe, &delim, 1) == -1) exit_sys("write"); } /* * Dizin girişinin sonuna gelinmişse, "errno" hala sıfırdır. */ if(errno != 0) exit_sys("readdir"); /* * En son boruyu açtığımız için ilkin boruyu kapatmalıyız. */ close(fdpipe); closedir(fddir); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } /* read */ #include "stdio.h" #include #include // No need for Alternative Way - II #include // No need for Alternative Way - II #define BUFFER_SIZE 4096 void exit_sys(const char*); int main(int argc, char** argv) { // Alternative Way - I /* * İlk önce boruyu açarak ona ait olan "fd" yi * elde ettik. */ int fdpipe; if((fdpipe = open("mypipe", O_RDONLY)) == -1) exit_sys("open"); FILE* fpipe; /* * Daha sonra boruya ait olan "fd" üzerinden * "FILE" yapısını elde ettik. */ if((fpipe = fdopen(fdpipe, "r")) == NULL) exit_sys("fdopen"); // Alternative Way - II /* * Boru dosyasını direkt olarak standart C fonksiyonu * ile açarak "FILE" yapısı elde ettik. Fakat C'nin standart yazma * fonksiyonları tamponlu çalıştığı için yazmadan hemen sonra "fflush" * yapmamız gerekebilir. Aksi halde yazılar boruya yazılmadan tampon * dolana kadar bekletilir. */ if((fpipe = fopen("mypipe", "r")) == NULL) exit_sys("fdopen"); // Common Section size_t len; char buf[BUFFER_SIZE]; ssize_t result; for(;;) { /* * Borudaki bilgiler, "buf" adres alanından itibaren yazılacaktır. * Yine '\n' karakteri de borudan çekilip, "buf" dizisine yazılacaktır. */ if(fgets(buf, BUFFER_SIZE, fpipe) == NULL) break; printf("%s", buf); } /* * "fclose" fonksiyonu, argüman olarak aldığı betimleyiciye iliştirilen * diğer betimleyicileri de kapatmaktadır. */ fclose(fpipe); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } Pekiyi isimli borular ile "Client-Server" haberleşme mimarisi nasıl uygulanabilirdir? "Client-Server" haberleşme mimarisinde ortamın pek bir önemi yoktur. Ortam yeri gelir boru olur, yeri gelir "socket" olur. Bu mimariyi bir tasarım kalıbı olarak da değerlendirebiliriz. Bu mimaride "Clint" olanlar "Server" dan bir istekte bulunurlar. "Server" ise gelen bu istekleri yerine getirip "Client" lara geri dönüş yapar. Burada "Client" lar isteklerini boru üzerinden iletirken tek bir boru kullanabilirler. Sonuç olarak her "Client", kendi isteğini bir "struct" içerisinde gönderiyor. Fakat "Server", cevapları tek bir boru üzerinden göndermesi manasızdır. Birden fazla "Client" tek bir borudan okuma yapacağı için, hangisi okursa boru boşaltılacaktır. Dolayısıyla "Server" dan geri bildirimler, her "Client" a özgü borular üzerinden yapılmalıdır. Özetle; -> From Client to Server, 1 Pipe Only -> From Server to Client, 1 Pipe per Client İşte "Client-Server" arasındaki haberleşme akışı da aşağıdaki şekilde gerçekleşmektedir: -> İsteklerini iletmek isteyen "Client" lar hangi boruyu kullanmaları gerektiğini işin başında bilmeleri gerekiyor ve bu boruyu açmaları gerekmektedir. Benzer şekilde "Server" ın da bu boruyu bilmesi gerekmektedir. Yani bu boruyu bizler komut satırından oluşturabiliriz. -> "Client" lar isteklerini bir "struct" haline getirmelidir. Örneğin, struct tagMSG{ pid_t pid; // Bu ID değeri o an için eşsizdir ve "Client" ın tespitinde kullanılır. int type; // Burada mesajın tür bilgisi tutulabilir. char message[]; // "Server" a iletilmek istenen mesajı tutacak tampon. Yani mesajın içeriği. }MSG; -> "Server" ın "Client" lara geri bildirim yapması için bir boru kullanması gerektiğini söyledik. Pekiyi bu boruyu kim oluşturacak? İşin başında kaç adet "Client" ın bağlanacağı bilinemez. Dolayısıyla çalışma zamanına yönelik bir çözüm üretmeliyiz. Akla ilk gelen yöntem, "Client" in göndereceği "struct" içerisindeki "type" bilgisini alan "Server", bu bilgi doğrultusunda bir boru oluşturur. Fakat "Server" kendi içerisinde "dictionary" tipindeki bir veri yapısında bu "type" bilgisi ile oluşturulan borunun "fd" bilgisini saklamalıdır. C++ dilinde bu tür veri yapılarına "std::set", "std::map" vb. örnek verilebilir. C dilinde böylesi bir veri yapısı yok. Dolayısıyla ya üçüncü parti kütüphanelerdekileri kullanmalıyız ya da daha ilkel bir veri yapısını kendimiz oluşturmalıyız. Bu veri yapısını oluşturduktan sonra, "Server" tarafı "Client" lara cevap vermek için kullanılacak olan boruları oluşturabilir. -> Bu haberleşme mimarisinde "Client" ın gönderdiği her mesaja "Server" ın bir cevap vermesi beklenir. Fakat "Client" ilk mesajını yolladıktan sonra "Server" tarafından boru hayata getirilirken bir hata oluşabilir ve boru oluşamaz. Bu durumda bu hata bilgisi "Client" a geri nasıl gönderilmelidir? Bu problemi çözmek için ilgili borunun "Client" tarafından oluşturulması ve bu borunun "fd" bilgisinin de gönderilen mesajın içine eklenmesi gerekir. Şimdi elimizde iki farklı çözüm var. Bu noktada izler ikinci çözümden devam edelim. YANİ İLGİLİ BORU "Client" TARAFINDAN OLUŞTURULMAKTADIR. Yine unutmamalıyız ki ilgili boru "Client" tarafından oluşturulduğu için yine ilgili "Client" tarafından yok edilmelidir. -> Bu aşamada "Client" ın hangi borudan "Server" a mesaj göndereceği ve "Server" ın gelen bu mesajı hangi boru üzerinden o "Client" a iletmesi gerektiği bilinmektedir. -> Haberleşme sonlanırken de "el sıkışma" felsefesi izlenmelidir. Yani "Client" tarafı haberleşmeyi sonlandırmak istediğini "Server" a iletir. Bu talebi alan "Server" bir takım kontroler gerçekleştirir ve haberleşmenin sonlanması için onay verir. Bu onayı alan "Client" ise gerekli kapatma işlemlerini gerçekleştirir. * Örnek 1, Aşağıda "Client-Server" mimarisinde boru kullanımına dair bir örnek verilmiştir. Haberleşmenin sağlanabilmesi için işin başında "serverpipe" isimli boru dosyasının var olması gerekmektedir. Daha sonra "Client" proses, "Server" tarafından kendisine mesaj gönderilirken kullanılacak olan borunun isminin girilmesini beklemektedir. Örneğin, bu isim olarak "clientpipe" girildiğini varsayalım. Artık haberleşme şu aşağıdaki şekilde olacaktır; -> From "Client" to "Server", "serverpipe". -> From "Server" to "Client", "clientpipe". Burada "clientpipe" isimli boru "Server" tarafından oluşturulacaktır ve oluşturduğu borunun "fd" değerini de "clienpipe" isimli boru üzerinden "Client" a göndermektedir. Bu "fd" değeri artık o "Client" ın ID değerini oluşturacaktır. Eğer bir sorun olmadığını varsayarsak, iki proses de artık haberleşmektedir. Eğer "Client" program "CMD ls -l" mesajını gönderirse, "Server" tarafı "ls -l" kabuk komutu "shell" programında çalıştırtacaktır. Fakat sonuçları "Client" a gönderecektir. Bir nevi "Server" programdan kabuk komutlarını çalıştırmasını istiyoruz. Haberleşmeyi sonlandırmak için de "Client" program "quit" mesajını "Server" a iletmesi yeterlidir. Fakat arka planda şöyle bir akış izlenir; -> "Client" program "Server" programa "DISCONNECT_REQUEST" mesajını gönderir. -> Bu mesajı alan "Server", bir takım kontroller sonrasında haberleşmenin sonlanma teklifini kabul eder ise "Client" programa "DISCONNECT_ACCEPTED" mesajını iletir. -> Bu mesajı alan "Client", son olarak "Server" a "DISCONNECT" mesajını gönderir ve haberleşme sonlanır. Aşağıda ilgili programlara dair kodlar mevcuttur. /* server.c */ #include #include #include #include #include #include #include #define SERVER_PIPE "serverpipe" #define MAX_MSG_LEN 32768 #define MAX_PIPE_PATH 1024 #define MAX_CLIENT 1024 /* "Client" tarafından "Server" a gönderilen mesaj yapısı. */ typedef struct tagCLIENT_MSG { int msglen; int client_id; char msg[MAX_MSG_LEN]; } CLIENT_MSG; /* "Server" tarafından "Client" a gönderilen mesaj yapısı. */ typedef struct tagSERVER_MSG { int msglen; char msg[MAX_MSG_LEN]; } SERVER_MSG; /* Bir mesajın içeriği. */ typedef struct tagMSG_CONTENTS { char *msg_cmd; char *msg_param; } MSG_CONTENTS; /* Bir mesaj geldiğinde çağrılacak fonksiyon. */ typedef struct tagMSG_PROC { const char *msg_cmd; int (*proc)(int, const char *msg_param); } MSG_PROC; /* * "Server", kendisine bağlanan "Client" lara * ilişkin bilgileri bu yapı nesnesi üzerinden * saklayacaktır. */ typedef struct tagCLIENT_INFO { int fdp; char path[MAX_PIPE_PATH]; } CLIENT_INFO; int get_client_msg(int fdp, CLIENT_MSG *cmsg); int putmsg(int client_id, const char *cmd); void parse_msg(char *msg, MSG_CONTENTS *msgc); void print_msg(const CLIENT_MSG *cmsg); int invalid_command(int client_id, const char *cmd); int connect_proc(int client_id, const char *msg_param); int disconnect_request_proc(int client_id, const char *msg_param); int disconnect_proc(int client_id, const char *msg_param); int cmd_proc(int client_id, const char *msg_param); void exit_sys(const char *msg); /* * "CONNECT" mesajı geldiğinde "connect_proc" fonksiyonu * çağrılacak. Yani "Client", bu aşağıdaki komutlardan birisini * göndermelidir. Örneğin, "CMD ls -l" şeklinde bir mesaj "Client" * tarafından gönderildiğinde, sonuçlar "Client" ın terminalinde * çıkacaktır. */ MSG_PROC g_msg_proc[] = { {"CONNECT", connect_proc}, {"DISCONNECT_REQUEST", disconnect_request_proc}, {"DISCONNECT", disconnect_proc}, {"CMD", cmd_proc}, {NULL, NULL} }; /* * "Server", kendisine bağlanan "Client" ları * veri yapısı olarak C'deki dizileri kullanmıştır. */ CLIENT_INFO g_clients[MAX_CLIENT]; int main(void) { int fdp; CLIENT_MSG cmsg; MSG_CONTENTS msgc; int i; /* * Haberleşmeyi başlatmadan evvel bir şekilde "serverpipe" * isimli boru dosyasının var olması gerekmektedir. */ if ((fdp = open(SERVER_PIPE, O_RDWR)) == -1) exit_sys("open"); for (;;) { /* * İş bu fonksiyon ile "Client" lardan gelen mesajlar * ilgili borudan okunur. */ if (get_client_msg(fdp, &cmsg) == -1) exit_sys("get_client_msg"); /* Hangi mesajın okunduğu ekrana yazılmıştır. */ print_msg(&cmsg); /* Şimdi bu mesajı "parse" etmişiz. */ parse_msg(cmsg.msg, &msgc); for (i = 0; g_msg_proc[i].msg_cmd != NULL; ++i) /* * Bu okunan mesaj, yukarıdaki C dizisinde saklanan * mesajlar arasında mı değil mi sorgusu yapmışız. */ if (!strcmp(msgc.msg_cmd, g_msg_proc[i].msg_cmd)) { /* * Eğer öyleyse, karşılık gelen fonksiyonuna çağrı yapmışım. */ if (g_msg_proc[i].proc(cmsg.client_id, msgc.msg_param)) { } break; } if (g_msg_proc[i].msg_cmd == NULL) if (invalid_command(cmsg.client_id, msgc.msg_cmd) == -1) continue; } close(fdp); return 0; } int get_client_msg(int fdp, CLIENT_MSG *cmsg) { /* İlk önce mesajın uzunluk bilgisi borudan okunacaktır. */ if (read(fdp, &cmsg->msglen, sizeof(int)) == -1) return -1; /* Daha sonra hangi "Client" ın bu mesajı yazdığı bilgisi okunacaktır. */ if (read(fdp, &cmsg->client_id, sizeof(int)) == -1) return -1; /* Daha sonra da boruya yazılmış olan mesajın bizzat kendisi okunacaktır. */ if (read(fdp, cmsg->msg, cmsg->msglen) == -1) return -1; /* En sonunda ise ilgili mesajın sonuna '\0' karakteri yerleştirilmiştir. */ cmsg->msg[cmsg->msglen] = '\0'; return 0; } int putmsg(int client_id, const char *cmd) { SERVER_MSG smsg; int fdp; strcpy(smsg.msg, cmd); smsg.msglen = strlen(smsg.msg); fdp = g_clients[client_id].fdp; return write(fdp, &smsg, sizeof(int) + smsg.msglen) == -1 ? -1 : 0; } void parse_msg(char *msg, MSG_CONTENTS *msgc) { int i; msgc->msg_cmd = msg; for (i = 0; msg[i] != ' ' && msg[i] != '\0'; ++i) ; msg[i++] = '\0'; msgc->msg_param = &msg[i]; } void print_msg(const CLIENT_MSG *cmsg) { printf("Message from \"%s\": %s\n", cmsg->client_id ? g_clients[cmsg->client_id].path : "", cmsg->msg); } int invalid_command(int client_id, const char *cmd) { char buf[MAX_MSG_LEN]; sprintf(buf, "INVALID_COMMAND %s", cmd); if (putmsg(client_id, buf) == -1) return -1; return 0; } int connect_proc(int client_id, const char *msg_param) { int fdp; char buf[MAX_MSG_LEN]; /* * "Server", "Client" a geri dönmek için kullanacağı * boruyu kendisi oluşturmuştur. */ if (mkfifo(msg_param, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) == -1) { printf("CONNECT message failed! Params = \"%s\"\n", msg_param); return -1; } /* Daha sonra bu boruyu açmış. */ if ((fdp = open(msg_param, O_WRONLY)) == -1) exit_sys("open"); g_clients[fdp].fdp = fdp; strcpy(g_clients[fdp].path, msg_param); /* En sonunda ise bu boruya aşağıdaki yazıyı yazmıştır. */ sprintf(buf, "CONNECTED %d", fdp); if (putmsg(fdp, buf) == -1) exit_sys("putmsg"); return 0; } int disconnect_request_proc(int client_id, const char *msg_param) { if (putmsg(client_id, "DISCONNECT_ACCEPTED") == -1) return -1; return 0; } int disconnect_proc(int client_id, const char *msg_param) { close(g_clients[client_id].fdp); if (remove(g_clients[client_id].path) == -1) return -1; return 0; } int cmd_proc(int client_id, const char *msg_param) { FILE *f; char cmd[MAX_MSG_LEN] = "CMD_RESPONSE "; int i; int ch; if ((f = popen(msg_param, "r")) == NULL) { printf("cannot execute shell command!..\n"); return -1; } for (i = 13; (ch = fgetc(f)) != EOF; ++i) cmd[i] = ch; cmd[i] = '\0'; if (putmsg(client_id, cmd) == -1) return -1; return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } /* client.c */ #include #include #include #include #include #include #include #define SERVER_PIPE "serverpipe" #define MAX_CMD_LEN 1024 #define MAX_MSG_LEN 32768 #define MAX_PIPE_PATH 1024 typedef struct tagCLIENT_MSG { int msglen; int client_id; char msg[MAX_MSG_LEN]; } CLIENT_MSG; typedef struct tagSERVER_MSG { int msglen; char msg[MAX_MSG_LEN]; } SERVER_MSG; typedef struct tagMSG_CONTENTS { char *msg_cmd; char *msg_param; } MSG_CONTENTS; typedef struct tagMSG_PROC { const char *msg_cmd; int (*proc)(const char *msg_param); } MSG_PROC; void sigpipe_handler(int sno); int putmsg(const char *cmd); int get_server_msg(int fdp, SERVER_MSG *smsg); void parse_msg(char *msg, MSG_CONTENTS *msgc); void check_quit(char *cmd); int connect_to_server(void); int cmd_response_proc(const char *msg_param); int disconnect_accepted_proc(const char *msg_param); int invalid_command_proc(const char *msg_param); void clear_stdin(void); void exit_sys(const char *msg); /* * "Server" tarafından gönderilen mesajların * bilgisidir. */ MSG_PROC g_msg_proc[] = { {"CMD_RESPONSE", cmd_response_proc}, {"DISCONNECT_ACCEPTED", disconnect_accepted_proc}, {"INVALID_COMMAND", invalid_command_proc}, {NULL, NULL} }; int g_client_id; int g_fdps, g_fdpc; /* Function Definitions */ int main(void) { char cmd[MAX_CMD_LEN]; char *str; SERVER_MSG smsg; MSG_CONTENTS msgc; int i; if (signal(SIGPIPE, sigpipe_handler) == SIG_ERR) exit_sys("signal"); if ((g_fdps = open(SERVER_PIPE, O_WRONLY)) == -1) exit_sys("open"); if (connect_to_server() == -1) { fprintf(stderr, "cannot connect to server! Try again...\n"); exit(EXIT_FAILURE); } for (;;) { printf("Client>"); fflush(stdout); fgets(cmd, MAX_CMD_LEN, stdin); if ((str = strchr(cmd, '\n')) != NULL) *str = '\0'; check_quit(cmd); if (putmsg(cmd) == -1) exit_sys("putmsg"); if (get_server_msg(g_fdpc, &smsg) == -1) exit_sys("get_client_msg"); parse_msg(smsg.msg, &msgc); for (i = 0; g_msg_proc[i].msg_cmd != NULL; ++i) if (!strcmp(msgc.msg_cmd, g_msg_proc[i].msg_cmd)) { if (g_msg_proc[i].proc(msgc.msg_param) == -1) { fprintf(stderr, "command failed!\n"); exit(EXIT_FAILURE); } break; } if (g_msg_proc[i].msg_cmd == NULL) { /* command not found */ fprintf(stderr, "Fatal Error: Unknown server message!\n"); exit(EXIT_FAILURE); } } return 0; } void sigpipe_handler(int sno) { printf("server down, exiting...\n"); exit(EXIT_FAILURE); } int putmsg(const char *cmd) { CLIENT_MSG cmsg; int i, k; for (i = 0; isspace(cmd[i]); ++i) ; for (k = 0; !isspace(cmd[i]); ++i) cmsg.msg[k++] = cmd[i]; cmsg.msg[k++] = ' '; for (; isspace(cmd[i]); ++i) ; for (; (cmsg.msg[k++] = cmd[i]) != '\0'; ++i) ; cmsg.msglen = (int)strlen(cmsg.msg); cmsg.client_id = g_client_id; if (write(g_fdps, &cmsg, 2 * sizeof(int) + cmsg.msglen) == -1) return -1; return 0; } int get_server_msg(int fdp, SERVER_MSG *smsg) { if (read(fdp, &smsg->msglen, sizeof(int)) == -1) return -1; if (read(fdp, smsg->msg, smsg->msglen) == -1) return -1; smsg->msg[smsg->msglen] = '\0'; return 0; } void parse_msg(char *msg, MSG_CONTENTS *msgc) { int i; msgc->msg_cmd = msg; for (i = 0; msg[i] != ' ' && msg[i] != '\0'; ++i) ; msg[i++] = '\0'; msgc->msg_param = &msg[i]; } void check_quit(char *cmd) { int i, pos; for (i = 0; isspace(cmd[i]); ++i) ; pos = i; for (; !isspace(cmd[i]) && cmd[i] != '\0'; ++i) ; if (!strncmp(&cmd[pos], "quit", pos - i)) strcpy(cmd, "DISCONNECT_REQUEST"); } int connect_to_server(void) { char name[MAX_PIPE_PATH]; char cmd[MAX_CMD_LEN]; char *str; SERVER_MSG smsg; MSG_CONTENTS msgc; int response; printf("Pipe name:"); fgets(name, MAX_PIPE_PATH, stdin); if ((str = strchr(name, '\n')) != NULL) *str = '\0'; if (access(name, F_OK) == 0) { do { printf("Pipe already exists! Overwrite? (Y/N)"); fflush(stdout); response = tolower(getchar()); clear_stdin(); if (response == 'y' && remove(name) == -1) return -1; } while (response != 'y' && response != 'n'); if (response == 'n') return -1; } sprintf(cmd, "CONNECT %s", name); if (putmsg(cmd) == -1) return -1; /* * "Server" ın "Client" a haber göndereceği boru * "Server" tarafından oluşturulacak. Boru hayata gelene * kadar bizler beklemede kalıyoruz. */ while (access(name, F_OK) != 0) usleep(300); if ((g_fdpc = open(name, O_RDONLY)) == -1) return -1; if (get_server_msg(g_fdpc, &smsg) == -1) exit_sys("get_client_msg"); parse_msg(smsg.msg, &msgc); if (strcmp(msgc.msg_cmd, "CONNECTED")) return -1; g_client_id = (int)strtol(msgc.msg_param, NULL, 10); printf("Connected server with '%d' id...\n", g_client_id); return 0; } int cmd_response_proc(const char *msg_param) { printf("%s\n", msg_param); return 0; } int disconnect_accepted_proc(const char *msg_param) { if (putmsg("DISCONNECT") == -1) exit_sys("putmsg"); exit(EXIT_SUCCESS); return 0; } int invalid_command_proc(const char *msg_param) { printf("invalid command: %s\n", msg_param); return 0; } void clear_stdin(void) { int ch; while ((ch = getchar()) != '\n' && ch != EOF) ; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } > Blokesiz Mod Kavramı & "Non-blocking" Boru İşlemleri: Bizler "open" fonksiyonunu işlerken "O_NONBLOCK" bayrağından bahsetmemiştik. Aygıt dosyaları, boru dosyaları gibi özel dosyaları "open" ile açarken "O_NONBLOCK" açış modunu da ekleyerek açmamız durumunda ilgili proses bloke EDİLMEYECEKTİR. "regular" dosyalar üzerinde bu bayrağın bir işlevi yoktur. Pekiyi nedir bloke edilmemek? Anımsanacağınız üzere bir borudan okuma yapmak istesek ama boru boş olsa ve biz "read" fonksiyonunu çağırdığımız zaman prosesimiz bloke edilecektir. İşte isimli boruları bu bayrak ile açarsak, prosesimiz blokeye düşmeden "read" fonksiyonumuz başarısızlıkla geri dönecektir. Benzer durum "write" fonksiyonu ile yazma için de geçerlidir. Fakat "write" fonksiyonu ile blokesiz modda "Partial Write" gerçekleşebilir. Öte yandan isimsiz boru haberleşmesi yaparken boruyu bizler açmadığımız için, bu bayrağı kullanmak istiyorsak "fcntl" fonksiyonunu kullanmalıyız. Çünkü isimsiz boru haberleşmesinde alt ve üst proses haberleşmesi için "open" fonksiyonu KULLANILMAMAKTADIR. Kullanılan "pipe" fonksiyonu da varsayılan durumda "blocking" moddadır. Özetle bloke oluşacak ise ilgili fonksiyon bloke oluşturmamakta, başarısız olmaktadır. Böylesi bir başarısızlık durumunda "errno" değişkeni "EAGAIN" değerini almaktadır. UNUTULMAMALIDIR Kİ VARSAYILAN DURUM "blocking" DURUMUDUR. Aşağıda isimsiz borular üzerinde "non-blocking" bir uygulamalar geliştirilmiştir. * Örnek 1, Aşağıda başarısızlık durumunda ilgili "thread" ler sonlanmaktadır. #include #include #include #include #include void exit_sys(const char *msg); int main(int argc, char** argv) { /* # OUTPUT # read: Resource temporarily unavailable */ /* * 0. Index: Read * 1. Index: Write */ int pipefds[2]; if(pipe(pipefds) == -1) exit_sys("pipe"); /* * Okuma yapılacak taraf artık "non-blocking" modda. Artık boruda okunacak * en az bir bayt varsa okunacak, aksi halde "read" fonksiyonu başarısız * olacaktır. */ if(fcntl(pipefds[0], F_SETFL, fcntl(pipefds[0], F_GETFL) | O_NONBLOCK) == -1) exit_sys("fcntl"); /* * Yazma yapılacak taraf artık "non-blocking" modda. Artık boruda yer kalmadığında * "write" fonksiyonu başarısız olacaktır. */ if(fcntl(pipefds[1], F_SETFL, fcntl(pipefds[1], F_GETFL) | O_NONBLOCK) == -1) exit_sys("fcntl"); pid_t pid; if((pid = fork()) == -1) exit_sys("fork"); if(pid != 0) /* Parent will write to the pipe. */ { close(pipefds[0]); /* * Üst proses mahsus bir saniye bloke edilmiştir çünkü "sleep" * fonksiyonu ilgili "thread" in blokesine yol açar. Geçen bu zamanda * alt prosesteki "read", boru boş olduğu için başarısız olacaktır. */ sleep(1); for(int i = 0; i < 10; ++i) if(write(pipefds[1], &i, sizeof(int)) == -1) exit_sys("write"); close(pipefds[1]); if(wait(NULL) == -1) exit_sys("wait"); } else /* Child will read from the pipe. */ { close(pipefds[1]); ssize_t result; int read_val; while((result = read(pipefds[0], &read_val, sizeof(int))) > 0) { printf("%d ", read_val); fflush(stdout); } if(result == -1) { /* * "fork" öncesi bizler tamponlu işler yapmışsak, "fork" sonrasında * bu tamponlar da alt prosese aktarılır. Dolayısıyla o tamponlar da * "flush" edilir. Fakat bu programda böyle bir işlem yapılmadığı için * "_exit()" ile değil "exit()" ile çıkılmıştır. */ exit_sys("read"); } close(pipefds[0]); } return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2, Aşağıdaki programda başarısızlık durumunda ilgili boruların son durumuna tekrar tekrar bakılmaktadır. #include #include #include #include #include #include void exit_sys(const char *msg); int main(int argc, char** argv) { /* # OUTPUT # 1 2 ... >>> Child process: background processing... <<< >>> Parent process: background processing... <<< >>> Child process: background processing... <<< >>> Child process: background processing... <<< ... 100000 */ /* * 0. Index: Read * 1. Index: Write */ int pipefds[2]; if(pipe(pipefds) == -1) exit_sys("pipe"); /* * Okuma yapılacak taraf artık "non-blocking" modda. Artık boruda okunacak * en az bir bayt varsa okunacak, aksi halde "read" fonksiyonu başarısız * olacaktır. */ if(fcntl(pipefds[0], F_SETFL, fcntl(pipefds[0], F_GETFL) | O_NONBLOCK) == -1) exit_sys("fcntl"); /* * Yazma yapılacak taraf artık "non-blocking" modda. Artık boruda yer kalmadığında * "write" fonksiyonu başarısız olacaktır. */ if(fcntl(pipefds[1], F_SETFL, fcntl(pipefds[1], F_GETFL) | O_NONBLOCK) == -1) exit_sys("fcntl"); pid_t pid; if((pid = fork()) == -1) exit_sys("fork"); if(pid != 0) /* Parent will write to the pipe. */ { close(pipefds[0]); for(int i = 0; i < 100000;) { if(write(pipefds[1], &i, sizeof(int)) == -1) { if(errno == EAGAIN) { /* * Borunun dolduğu anlaşılmaktadır. Arka planda bir takım * işler yapıldığı varsayılmıştır. Belli aralıklar ile boru * kontrol edilecektir. */ printf(">>> Parent process: background processing... <<<\n"); usleep(500); continue; } else exit_sys("write"); } ++i; } close(pipefds[1]); if(wait(NULL) == -1) exit_sys("wait"); } else /* Child will read from the pipe. */ { close(pipefds[1]); ssize_t result; int read_val; for(;;) { if((result = read(pipefds[0], &read_val, sizeof(int))) == 0) break; if(result == -1) if(errno == EAGAIN) { /* * Borunun boş anlaşılmaktadır. Arka planda bir takım * işler yapıldığı varsayılmıştır. Belli aralıklar ile boru * kontrol edilecektir. */ printf(">>> Child process: background processing... <<<\n"); usleep(500); continue; } else exit_sys("read"); // printf("%d\n", read_val); } printf("\n"); close(pipefds[0]); } return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } Anımsanacağı üzere, isimli borularda, "open" fonksiyonu blokeli modda çalıştığında, ilgili borunun ters amaçla açılması gerçekleşene kadar ilgili "thread" i bloke etmekteydi. Eğer "open" fonksiyonuna "O_NONBLOCK" bayrağını da geçersek artık blokeye yol açmayacaktır. Şimdi bu bayrağın "open" sırasında kullanılması şöyle bir takım problemlere yol açacaktır: -> Eğer "O_NONBLOCK" bayrağı kullanılmasaydı, "read" yapacak olan proses, "open" fonksiyon çağrısı sırasında bloke olacaktı. Bu bayrak kullanıldığı için programın akışı "open" fonksiyonundan çıkacaktır eğer karşı taraf da "write" modda açmamış ise. Eğer bu aşamada "read" yapılırsa ve o boruya yazma potansiyeli olan bir "fd" de yok ise "read" fonksiyonu "0" ile geri dönecektir. -> Eğer "O_NONBLOCK" bayrağı kullanılmasaydı, "write" yapacak olan proses, "open" fonksiyon çağrısı sırasında bloke olacaktı. Bu bayrak kullanıldığı için "open" fonksiyonu başarısız olacaktır eğer o an karşı taraf boruyu "read" modda açmamış ise. Ek olarak "errno" değişkeni de "ENXIO" değerini alacaktır. Eğer "open" fonksiyonunun bu başarısızlığını kontrol etmez ve "write" yapmaya çalışırsak sinyal oluşacaktır. Dolayısıyla her iki prosesin de kullanacakları boruyu eş zamanlı açmaları gerekmektedir. Bunu sağlamak için de bizler bir senkronizasyon mekanizması geliştirmeliyiz. Örneğin, -> Proseslerden birisi "blocking" diğeri "non-blocking" modda olursa problem çözülecektir. -> Proseslerden birisini "sleep" benzeri fonksiyonlar ile bloke edebiliriz. O proses blokeli bir şekilde beklerken, karşı taraf işini halledeceği için, problem ortadan kalkacaktır. -> Proseslerin her ikisi de boruyu blokeli modda açtıktan sonra "fcntl" fonksiyonu ile modunu blokesiz hale getirmek. Aşağıda bu hususa ilişkin örnekler verilmiştir: * Örnek 1, Aşağıdaki her iki proses de boruyu blokesiz modda açmıştır. Fakat "read" yapacak olan "getchar()" ile bloke edilmiştir. Böylelikle "write" yapan boruyu açtıktan sonra bizler "read" yapacak olanın blokesini kaldırmalıyız. /* write */ #include "stdio.h" #include #include #include #include void exit_sys(const char*); int main(int argc, char** argv) { int fdpipe; if((fdpipe = open("mypipe", O_RDONLY | O_NONBLOCK)) == -1) exit_sys("open"); for(int i = 0; i < 100000;) { if(write(fdpipe, &i, sizeof(int)) == -1) { if(errno == EAGAIN) { printf(">>> Parent process: background processing... <<<\n"); usleep(500); continue; } else exit_sys("write"); } ++i; } close(fdpipe); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } /* read */ #include "stdio.h" #include #include #include #include void exit_sys(const char*); int main(int argc, char** argv) { int fdpipe; if((fdpipe = open("mypipe", O_WRONLY | O_NONBLOCK)) == -1) exit_sys("open"); getchar(); ssize_t result; int read_val; for(;;) { if((result = read(fdpipe, &read_val, sizeof(int))) == 0) break; if(result == -1) if(errno == EAGAIN) { /* * Borunun boş anlaşılmaktadır. Arka planda bir takım * işler yapıldığı varsayılmıştır. Belli aralıklar ile boru * kontrol edilecektir. */ printf(">>> Child process: background processing... <<<\n"); usleep(500); continue; } else exit_sys("read"); // printf("%d\n", read_val); } printf("\n"); close(fdpipe); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2, Aşağıdaki her iki proses de boruyu blokeli modda açtıktan sonra modları blokesiz hale getirilmiştir. Artık "open" sırasında bloke olmakta, "read"/"write" sırasında bloke OLMAMAKTADIRLAR. /* write */ #include "stdio.h" #include #include #include #include void exit_sys(const char*); int main(int argc, char** argv) { int fdpipe; if((fdpipe = open("mypipe", O_RDONLY)) == -1) exit_sys("open"); if(fcntl(fdpipe, F_SETFL, fcntl(fdpipe, F_GETFL) | O_NONBLOCK) == -1) exit_sys("fcntl"); for(int i = 0; i < 100000;) { if(write(fdpipe, &i, sizeof(int)) == -1) { if(errno == EAGAIN) { printf(">>> Parent process: background processing... <<<\n"); usleep(500); continue; } else exit_sys("write"); } ++i; } close(fdpipe); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } /* read */ #include "stdio.h" #include #include #include #include void exit_sys(const char*); int main(int argc, char** argv) { int fdpipe; if((fdpipe = open("mypipe", O_WRONLY)) == -1) exit_sys("open"); if(fcntl(fdpipe, F_SETFL, fcntl(fdpipe, F_GETFL) | O_NONBLOCK) == -1) exit_sys("fcntl"); ssize_t result; int read_val; for(;;) { if((result = read(fdpipe, &read_val, sizeof(int))) == 0) break; if(result == -1) if(errno == EAGAIN) { /* * Borunun boş anlaşılmaktadır. Arka planda bir takım * işler yapıldığı varsayılmıştır. Belli aralıklar ile boru * kontrol edilecektir. */ printf(">>> Child process: background processing... <<<\n"); usleep(500); continue; } else exit_sys("read"); // printf("%d\n", read_val); } printf("\n"); close(fdpipe); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } Bir takım kabuk programlarını da boru haberleşmesi yöntemiyle haberleştirebiliriz. Örneğin, bir "shell" programına aşağıdaki komutu girelim: ls > mypipe Burada "mypipe" isimli boru dosyasının halihazırda var olduğu varsayılmıştır. Artık "ls" programı "stdout" a değil "mypipe" yazacaktır. Varsayılan ayarda "open" fonksiyonu blokeli olduğu için, bu "shell" programı aynı boru "read" amacıyla açılana kadar bloke edilecektir. Şimdi de şu aşağıdaki komutu başka bir "shell" programına girelim: cat < mypipe Normalde "cat" programı "stdin" den okuduklarını "stdout" a yazmaktadır. Artık "mypipe" den okuduklarını "stdout" a yazacaktır. > Hatırlatıcı Notlar: >> İnteraktif olmayan bir biçimde "shell" programını çalıştırmak için "-c" seçeneği kullanılır. >> İnteraktif olmayan bir biçimde çalıştırılan "shell" programlarının "exit" kodları, aslında çalıştırılan "shell" komutlarının "exit" kodlarıdır. >> Boru dosyalarının "O_RDWR" modunda açılması Linux sistemlerince tanımlı olan, fakat POSIX standartlarınca "unspecified" olarak nitelenen bir durumdur. >> "mkfifo" kabuk komutunu kullanırken, "-m" seçeneğini kullanırsak, oluşturulacak boru dosyasının erişim haklarını önceden belirleyebiliriz. >> Sıralı arama yapmak, eleman sayısının 20'yi geçmediği durumlarda, gayet de verimli bir arama yöntemidir. >> POSIX'te borular, aynı makinedeki prosesler arasında haberleşme için kullanılmaktadır. >> Daha önceki derste "Client-Server" için bir tasarım kalıbı olduğundan bahsetmiştik. Bizler burada boruları kullandığımız için aynı makinedeki iki prosesin haberleşmesini gerçekleştireceğiz. Farklı makinedeki proseslerin haberleşmesi için başka ortamlar kullanmamız gerekmektedir eğer "Client-Server" mimarisini kullanacaksak. Her ne kadar "Client-Server" dendiğinde akla "TCP-IP Socket" programlaması gelsede, özünde bir tasarım kalıbıdır. Bu haberleşme mimarisinde esas işi yapan taraf "Server" tarafıdır. Fakat istekte bulunan taraf "Client" tarafıdır. >> Boru oluşturduktan sonra "cat > x" komutunu çalıştırmamız, "cat" komutunun çıktılarının "x" borusuna yazılacağını belirtir.