标题：FIFO管道
概述
FIFO中可以很好地解决在无关进程间数据交换的要求，并且由于它们是存在于文件系统中的，这也提供了一种比匿名管道更持久稳定的通信办法。
FIFO的通信方式类似于在进程中使用文件来传输数据，只不过FIFO类型文件同时具有管道的特性。在数据读出时，FIFO管道中同时清除数据。在shell中mkfifo命令可以建立有名管道，下面通过一个实例来帮助读者理解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要阻塞到有进程为读打开FIFOFIFO的写操作规则类似于匿名管道的写操作规则，当没有进程为读打开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的时候，如果仅以读打开，则当所有的客户端都退出时，服务器端会读取到文件结束符。这个问题的解决办法是服务器以读写打开公共FIFO，如图2所示。服务器与客户端如何实现互相通信。
图2 FIFO在客户端与服务器通信的应用2