14.3 FIFO管道
FIFO也称为有名管道,它是一种文件类型,在文件系统中可以看到。程序中可以查看文件stat结构中st_mode成员的值来判断文件是否是FIFO文件。创建一个FIFO文件类似于创建文件,FIFO文件就像普通文件一样。本小节将介绍FIFO管道。
14.3.1 FIFO的概念
在本章14.2.2小节详细说明了匿名管道的缺点以及限制条件,在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读出数据。
14.3.2 创建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文件的特性,所以它被隐性地规定不具有执行权限。
(1)在vi编辑器中编辑该程序如下:
程序清单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。
14.3.3 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接口,如图14-5所示。
图14-5 FIFO在客户端与服务器通信的应用1
14.3.4 FIFO的缺点
当然FIFO也有它的局限性,如图14-6所示。客户端可以发请求到服务器,但前提是要知道一个公共的FIFO通道,对于实现服务器回传应答到客户端的问题,可以通过为每一个客户端创建一个专用的FIFO,来实现回传应答。但也有不足,服务器会同时应答成千上万个客户端,创建如此多的FIFO是否会使系统负载过大,相应的如何判断客户端是否因意外而崩溃成为难题,或者客户端不读取应答直接退出,所以服务器必须处理SIGPIPE信号,并做相应处理。
%说明:在服务器端打开公共FIFO的时候,如果仅以读打开,则当所有的客户端都退出时,服务器端会读取到文件结束符。这个问题的解决办法是服务器以读写打开公共FIFO,如图14-6所示。服务器与客户端如何实现互相通信。
图14-6 FIFO在客户端与服务器通信的应用2