什么是命名管道
匿名管道(pipe)是一种进程间通信的古老方式,它只能用于父子进程间的通信。对于没有亲缘关系的连个进程,匿名管道就无能为力了。此时可以借助于一个叫做 命名管道 的东西,顾名思义,命名管道与匿名管道应该是相似的,但是用途更加广泛。
Linux 中一切皆文件,通俗点来讲,进程之间可以通过文件交换数据。命名管道就是通过一种特殊的文件实现进程间的数据交流,这种文件也被称为 FIFO 文件,它是一种特殊类型的文件。stat.h 中定义了它的文件类型S_FIFO
,提供了宏S_ISFIFO(m)
判断一个文件是否是 FIFO 文件。
创建 FIFO 文件
命名管道 在文件系统中以文件的方式存在,对命名管道的管理与对文件的管理类似。
使用mkfifo
函数来创建一个命名管道(FIFO)文件:
#include <sys/stat.h> // mkfifo
#include <sys/types.h> // mode_t
/* Create a new FIFO named PATH, with permission bits MODE. */
int mkfifo (const char *path, mode_t mode);
FIFO 的应用场景:
- FIFO 文件由操作系统自动创建,使用者无需手动建立中间文件
- FIFO 是一个先进先出的队列,可以用于处理客户端消息等业务
- ……
使用命名管道
mkfifo
函数本质上是创建一个特殊的文件,称之为 命名管道文件 或者 FIFO 文件。命名管道 本质上是通过 FIFO 文件实现的,命名就是指的 FIFO 文件的文件路径是唯一的,其使用方法与使用文件是类似的。因此使用一个命名管道只需要打开对应的 FIFO 文件进行操作即可。
但是与普通文件不同的是,我可以打开一个可读可写的普通文件,但是不能打开一个可读可写的 FIFO 文件,FIFO 文件只能以只读或者只写的方式打开。FIFO 文件是为了在进程间传递数据,如果可以可读可写的打开 FIFO 文件,那么进程就不知道哪些数据是自己需要读取的,因为读取的数据可能是自己刚写进去的,这与命名管道的设计初衷相悖。
使用open
函数打开一个 FIFO 文件,以只读方式打开就是从命名管道中读取数据,以只写方式打开就是往命名管道中写入数据:
#include <fcntl.h> // open
open(const char *fifo_fp, O_RDONLY);
open(const char *fifo_fp, O_RDONLY | O_NONBLOCK);
open(const char *fifo_fp, O_WRONLY);
open(const char *fifo_fp, O_WRONLY | O_NONBLOCK);
O_NONBLOCK
表示以 非阻塞 的方式打开 FIFO 文件。阻塞指的是,该文件至少得存在一个只读打开和一个只写打开,这样这个命名管道才是“活跃”的(管道的读端和写端都与进程建立了连接)。例如使用O_NONBLOCK
打开一个只读的 FIFO 文件,该进程会一直等待,直到有另外一个进程以只写的方式打开同一个文件后,open
函数才会调用成功并返回。不加O_NONBLOCK
时,即时没有其他进程只写打开这个 FIFO 文件,open
函数不会去读取这个 FIFO 文件(?),而是直接返回 -1。FIFO 文件默认以阻塞式的方式打开。
写数据
#include <stdio.h> // perror
#include <unistd.h> // write close
#include <sys/stat.h> // mkfifo
#include <errno.h> // errno
#include <fcntl.h> // open O_WRONLY
#define FIFO "/tmp/test.fifo" // FIFO 文件路径
int main()
{
// 创建 FIFO 文件并测试创建是否成功,文件权限为仅拥有者可读可写
if (mkfifo(FIFO, S_IRUSR | S_IWUSR) < 0 && errno != EEXIST)
{
perror("make fifo failed");
return -1;
}
// 以只读方式打开 FIFO 文件,返回文件描述符
int fd = open(FIFO, O_WRONLY);
// 向 FIFO 文件写入数据
char s[] = "hello world";
write(fd, s, sizeof(s));
// 关闭 FIFO 文件
close(fd);
return 0;
}
然后运行该程序:
gcc fifo_wr.c -o fifo_wr && fifo_wr
程序会进入一直等待,因为我们默认以只写阻塞的方式打开的 FIFO 文件,程序会一直等,直到有其他进程以只读的方式打开这个 FIFO 文件。
此时检查文件系统中确实创建了这样一个特殊的文件:
$ ll /tmp/ | grep test.fifo
prw-------. 1 barwe barwe 0 7月 14 15:27 test.fifo|
- 文件类型是 p,表示这一个命名管道文件(FIFO 文件)
- 文件权限只允许所有者可读可写
- 文件名称是我们定义的 test.fifo,还加了一个管道符(|)
读数据
用另一个程序来从命名管道读取数据:
#include <stdio.h> // perror
#include <unistd.h> // write close
#include <sys/stat.h> // mkfifo
#include <errno.h> // errno
#include <fcntl.h> // open O_WRONLY
#define FIFO "/tmp/test.fifo"
#define MAX_LEN 128
int main(){
// 打开 FIFO 文件
int fd = open(FIFO, O_RDONLY);
if (fd < 0) {
perror("open fifo file failed\n");
}
// 读取管道内容
char line[MAX_LEN] = {};
int n = read(fd, line, MAX_LEN);
printf("read %d bytes from pipe: %s\n", n, line);
// 关闭 FIFO 文件
close(fd);
// 删除 FIFO 文件
unlink(FIFO);
return 0;
}
然后运行该程序:
gcc fifo_rd.c -o fifo_rd && fifo_rd
会发现程序立即输出:
read 12 bytes from pipe: hello world
并且运行的第一个程序立即结束。
并且文件系统中的 test.fifo 文件也被删除掉了。
命名管道的安全问题
主要发生在多端写,一端读的情境下。写入端每次写数据应该写入一个相对完整的数据块,手动保证数据连贯。
评论区