管道是进程间通信(IPC)常见的方式,管道一般分为:匿名管道 和有名管道
匿名管道
匿名管道 的原理主要是通过内核的环形缓冲区 实现单向通信,仅用于父子进程或兄弟进程等有亲缘关系的进程间
Linux 中用 pipe
创建一个匿名管道
环形缓冲区
什么是环形缓冲区?简单来说用一个固定大小的连续内存空间,通过一个读指针(read_pos)和一个写指针(write_pos)来实现内容的读写
lua
写数据
↓
+-------------------------+
| 已读 | 数据 | 空 |
+-------------------------+
↑ ↑
read_pos write_pos
↑
读数据
当 read_pos 和 write_pos 相遇:
- 如果是读端达到 write_pos => 没有数据,阻塞读
- 如果是写端达到 read_pos 前一位 => 没有空间,阻塞写

根据环形缓冲区的特点,因此匿名管道的信息采用先进先出(FIFO)方式传递数据,所谓的匿名是没有文件系统中的名称标识

通过 pipe
创建一个管道
arduino
#include <unistd.h>
int pipe(int pipefd[2]);
pipe
会创建两个管道标识符
pipefd[0]
:从管道进行读取数据。pipefd[1]
:向管道进行写入数据。
终端中的 |
我们经常在终端中使用 |
,这就是管道的符号,通过匿名管道的方式来实现父子进程的通信,例如下面的命令

通过 |
配合 grep
来实现搜索数据的过滤,下面是一段简单的代码来实现上面的功能
scss
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
int pipefd[2]; // pipefd[0] = 读取端, pipefd[1] = 写入端
pid_t pid1, pid2;
// 1. 创建匿名管道
if (pipe(pipefd) == -1) {
perror("pipe");
exit(1);
}
// 2. 创建第一个子进程(执行 `ls`,写入管道)
pid1 = fork();
if (pid1 == -1) {
perror("fork");
exit(1);
}
if (pid1 == 0) { // 子进程1(`ls`)
close(pipefd[0]); // 关闭读取端(不需要读)
dup2(pipefd[1], STDOUT_FILENO); // 将标准输出重定向到管道写入端
close(pipefd[1]); // 关闭原始写入端(已被 dup2 替换)
execlp("ls", "ls", NULL); // 执行 `ls`
perror("execlp ls"); // 如果执行失败
exit(1);
}
// 3. 创建第二个子进程(执行 `grep Hello`,从管道读取)
pid2 = fork();
if (pid2 == -1) {
perror("fork");
exit(1);
}
if (pid2 == 0) { // 子进程2(`grep Hello`)
close(pipefd[1]); // 关闭写入端(不需要写)
dup2(pipefd[0], STDIN_FILENO); // 将标准输入重定向到管道读取端
close(pipefd[0]); // 关闭原始读取端(已被 dup2 替换)
execlp("grep", "grep", "Hello", NULL); // 执行 `grep Hello`
perror("execlp grep"); // 如果执行失败
exit(1);
}
// 4. 父进程关闭管道(子进程已经接管)
close(pipefd[0]);
close(pipefd[1]);
// 5. 等待两个子进程结束
waitpid(pid1, NULL, 0);
waitpid(pid2, NULL, 0);
return 0;
}
shell
在遇到 |
时,会通过 fork
创建出一个子进程,来进行进一步的操作,这里其中 STDOUT_FILENO
是标准输入的意思,意味着 execlp("ls", "ls", NULL)
的结果都会写入管道中
三个标准文件描述符
- STDIN_FILENO (0): 标准输入(键盘输入)
- STDOUT_FILENO (1): 标准输出(屏幕输出)
- STDERR_FILENO (2): 标准错误输出(屏幕错误输出)
在 pid2 也就是 grep
子进程会通过读取管道中的信息,来过滤相关信息,然后输出到 shell 中
有名管道
有名管道允许无亲缘关系的进程通信,通过在文件系统中创建一个特殊类型的文件来实现
Linux 中用 mkfifo
命令进行系统调用
bash
mkfifo myfifo

