一、有名管道概念
上一节对无名管道做了阐述,无名管道只能用在两个有亲缘关系的进程直接。而有名管道FIFO可以用在任意两进程之间,它依赖于文件系统,是一个存在的特殊文件。
有名管道和普通文件一样具有磁盘存放路径。文件权限和其他属性。但是,有名管道和普通文件又有区别,有名管道并没有在磁盘正存放真正的信息,它存储的通信信息在内存中,两进程结束后自动丢失,拥有一个磁盘路径仅仅是一个接口,其目的是使进程间信息的编程更简单统一。通信的两个进程结束后,有名管道的文件路径本身仍然存在,这是和无名管道不一样的地方。有名管道的特性可以总结为一下几点:
1.可以用于同一台PC下任意两进程通信;
2.无法seek,要保持先入先出(FIFO)原则;
3.有名管道通信是单项的;
4.有名管道本身不会随通信双方的结束而消失,但有名管道中的内容会随着通信双方结束消失。
二、创建有名管道
mkfifo()
函数用来创建有名管道,其函数声明如下:
// come from /usr/include/sys/stat.h
/*Create a new FIFO named PATH, with permission bits MODE*/
int mkfifo(const char* path, __mode_t mode);
第一个参数path为要创建的管道文件名;
第二个参数mode为生成文件的模式。
mkfifo()
会根据参数建立特殊的有名管道文件,该文件必须不存在。mkfifo()
创建的FIFO文件其他进程都可以用读写一般文件的方式存取。当使用open()函数打开FIFO文件是,O_NONBLOCK会有影响。
三、读写有名管道
有名管道的实质和无名管道一样是一段内核管理的内存空间。但在通过write和read系统调用来执行读写操作前,需要调用open()函数来打开该文件。另外,操作无名无名管道的阻塞位置为open()位置,而不是有名管道的读写位置。
1.打开有名管道
如果希望以写的方式打开管道,则需要另一个进程以读的方式打开管道,否则一直阻塞。反过来如果以读的方式打开管道,则需要另一进程以写的方式打开管道,否则一直阻塞。即,如果以某种方式打开有名管道,则系统将阻塞进程,直到有另一个进程(包括自己)以另一种方式打开该管道后才会继续执行。显然,一个进程以可读可写方式打开管道,当前进程充当了读和写两个身份,进程不会阻塞。
2.两进程已经完成打开管道操作,阻塞读操作按一下方式执行:
a.如果管道中没有数据,读操作默认阻塞;
b.如果管道中有数据,但小于期望读取数据量,读出所有数据返回;
c.如果管道中有数据,但大于期望读取数据量,读出期望大小数据返回。
3.两进程已经完成打开管道操作,阻塞写操作按一下方式执行:
a.如果管道中没有空间,写操作阻塞;
b.如果管道中有空间,但小于期望写入数据量,写满空间后阻塞;
c.如果管道中有空间,但大于期望写入数据量,写入数据后返回。
4.两进程已经完成打开管道操作,中途其中一个进程退出:
a.未退出一端如果是写操作,将返回SIGPIPE信号;
b.未退出一端如果是阻塞读操作,读操作将不再阻塞,直接返回0。
四、示例
下面给出在父子进程间使用FIFO通信的示例,在非情缘关系的进程间原理是一样的:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <fcntl.h>
#define FIFO_PATH "./fifo"
void handler(int sig)
{
printf("sig=%d\n", sig);
}
int main()
{
signal(SIGPIPE, handler);
unlink(FIFO_PATH); // 删除原来的管道
mkfifo(FIFO_PATH, 0644); // 创建管道
pid_t pid = fork();
if (pid < 0) {
perror("fork error");
return -1;
}
if (0 == pid) { // 子进程
int fd;
fd = open(FIFO_PATH, O_WRONLY);
write(fd, "hello world!", 12);
sleep(1);
close(fd);
} else { // 父进程
int fd;
char buf[128] = {0};
fd = open(FIFO_PATH, O_RDONLY);
int ret = read(fd, buf, sizeof(buf));
printf("ret=%d buf=%s\n", ret, buf);
close(fd);
}
return 0;
}
运行结果如下所示: