一、管道通信的核心背景
Linux 系统中进程地址空间相互隔离,管道通过内核提供的 "伪文件"(队列结构)实现进程间数据传递,是最经典的单机 IPC 方式之一,具备实现简单、轻量高效的特点。
二、无名管道(Unnamed Pipe)
1. 核心定义
无名管道(也叫匿名管道)是仅支持有亲缘关系进程(父子 / 兄弟进程)通信的半双工管道,无文件名称,文件系统不可见,生命周期随进程结束而销毁。
2. 核心特性
| 特性 | 说明 |
|---|---|
| 通信范围 | 仅限有亲缘关系的进程(如 fork 创建的父子进程) |
| 工作模式 | 半双工(数据单向传输),实际开发中通常当作单工使用 |
| 文件属性 | 特殊的伪文件,不支持lseek()(文件 IO)/fseek()(标准 IO)定位操作 |
| IO 方式 | 首选文件 IO(open/read/write/close),也可使用标准 IO(fgets/fread 等,需注意缓冲区) |
| 核心限制 | 数据传输上限约 64KB,超出会触发阻塞 |
3. 关键函数
c
运行
#include <unistd.h>
int pipe(int pipefd[2]);
- 功能:创建并打开一个无名管道,在内核中分配队列资源;
- 参数 :
pipefd[0]固定为管道读端,pipefd[1]固定为管道写端; - 返回值 :成功返回 0,失败返回 - 1(可通过
perror()查看错误原因)。
4. 编程流程
plaintext
创建管道(pipe()) → fork创建子进程 → 关闭无用端(父子进程分工读/写) → 读写管道 → 关闭管道(close())
5. 核心行为(必记)
- 写阻塞:读端存在时,向管道写入数据超过 64KB,写操作会阻塞(写进程速度过快);
- 读阻塞:写端存在时,读取空管道,读操作会阻塞(读进程速度过快);
- 管道破裂 :读端关闭后仍向管道写数据,内核会向写进程发送
SIGPIPE信号,导致写进程退出; - 通信结束:写端关闭后,读进程读取空管道会返回 0(可作为通信结束的标志)。
6. 极简示例代码
c
运行
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main() {
int pipefd[2];
// 1. 创建无名管道
if (pipe(pipefd) == -1) {
perror("pipe create failed");
return 1;
}
// 2. fork创建子进程(亲缘进程)
pid_t pid = fork();
if (pid == -1) {
perror("fork failed");
return 1;
}
// 子进程:写管道
if (pid == 0) {
close(pipefd[0]); // 关闭读端(仅写)
char buf[] = "Hello from child process (pipe)";
write(pipefd[1], buf, strlen(buf));
close(pipefd[1]); // 关闭写端
return 0;
}
// 父进程:读管道
close(pipefd[1]); // 关闭写端(仅读)
char recv_buf[1024] = {0};
read(pipefd[0], recv_buf, sizeof(recv_buf));
printf("Parent read: %s\n", recv_buf);
close(pipefd[0]); // 关闭读端
wait(NULL); // 等待子进程退出
return 0;
}
三、有名管道(FIFO)
1. 核心定义
有名管道(FIFO)解决了无名管道 "仅亲缘进程通信" 的限制,支持任意单机进程通信,底层队列结构与无名管道一致,最大区别是 "文件系统可见"(有明确的路径和名称)。
2. 核心特性
- 继承无名管道的所有特性(半双工、无定位、64KB 上限等);
- 额外特性:管道以文件形式存在(
ls -l可见,文件类型为 p),若一端未打开,另一端调用open()时会阻塞; - 生命周期:文件存在于文件系统,需手动删除(
unlink()/rm),否则会残留。
3. 关键函数
c
运行
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
- 功能:在指定路径创建有名管道文件;
- 参数 :
pathname为管道路径 + 名称(如./my_fifo),mode为 8 进制文件权限(如 0664); - 返回值:成功返回 0,失败返回 - 1(若文件已存在,错误码为 EEXIST)。
4. 编程流程
plaintext
创建有名管道(mkfifo()) → 进程A/B打开管道(open()) → 进程间读写管道 → 关闭管道(close()) → 删除管道文件(unlink())
5. 极简示例代码
写进程(fifo_write.c)
c
运行
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#define FIFO_PATH "./my_fifo"
int main() {
// 1. 创建有名管道
if (mkfifo(FIFO_PATH, 0664) == -1) {
perror("mkfifo failed");
return 1;
}
// 2. 打开管道(写模式,阻塞等待读端打开)
int fd = open(FIFO_PATH, O_WRONLY);
if (fd == -1) {
perror("open failed");
return 1;
}
// 3. 写数据
char buf[] = "Hello from FIFO write process";
write(fd, buf, strlen(buf));
printf("Write data: %s\n", buf);
// 4. 关闭并删除管道
close(fd);
unlink(FIFO_PATH);
return 0;
}
读进程(fifo_read.c)
c
运行
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#define FIFO_PATH "./my_fifo"
int main() {
// 1. 打开管道(读模式,阻塞等待写端打开)
int fd = open(FIFO_PATH, O_RDONLY);
if (fd == -1) {
perror("open failed");
return 1;
}
// 2. 读数据
char recv_buf[1024] = {0};
read(fd, recv_buf, sizeof(recv_buf));
printf("Read data: %s\n", recv_buf);
// 3. 关闭管道
close(fd);
return 0;
}
6. 运行方式
bash
运行
# 编译
gcc fifo_write.c -o fifo_write
gcc fifo_read.c -o fifo_read
# 终端1运行读进程(阻塞等待写端)
./fifo_read
# 终端2运行写进程(完成后自动删除管道)
./fifo_write
四、无名管道 vs 有名管道 核心对比
| 维度 | 无名管道 | 有名管道 |
|---|---|---|
| 通信范围 | 仅亲缘进程 | 任意单机进程 |
| 文件可见性 | 不可见(仅内核中存在) | 可见(文件系统中有路径 / 名称) |
| 创建方式 | pipe() | mkfifo() |
| 生命周期 | 随进程退出销毁 | 需手动删除,否则残留 |
| open 阻塞 | 无(创建时已打开) | 一端未打开时,另一端 open 阻塞 |
| 核心特性 | 半双工、64KB 上限、无定位 | 继承无名管道所有特性 |
五、管道通信实践建议
- 场景选择 :
- 亲缘进程单机通信 → 无名管道(无需手动清理,轻量);
- 任意单机进程通信 → 有名管道(灵活,需注意文件残留);
- 注意事项 :
- 避免管道破裂:读端关闭前,写端不要持续写数据;
- 数据完整性:管道传输字节流,需自定义分隔符(如
\n)标识数据边界; - 性能限制:64KB 上限决定管道不适合大数据传输,大数据场景优先选共享内存。