其中开头的 p
表示这是一个管道文件,也称 FIFO 文件,对于文件来说,我们都可以使用open
、write
、read
等函数操作
例如这里用有名管道实现两个不相关进程简单的聊天
c
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#define FIFO_FILE "/tmp/learning/icp/unname/chat_fifo"
int main() {
int fd;
char buf[100];
// 创建有名管道(如果不存在)
mkfifo(FIFO_FILE, 0666);
// 以只写方式打开管道(会阻塞直到读取端打开)
fd = open(FIFO_FILE, O_WRONLY);
printf("Writer ready. Enter messages (Ctrl+C to exit):\n");
while (1) {
printf("> ");
fflush(stdout); // 确保提示符显示
if (fgets(buf, sizeof(buf), stdin) == NULL) {
break; // 读取失败(如Ctrl+D)
}
write(fd, buf, strlen(buf) + 1); // +1 包含字符串结束符
}
close(fd);
return 0;
}
arduino
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define FIFO_FILE "/tmp/learning/icp/unname/chat_fifo"
#define BUF_SIZE 100
int main() {
int fd;
char buf[BUF_SIZE];
// 以只读方式打开管道(会阻塞直到写入端打开)
fd = open(FIFO_FILE, O_RDONLY);
printf("Reader ready. Waiting for messages...\n");
while (1) {
ssize_t bytes_read = read(fd, buf, BUF_SIZE - 1); // 保留1字节给结束符
if (bytes_read <= 0) {
break; // 写入端关闭或出错
}
buf[bytes_read] = '\0'; // 确保字符串正确终止
printf("Received: %s", buf);
}
close(fd);
return 0;
}
这里我们写了两个简单的程序,两个完全不同的进程进行简单的聊天,通过 mkfifo(FIFO_FILE, 0666);
会创建一个管道文件
这里去运行这两个程序

可以看到 reader
进程成功接收到了 writer
传递过来的消息

也在当前目录下创建了一个 chat_fifo
的管道文件,这里再通过观察 chat_fifo
文件

有名管道特性
-
阻塞行为
- 读空管道会阻塞直到有数据
- 写满管道会阻塞直到有空间
-
数据也是以先进先出(FIFO) 传递
-
持久性:有名管道在文件系统中存在,直到被显式删除(unlink)
这里还有一点:无论是无名管道还是命名管道,数据读取后都会消失
管道的实际引用
上面 shell
通过 |
创建匿名管道进行通信就是最简单的实践,管道还广泛的引用在其他应用场景中
容器日志重定向
docker 使用管道技术重定向输出到日志分析系统
perl
# 容器日志收集
docker logs -f container_id | grep "ERROR" > errors.log
- 可以分离容器和日志分析器的生命周期
- 支持更复杂的处理流程
Nginx中管道技术的高效请求/响应流转发
bash
location / {
proxy_pass http://backend;
proxy_set_header X-Real-IP $remote_addr;
}
上面的配置是将所有请求 /
转发到 [http://backend](http://backend)
服务器组
客户端请求 → 接收缓冲区 → 解析 → 各阶段处理器 → 上游服务器 → 响应缓冲 → 客户端
- 管道将 HTTP请求 和 响应数据 拆分为多个数据块(chunks),通过内存管道在不同处理模块间流动
- 避免了完整数据缓冲,支持边接收边转发
- 降低了内存占用,提升了性能
大数据处理
Apache Spark :RDD 转换操作底层使用管道式数据处理
流式处理框架
Fluentd日志收集 :管道式日志处理流程,其核心在于通过插件化的组件将日志的输入、过滤、缓冲、输出串联为流水线式处理链路
管道技术的核心优势在于它的 流式处理能力 和 低资源消耗 ,这使得它在需要高效数据流转的场景中仍然是不可替代的基础技术。现代实现通常会结合缓冲、非阻塞IO等优化手段来提升性能