Linux系統程式設計(11)——程式間通訊之有名管道

尹成發表於2014-07-25

 

管道應用的一個重大限制是它沒有名字,因此,只能用於具有親緣關係的程式間通訊,在有名管道(named pipe或FIFO)提出後,該限制得到了克服。FIFO不同於管道之處在於它提供一個路徑名與之關聯,以FIFO的檔案形式存在於檔案系統中。這樣,即使與FIFO的建立程式不存在親緣關係的程式,只要可以訪問該路徑,就能夠彼此通過FIFO相互通訊(能夠訪問該路徑的程式以及FIFO的建立程式之間),因此,通過FIFO不相關的程式也能交換資料。值得注意的是,FIFO嚴格遵循先進先出(first in first out),對管道及FIFO的讀總是從開始處返回資料,對它們的寫則把資料新增到末尾。它們不支援諸如lseek()等檔案定位操作。

 

有名管道的建立

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char * pathname, mode_tmode)


該函式的第一個引數是一個普通的路徑名,也就是建立後FIFO的名字。第二個引數與開啟普通檔案的open()函式中的mode 引數相同。如果mkfifo的第一個引數是一個已經存在的路徑名時,會返回EEXIST錯誤,所以一般典型的呼叫程式碼首先會檢查是否返回該錯誤,如果確實返回該錯誤,那麼只要呼叫開啟FIFO的函式就可以了。一般檔案的I/O函式都可以用於FIFO,如close、read、write等等。

 

有名管道的開啟規則

有名管道比管道多了一個開啟操作:open。

FIFO的開啟規則:

如果當前開啟操作是為讀而開啟FIFO時,若已經有相應程式為寫而開啟該FIFO,則當前開啟操作將成功返回;否則,可能阻塞直到有相應程式為寫而開啟該FIFO(當前開啟操作設定了阻塞標誌);或者,成功返回(當前開啟操作沒有設定阻塞標誌)。

如果當前開啟操作是為寫而開啟FIFO時,如果已經有相應程式為讀而開啟該FIFO,則當前開啟操作將成功返回;否則,可能阻塞直到有相應程式為讀而開啟該FIFO(當前開啟操作設定了阻塞標誌);或者,返回ENXIO錯誤(當前開啟操作沒有設定阻塞標誌)。

對開啟規則的驗證參見附2。

 

有名管道的讀寫規則

從FIFO中讀取資料:

約定:如果一個程式為了從FIFO中讀取資料而阻塞開啟FIFO,那麼稱該程式內的讀操作為設定了阻塞標誌的讀操作。

如果有程式寫開啟FIFO,且當前FIFO內沒有資料,則對於設定了阻塞標誌的讀操作來說,將一直阻塞。對於沒有設定阻塞標誌讀操作來說則返回-1,當前errno值為EAGAIN,提醒以後再試。

對於設定了阻塞標誌的讀操作說,造成阻塞的原因有兩種:當前FIFO內有資料,但有其它程式在讀這些資料;另外就是FIFO內沒有資料。解阻塞的原因則是FIFO中有新的資料寫入,不論信寫入資料量的大小,也不論讀操作請求多少資料量。

讀開啟的阻塞標誌只對本程式第一個讀操作施加作用,如果本程式內有多個讀操作序列,則在第一個讀操作被喚醒並完成讀操作後,其它將要執行的讀操作將不再阻塞,即使在執行讀操作時,FIFO中沒有資料也一樣(此時,讀操作返回0)。

如果沒有程式寫開啟FIFO,則設定了阻塞標誌的讀操作會阻塞。

注:如果FIFO中有資料,則設定了阻塞標誌的讀操作不會因為FIFO中的位元組數小於請求讀的位元組數而阻塞,此時,讀操作會返回FIFO中現有的資料量。

 

向FIFO中寫入資料:

約定:如果一個程式為了向FIFO中寫入資料而阻塞開啟FIFO,那麼稱該程式內的寫操作為設定了阻塞標誌的寫操作。

 

對於設定了阻塞標誌的寫操作:

