函数原型
/** * int mkfifo(const char *pathname, mode_t mode); * * @brief 用于创建有名管道。该函数可以创建一个路径为pathname的FIFO专用文件,mode指定了FIFO的权限,FIFO的权限和它绑定的文件是一致的。FIFO和pipe唯一的区别在于创建方式的差异。一旦创建了FIFO专用文件,任何进程都可以像操作文件一样打开FIFO,执行读写操作。 * * @param pathname 有名管道绑定的文件路径 * @param mode 有名管道绑定文件的权限 * @return int */接下来写一段发送端代码,以及接收端代码演示
发送端代码:
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <stdlib.h> #include <fcntl.h> #include <errno.h> /** * int mkfifo(const char *pathname, mode_t mode); * * @brief 用于创建有名管道。该函数可以创建一个路径为pathname的FIFO专用文件,mode指定了FIFO的权限,FIFO的权限和它绑定的文件是一致的。FIFO和pipe唯一的区别在于创建方式的差异。一旦创建了FIFO专用文件,任何进程都可以像操作文件一样打开FIFO,执行读写操作。 * * @param pathname 有名管道绑定的文件路径 * @param mode 有名管道绑定文件的权限 * @return int */ int main(int argc, char const *argv[]) { char* fifo_path = "/tmp/myfifo"; //fifo专用文件 //创建有名管道 if(mkfifo(fifo_path,0664) != 0) { if (errno != EEXIST) { // 如果不是因为文件已存在,才是真错误 perror("mkfifo failed"); exit(EXIT_FAILURE); } } //打开有名管道用于写入 int fd = open(fifo_path,O_WRONLY); if(fd == -1) { perror("open fail"); exit(EXIT_FAILURE); } char write_buf[100]; ssize_t read_num; //从控制台读取输入 while((read_num = read(STDIN_FILENO,write_buf,sizeof(write_buf))) > 0) { //写入fifo管道 write(fd,write_buf,read_num); } if(read_num < 0) { perror("read"); close(fd); exit(EXIT_FAILURE); } close(fd); printf("发送端退出\n"); unlink(fifo_path); return 0; }接收端代码:
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <stdlib.h> #include <fcntl.h> /** * int mkfifo(const char *pathname, mode_t mode); * * @brief 用于创建有名管道。该函数可以创建一个路径为pathname的FIFO专用文件,mode指定了FIFO的权限,FIFO的权限和它绑定的文件是一致的。FIFO和pipe唯一的区别在于创建方式的差异。一旦创建了FIFO专用文件,任何进程都可以像操作文件一样打开FIFO,执行读写操作。 * * @param pathname 有名管道绑定的文件路径 * @param mode 有名管道绑定文件的权限 * @return int */ int main(int argc, char const *argv[]) { char* fifo_path = "/tmp/myfifo"; //fifo专用文件 //打开有名管道用于读取 int fd = open(fifo_path,O_RDONLY); if(fd == -1) { perror("open fail"); exit(EXIT_FAILURE); } char read_buf[100]; ssize_t read_num; //从管道文件描述符中读取 while((read_num = read(fd,read_buf,sizeof(read_buf))) > 0) { //输出到控制台 write(STDOUT_FILENO,read_buf,read_num); } if(read_num < 0) { perror("read"); close(fd); exit(EXIT_FAILURE); } close(fd); printf("接收端退出\n"); return 0; }运行两个程序演示通信
左边为发送端,右边为接收端
动画讲解
这边用ai 生成动画以便大家理解
1. "有名" 的意义 (The Name is the Rendezvous Point)
无名管道(pipe)之所以只能在父子进程间使用,是因为它只存在于内存中,没有名字,其他进程根本“指不到”它。
FIFO 的突破:
mkfifo在文件系统中创建了一个节点(/tmp/myfifo)。这个文件就像一个公开的接头地点。
发送端和接收端不需要互为父子,它们只要大家都约定好去
/tmp/myfifo这个地方,就能建立连接。注意:虽然它在磁盘上有个文件,但数据本身是不存硬盘的。那个文件只是一个“路标”,数据依然是在内核内存中流动。
2. 打开时的阻塞机制 (Blocking on Open)
这是代码中最容易让人困惑的地方(动画步骤 3):
规则:FIFO 必须读写两端同时打开才能开始工作。
如果你写了一个程序只调用
open(path, O_WRONLY),而没有另一个程序去O_RDONLY,那么open函数会卡住(阻塞),直到另一个程序出现。这是一种天然的同步机制,保证了在写入数据之前,接收端已经准备好接收了。
3. 像操作文件一样操作管道
代码中使用了open,read,write,close,这些全是标准的文件 I/O 操作。
Linux 的哲学是"一切皆文件"。
内核把管道伪装成了一个文件,让程序员可以用熟悉的文件操作接口来收发数据,而不需要学习新的 API。
无名管道 vs 有名管道
| 特性 | 无名管道 (Pipe) | 有名管道 (FIFO) |
| 存在形式 | 仅存在于内存,无文件路径 | 在文件系统中有路径(如/tmp/myfifo) |
| 通信范围 | 仅限父子、兄弟等有亲缘关系的进程 | 任意两个进程,只要能访问该文件路径 |
| 创建方式 | pipe() | mkfifo()命令或函数 |
| 数据流 | 单向字节流 | 单向字节流 |
| 持久性 | 进程结束即销毁 | 文件节点一直存在,直到unlink |