複製鏈接
請複製以下鏈接發送給好友

FIFO管道

鎖定
它是一種文件類型,在文件系統中可以看到。程序中可以查看文件stat結構中st_mode成員的值來判斷文件是否是FIFO文件。創建一個FIFO文件類似於創建文件,FIFO文件就像普通文件一樣。
中文名
FIFO管道
別    名
有名管道
優    點
更持久穩定
通信方式
在進程中使用文件來傳輸數據

FIFO管道文件概括

FIFO中可以很好地解決在無關進程間數據交換的要求,並且由於它們是存在於文件系統中的,這也提供了一種比匿名管道更持久穩定的通信辦法。
FIFO的通信方式類似於在進程中使用文件來傳輸數據,只不過FIFO類型文件同時具有管道的特性。在數據讀出時,FIFO管道中同時清除數據。在shell中mkfifo命令可以建立有名管道,下面通過一個實例來幫助讀者理解FIFO。

FIFO管道相關指令

mkfifo命令的幫助手冊如下所示:
mkfifo [option] name...
其中option選項中可以選擇要創建FIFO的模式,使用形式為-m mode,這裏mode指出將要創建FIFO的八進制模式,注意,這裏新創建的FIFO會像普通文件一樣受到創建進程的umask修正。在shell中輸入命令如下:
$mkfifo –m 600 fifocat
$cat <fifocat
$./recat > fifocat
$./recat > fifocat
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <limits.h>
#define BUFES PIPE_BUF
int main ( void )
{
FILE *fp;
char * cmd = "cat file1"; /*shell 命令*/
char * buf[BUFSZ];
...
...
...
pclose ( fp ) ; /*關閉管道*/
exit (0) ;
}
$_
以上實例使用系統命令mkfifo創建FIFO類型文件fifocat,並通過14.2.4節的程序recat來讀取文件recat.c,將程序的標準輸出從定向到fifocat中,再使用命令cat從fifocat讀出數據。

FIFO管道文件創建

創建一個FIFO文件類似於創建文件,FIFO文件就像普通文件一樣,也是可以經過路徑名來訪問的。相應文件stat結構的域st_mode的編碼指明瞭文件是否是FIFO類型。FIFO管道通過函數mkfifo創建,函數原型如下:
#include <sys/stat.h>
#include <sys/types.h>
int mkfifo( const char * filename, mode_t mode );
mkfifo函數中參數mode指定FIFO的讀寫權限,新創建FIFO的用户ID和組ID規則域open函數相同。參數filename指定新創建FIFO的文件名稱。函數如果成功返回0,出 錯返回–1,並更改errno的值。errno有可能出現的值為:EACCESS、EEXIST、ENAMETOO- LONG、ENOENT、ENOSPE、ENOTDIR和EROFS。
下面實例演示瞭如何使用mkfifo函數來創建一個FIFO。程序中從程序的命令行參數中得到一個文件名,然後使用mkfifo函數創建FIFO文件。新創建的FIFO只具有讀寫權限。由於FIFO文件的特性,所以它被隱性地規定不具有執行權限。
程序清單14-5 create_fifo.c 使用mkfifo函數創建FIFO管道
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
int main (int argc, char *argv[] )
{
mode_t mode = 0666; /*新創建的FIFO模式*/
if ( argc != 2 ){
/*向用户提示程序使用幫助*/
printf("USEMSG: create_fifo {fifoname}\n");
exit (1);
}
/* 使用mkfifo函數創建一個FIFO管道*/
if ( ( mkfifo (argv[1], mode )) < 0) {
perror ( "failed to mkfifo" );
exit ( 1 );
}
else
printf ("you successfully create a FIFO name is : %s\n", argv[1]);
/* 輸出FIFO文件的名稱 */
exit (0);
}
(2)在shell中編譯該程序如下:
$gcc create_fifo.c–o create_fifo
(3)在shell中運行該程序如下:
$./ create_fifo
USEMSG: create_fifo {fifoname}
輸入正確的命令符。
$./ create_fifo fifo1
you successfully create a FIFO name is :fifo1
$./ create_fifo fifo1
mkfifo: File exists
上述程序使用mkfifo函數創建一個FIFO,名字是基於用户的輸入文件名,可以看到當要創建一個已經存在的FIFO時,程序會產生一個EEXIST的異常,相對應該異常,perror函數打印了相應的幫助信息為mkfifo: File exists。

