PHP多進程編程(三) 管道通信2

上一節介紹了管道基本概念和無名管道,這一節來看看有名管道。


有名管道概述及相關API應用


2.1 有名管道相關的關鍵概念


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


2.2有名管道的創建

#include <sys/types.h>

#include <sys/stat.h>

int mkfifo(const char * pathname, mode_t mode)

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

2.3有名管道的打開規則

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

FIFO的打開規則:

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

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


2.4有名管道的讀寫規則

從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讀寫規則進行驗證。

程序1:寫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)

//參數爲即將寫入的字節數

{

int fd;

char w_buf[4096*2];

int real_wnum;

memset(w_buf,0,4096*2);

if((mkfifo(FIFO_SERVER,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST))

printf("cannot create fifoserver\n");

if(fd==-1)

if(errno==ENXIO)

printf("open error; 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("write to fifo error; try later\n");

}

else 

printf("real write 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("try later\n");

}

程序2:與程序1一起測試寫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)

{

char r_buf[4096*2];

int  fd;

int  r_size;

int  ret_size;

r_size=atoi(argv[1]);

printf("requred real 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("no data avlaible\n");

printf("real read bytes %d\n",ret_size);

sleep(1);

}

pause();

unlink(FIFO_SERVER);

}

程序應用說明:

把讀程序編譯成兩個不同版本:

  • 阻塞讀版本:br

  • 以及非阻塞讀版本nbr

把寫程序編譯成兩個四個版本:

  • 非阻塞且請求寫的字節數大於PIPE_BUF版本:nbwg

  • 非阻塞且請求寫的字節數不大於PIPE_BUF版本:版本nbw

  • 阻塞且請求寫的字節數大於PIPE_BUF版本:bwg

  • 阻塞且請求寫的字節數不大於PIPE_BUF版本:版本bw

下面將使用br、nbr、w代替相應程序中的阻塞讀、非阻塞讀

驗證阻塞寫操作:

當請求寫入的數據量大於PIPE_BUF時的非原子性:

  • nbr 1000

  • bwg

當請求寫入的數據量不大於PIPE_BUF時的原子性:

  • nbr 1000

  • bw

驗證非阻塞寫操作:

當請求寫入的數據量大於PIPE_BUF時的非原子性:

  • nbr 1000

  • nbwg

請求寫入的數據量不大於PIPE_BUF時的原子性:

  • nbr 1000

  • nbw

不管寫打開的阻塞標誌是否設置,在請求寫入的字節數大於4096時,都不保證寫入的原子性。但二者有本質區別:

對於阻塞寫來說,寫操作在寫滿FIFO的空閒區域後,會一直等待,直到寫完所有數據爲止,請求寫入的數據最終都會寫入FIFO;

而非阻塞寫則在寫滿FIFO的空閒區域後,就返回(實際寫入的字節數),所以有些數據最終不能夠寫入。

對於讀操作的驗證則比較簡單,不再討論。


2.5有名管道應用實例

在驗證了相應的讀寫規則後,應用實例似乎就沒有必要了。


小結:

管道常用於兩個方面:(1)在shell中時常會用到管道(作爲輸入輸入的重定向),在這種應用方式下,管道的創建對於用戶來說是透明的;(2)用於具有親緣關係的進程間通信,用戶自己創建管道,並完成讀寫操作。

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

管道和FIFO的數據是字節流,應用程序之間必須事先確定特定的傳輸"協議",採用傳播具有特定意義的消息。

要靈活應用管道及FIFO,理解它們的讀寫規則是關鍵。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章