當要寫入的資料量不大於PIPE_BUF時,linux將保證寫入的原子性。如果此時管道空閒緩衝區不足以容納要寫入的位元組數,則進入睡眠,直到當緩衝區中能夠容納要寫入的位元組數時,才開始進行一次性寫操作。

當要寫入的資料量大於PIPE_BUF時,linux將不再保證寫入的原子性。FIFO緩衝區一有空閒區域,寫程式就會試圖向管道寫入資料,寫操作在寫完所有請求寫的資料後返回。

 

對於沒有設定阻塞標誌的寫操作:

當要寫入的資料量大於PIPE_BUF時,linux將不再保證寫入的原子性。在寫滿所有FIFO空閒緩衝區後,寫操作返回。

當要寫入的資料量不大於PIPE_BUF時,linux將保證寫入的原子性。如果當前FIFO空閒緩衝區能夠容納請求寫入的位元組數,寫完後成功返回;如果當前FIFO空閒緩衝區不能夠容納請求寫入的位元組數,則返回EAGAIN錯誤,提醒以後再寫;

 

對FIFO讀寫規則的驗證:

下面的兩個程式說明了對FIFO的讀寫規則。

 

寫FIFO的程式:

#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#define FIFO_SERVER"/tmp/fifoserver"
main(int argc,char** argv)
//引數為即將寫入的位元組數
{
         intfd;
         charw_buf[4096*2];
         intreal_wnum;
         memset(w_buf,0,4096*2);
         if((mkfifo(FIFO_SERVER,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST))
                   printf("cannotcreate fifoserver\n");
         if(fd==-1)
                   if(errno==ENXIO)
                            printf("openerror; no reading process\n");
                  
            fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0);
         //設定非阻塞標誌
         //fd=open(FIFO_SERVER,O_WRONLY,0);
         //設定阻塞標誌
         real_wnum=write(fd,w_buf,2048);
         if(real_wnum==-1)
         {
                   if(errno==EAGAIN)
                            printf("writeto fifo error; try later\n");
         }
         else
                   printf("realwrite num is %d\n",real_wnum);
         real_wnum=write(fd,w_buf,5000);
         //5000用於測試寫入位元組大於4096時的非原子性
         //real_wnum=write(fd,w_buf,4096);
         //4096用於測試寫入位元組不大於4096時的原子性
        
         if(real_wnum==-1)
                   if(errno==EAGAIN)
                            printf("trylater\n");
}


 

與上一個程式一起測試寫FIFO的規則,第一個命令列引數是請求從FIFO讀出的位元組數

#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#define FIFO_SERVER"/tmp/fifoserver"
main(int argc,char** argv)
{
         charr_buf[4096*2];
         int  fd;
         int  r_size;
         int  ret_size;
         r_size=atoi(argv[1]);
         printf("requredreal read bytes %d\n",r_size);
         memset(r_buf,0,sizeof(r_buf));
         fd=open(FIFO_SERVER,O_RDONLY|O_NONBLOCK,0);
         //fd=open(FIFO_SERVER,O_RDONLY,0);
         //在此處可以把讀程式編譯成兩個不同版本:阻塞版本及非阻塞版本
         if(fd==-1)
         {
                   printf("open%s for read error\n");
                   exit();       
         }
         while(1)
         {
                  
                   memset(r_buf,0,sizeof(r_buf));
                   ret_size=read(fd,r_buf,r_size);
                   if(ret_size==-1)
                            if(errno==EAGAIN)
                                     printf("nodata avlaible\n");
                   printf("realread bytes %d\n",ret_size);
                   sleep(1);
         }       
         pause();
         unlink(FIFO_SERVER);
}


 

管道常用於兩個方面:

1、在shell中時常會用到管道(作為輸入輸入的重定向),在這種應用方式下,管道的建立對於使用者來說是透明的;

2、用於具有親緣關係的程式間通訊,使用者自己建立管道,並完成讀寫操作。

FIFO可以說是管道的推廣,克服了管道無名字的限制,使得無親緣關係的程式同樣可以採用先進先出的通訊機制進行通訊。

管道和FIFO的資料是位元組流,應用程式之間必須事先確定特定的傳輸"協議",採用傳播具有特定意義的訊息。


 

 

 

 

 

 

 

相關文章