FIFO管道讀寫操作

一般的I/O(open close read write unlink)函數都可以用於FIFO文件,需要注意的是,在使用open函數打開一個FIFO文件時,open函數參數flag標誌位的O_NONBLOCK標誌,它關係到函數的返回狀態。詳細説明如表14-2所示。
表14-2 open函數的flag(O_NONBLOCK)詳細説明
O_NONBLOCK標誌
詳 細 説 明
置位
只讀open立即返回。當只寫open時,如果沒有進程為讀打開FIFO,則返回–1,並置errno值為ENXIO
不置位
open視情況阻塞。只讀open要阻塞到有進程為寫打開FIFO,只寫open要阻塞到有進程為讀打開FIFO
FIFO的寫操作規則類似於匿名管道的寫操作規則,當沒有進程為讀打開FIFO,調用write函數來進行寫操作會產生信號SIGPIPE,則信號可以被捕捉或者完全忽略。
%注意:當FIFO的所有寫進程都已經關閉,則為FIFO的讀進程產生一個文件結束符。
FIFO的出現,極好地解決了系統在應用過程中產生的大量的中間臨時文件的問題。FIFO可以被shell調用使數據從一個進程到另一個進程,系統不必為該中間通道去煩惱清理不必要的垃圾,或者去釋放該通道的資源,它可以被留做後來的進程使用。並且規避了匿名管道在作用域的限制,可應用於不相關的進程之間。
下面實例演示了使用FIFO來進行兩個進程間通信的例子。在程序write_fifo.c中打開一個名為fifo1的FIFO文件,並分10次向這個FIFO中寫入數據。在程序read_fifo.c中先打開fifo1文件,讀取裏面的數據並輸出到標準輸出中。
vi編輯器中編輯該程序如下:
程序清單14-6 write_fifo.c 使用FIFO進行通信
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <limits.h>
#define BUFES PIPE_BUF
int main(void)
{
int fd ;
int n, i ;
char buf[BUFES];
time_t tp;
printf("I am %d\n",getpid()); /*説明進程的ID*/
if((fd=open("fifo1",O_WRONLY))<0){ /*以寫打開一個FIFO1*/
perror("open");
exit(1);
}
for ( i=0 ; i<10; i++){ /*循環10次向FIFO中寫入數據*/
time(&tp); /*取系統當前時間*/
/*使用sprintf 函數向buf中格式化寫入進程ID 和時間值*/
n=sprintf(buf,"write_fifo %d sends %s",getpid(),ctime(&tp));
printf("Send msg:%s\n",buf);
if((write(fd, buf, n+1))<0) { /*寫入到FIFO中*/
perror("write");
close(fd); /* 關閉FIFO文件 */
exit(1);
}
sleep(3); /*進程睡眠3秒*/
}
close(fd); /* 關閉FIFO文件 */
exit(0);
}
程序中使用open函數打開一個名為fifo1的FIFO管道,並分10次向fifo1中寫入字符串,其中的數據有當前進程ID以及寫入時的系統時間。並把這個數據串輸出到標準輸出,然後程序自動睡眠3秒。
(1)在vi編輯器中編輯該程序如下:
程序清單14-7 read_fifo.c 使用FIFO進行通信
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <fcntl.h>
#include <unistd.h>
#define BUFES PIPE_BUF
int main(void)
{
int fd;
int len;
char buf[BUFES];
mode_t mode = 0666; /* FIFO文件的權限 */
if((fd=open("fifo1",O_RDONLY))<0) /* 打開FIFO文件 */
{
perror("open");
exit(1);
}
while((len=read(fd,buf, BUFES))>0) /* 開始進行通信 */
printf("read_fifo read: %s",buf);
close(fd); /* 關閉FIFO文件 */
exit(0);
}
程序中使用open函數以讀方式打開一個名為fifo1的FIFO管道,並循環讀出管道的數據,這裏使用while循環的作用就是確保數據可以全部讀出,因為在讀FIFO管道數據時,默認的是一次性讀取PIPE_BUF個字節,當管道中數據多於PIPE_BUF個字節時,一次性讀出PIPE_BUF-1個字節,然後read函數返回,再打印數據到標準輸出。
(2)在shell中分別編譯上述兩個程序如下:
$gcc write_fifo.c–o write_fifo
$gcc read_fifo.c–o read_fifo
(3)在shell中使用mkfifo創建程序中將要用到的FIFO管道。
$mkfifo –m 666 fifo1
(4)打開兩個shell分別運行程序write_fifo 和程序 read_fifo。一個shell中輸入如下:
$./write_fifo
i am 3708
Send msg:write_fifo 3708 sends Thu Apr 17 18:26:01 2008
Send msg:write_fifo 3708 sends Thu Apr 17 18:26:04 2008
Send msg:write_fifo 3708 sends Thu Apr 17 18:26:07 2008
Send msg:write_fifo 3708 sends Thu Apr 17 18:26:10 2008
Send msg:write_fifo 3708 sends Thu Apr 17 18:26:13 2008
Send msg:write_fifo 3708 sends Thu Apr 17 18:26:16 2008
Send msg:write_fifo 3708 sends Thu Apr 17 18:26:19 2008
Send msg:write_fifo 3708 sends Thu Apr 17 18:26:22 2008
Send msg:write_fifo 3708 sends Thu Apr 17 18:26:25 2008
Send msg:write_fifo 3708 sends Thu Apr 17 18:26:28 2008
另一個shell中輸入如下:
$./read_fifo
read_fifo read: write_fifo 3708 sends Thu Apr 17 18:26:01 2008
read_fifo read: write_fifo 3708 sends Thu Apr 17 18:26:04 2008
read_fifo read: write_fifo 3708 sends Thu Apr 17 18:26:07 2008
read_fifo read: write_fifo 3708 sends Thu Apr 17 18:26:10 2008
read_fifo read: write_fifo 3708 sends Thu Apr 17 18:26:13 2008
read_fifo read: write_fifo 3708 sends Thu Apr 17 18:26:16 2008
read_fifo read: write_fifo 3708 sends Thu Apr 17 18:26:19 2008
read_fifo read: write_fifo 3708 sends Thu Apr 17 18:26:22 2008
read_fifo read: write_fifo 3708 sends Thu Apr 17 18:26:25 2008
read_fifo read: write_fifo 3708 sends Thu Apr 17 18:26:28 2008
上述例子可以擴展成客户端與服務器通信的實例,write_fifo的作用類似於客户端,可以打開多個客户端向一個服務器發送請求信息,read_fifo類似於服務器,它適時監控着FIFO的讀出端,當有數據時,讀出並進行處理,但是有一個關鍵的問題是,每一個客户端必須預先知道服務器提供的FIFO接口,如圖1所示。
圖1 FIFO在客户端與服務器通信的應用1

FIFO管道文件缺點

當然FIFO也有它的侷限性,如圖2所示。客户端可以發請求到服務器,但前提是要知道一個公共的FIFO通道,對於實現服務器回傳應答到客户端的問題,可以通過為每一個客户端創建一個專用的FIFO,來實現回傳應答。但也有不足,服務器會同時應答成千上萬個客户端,創建如此多的FIFO是否會使系統負載過大,相應的如何判斷客户端是否因意外而崩潰成為難題,或者客户端不讀取應答直接退出,所以服務器必須處理SIGPIPE信號,並做相應處理。
%説明:在服務器端打開公共FIFO的時候,如果以讀(O_RDONLY)打開,則當所有的客户端都退出時,服務器端會讀取到文件結束符(read返回值為0)。這個問題的解決辦法是服務器以讀寫(O_RDWR)打開公共FIFO,或者對read的返回值為0時進行特殊處理。如圖2所示。服務器與客户端如何實現互相通信。
圖2 FIFO在客户端與服務器通信的應用2