7.2.2有名管道(FIFO)
管道的一个很重要的问题是它没有名字,因此,只能用于具有亲缘关系的进程间通信,在有名管道(named
pipe或FIFO)提出后,这个问题就得到解决。FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信。值得注意的是,FIFO严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。
1、命令行下的有名管道
先来看一下在命令行下如何使用FIFO,主要使用命令mkfifo
#mkfifo /tmp/fifo
此命令在/tmp目录下面建立一个称为fifo的命名管道
使用命令ls –l
/tmp/fifo,可以得到与下面类似的结果:
prw-r--r-- 1 root root 0 3月 25 12:10 /tmp/fifo
然后输入命令
#cat /tmp/fifo
此命令用于输出FIFO的内容,这时这个终端就会挂起,等待FIFO的输出。
此时,打开另一个终端,例如ALT+F3用于打开3号终端。
在此输入
#echo This is fifo > /tmp/fifo
就可以发现刚才输入cat的命令有了输出。
#cat /tmp/fifo
This is fifo
可以明显的看出刚才的cat命令和echo命令不是父子进程,却能共享数据,这就是FIFO的应用实例。
2、编程中使用命令管道
在文件系统中创建一个命名管道(FIFO)使用函数mkfifo()
#include
#include
int mkfifo(const char * pathname, mode_t mode);
参数一 pathname指定了FIFO在文件系统中的路径;
参数二mode指定了FIFO的读写权限,与打开普通文件的open()函数中的mode 参数相同。mkfifo函数如果调用成功的话,返回值为0;如果调用失败返回值为-1。如果mkfifo的第一个参数是一个已经存在的路径名时,会返回-1,并且将系统错误代码errno设置成EEXIST,所以一般典型的代码会检查是否存在FIFO,如果通过errno判断存在此FIFO,那么只要调用打开FIFO的函数就可以了。一般文件的I/O函数都可以用于FIFO,如read、write、close等等。
注意点:如果当前打开操作是为读而打开FIFO时,若已经有相应进程为写而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为写而打开该FIFO(当前打开操作设置了阻塞标志);或者,成功返回(当前打开操作没有设置阻塞标志)。设置阻塞标记是在open函数的mode参数是否O_NONBLOCK(例如非阻塞读模式的话可以将mode设置成O_WRONLY|O_NONBLOCK)。
如果当前打开操作是为写而打开FIFO时,如果已经有相应进程为读而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为读而打开该FIFO(当前打开操作设置了阻塞标志);或者,返回ENXIO错误(当前打开操作没有设置阻塞标志)。
下面我们就利用此函数来实现刚才shell里mkfifo命令所演示的功能:
程序一、创建FIFO
/* mymkfifo.c */
#include
#include
#include
#include
#include
#define FIFO_FILE "/tmp/progfifo"
int main()
{
int ret;
ret = mkfifo(FIFO_FILE, O_CREAT | O_EXCL);
if (ret == 0) {
printf("Create FIFO
success:%s\n", FIFO_FILE);
} else if (errno == EEXIST) {
/*mkfifo函数返回-1且errno被设置成EEXIST,说明要创建的FIFO已存在*/
printf("Warning: FIFO %s already
exists.\n", FIFO_FILE);
} else {
printf("Create FIFO
fail:%s.\n", FIFO_FILE);
}
return 0;
}
使用命令gcc –o mymkfifo mymkfifo.c 编译程序
运行命令./mymkfifo ,可得到结果:
Create FIFO success:/tmp/progfifo.
这时使用命令 ls
–l /tmp/progfifo ,应该可以得到与下面相似的结果:
prw-r--r-- 1 root root 0 3月 25 17:40 /tmp/progfifo
说明我们创建FIFO已经成功了。
若再次运行命令
./mymkfifo,得到如下结果:
Warning: FIFO /tmp/progfifo already exists.
说明通过mkfifo函数的返回值和errno的值,我们判断出FIFO已存在的错误。
程序二、写数据到FIFO 中
/*wfifo.c */
#include
#include
#include
#include
#include
#define FIFO_FILE "/tmp/progfifo"
int main()
{
int fd;
char data[] = "Data in FIFO."; /* 将要写入FIFO的数据 */
fd = open(FIFO_FILE, O_WRONLY); /* 就像一般的文件一样打开FIFO文件
* 但注意这里以阻塞方式打开FIFO文件,若要非阻塞用O_WRONLY|O_NONBLOCK*/
*/
if (fd < 0) {
printf("Open FIFO fail:%s.\n",
FIFO_FILE);
exit(1);
}
write(fd, data, sizeof(data)); /* 向FIFO文件中写数据,此时程序会挂起,直到FIFO中的数据被读出 */
printf("Here, the data has been read
out.\n"); /* 如果运行到这行,说明写入的数据已经被全部取出,写进程继续运行了
*/
return (0);
}
因为读写关联较大,所以我们先不急着运行wfifo,先来看如何从FIFO数据中读数据。
程序三、从FIFO中读数据
/* rfifo.c */
#include
#include
#include
#include
#include
#include
#define FIFO_FILE "/tmp/progfifo"
int main()
{
int fd;
char data[20] = "";
/* 以阻塞方式打开FIFO文件,若要非阻塞用O_RDONLY|O_NONBLOCK*/
fd = open(FIFO_FILE, O_RDONLY);
if (fd < 0) {
printf("Open FIFO fail:%s.\n",
FIFO_FILE);
exit(1);
}
read(fd, data, sizeof(data)); /* 这里从FIFO中读数据,注意如果此时FIFO中没有数据,程序也会挂起等待数据 */
printf("FIFO data read: %s\n", data);
return (0);
}
现在我们将读写两程序联合起来运行看结果(请务必先运行mymkfifo建立FIFO文件)。
编译wfifo.c和rfifo.c。
在一个终端里运行程序./wfifo,发现程序挂起,说明在等待FIFO中数据被取出。
这时在另一个终端里运行程序./rfifo,可以看到运行结果
#./rfifo
FIFO data read: Data in the FIFO.
然后再回去刚才执行wfifo的终端里发现程序不再阻塞,已经继续执行了。并且显示了如下信息:
Here, the data has been read out.
如果两个程序换个顺序执行,就是先执行rfifo,再运行wfifo,也可以发现rfifo事先挂起,直到wfifo写入数据后再继续运行。