第一部分:命名管道 (FIFO) —— 给管道起个名
1. 核心概念
- 匿名管道:在内存里,没有名字。只有父子进程能通过文件描述符看到它。
- 命名管道:在磁盘上有文件名。
- 因为它有路径(比如
/tmp/mypipe),所以任何进程,只要知道这个路径,有权限,就可以通过打开文件的方式来打开它。 - 关键点:虽然它在磁盘上有个文件壳子,但数据本身是不存硬盘的。数据依然是在内核的内存缓冲区里流动。一旦断电或重启,数据就没了,但那个文件壳子还在。
- 因为它有路径(比如
2. 直观体验
你可以不写代码,直接在 Linux 终端感受一下。
- 创建管道:使用命令
mkfifo
Bash
mkfifo mypipe- 查看属性:
Bash
ls -l mypipe # 输出: prw-r--r-- 1 user group 0 ... mypipe注意开头的p,这代表它是一个Pipe文件。
- 测试通信:
- 开一个终端 A(读端):
cat mypipe
- 开一个终端 A(读端):
- 现象:卡住了(阻塞)。因为它在等有人来写。
- 开终端 B(写端):
echo "Hello FIFO" > mypipe
- 开终端 B(写端):
- 现象:终端 A 立刻打印出 "Hello FIFO",然后命令结束。
第二部分:代码实战 —— Server & Client 通信模型
为了演示“毫无关系”的进程通信,我们需要写两个独立的程序:
server.cpp:负责创建管道,读取数据(读端)。client.cpp:负责打开管道,发送数据(写端)。
这其实就是最简陋的服务器-客户端模型。
1. Server 端 (server.cpp)
#include <iostream> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <errno.h> #define FIFO_FILE "./mypipe" int main() { // 1. 创建命名管道 // 参数2:权限掩码 (类似 mkdir) if (mkfifo(FIFO_FILE, 0666) < 0) { if (errno != EEXIST) { // 如果文件已存在,不算错 perror("mkfifo"); return 1; } } std::cout << "Server: FIFO created, waiting for client..." << std::endl; // 2. 打开管道 (读方式) // 注意:这里会【阻塞】,直到有别的进程以写方式打开同一个 FIFO int fd = open(FIFO_FILE, O_RDONLY); if (fd < 0) { perror("open"); return 2; } std::cout << "Server: Client connected!" << std::endl; // 3. 循环读取 char buffer[1024]; while (true) { memset(buffer, 0, sizeof(buffer)); ssize_t s = read(fd, buffer, sizeof(buffer) - 1); if (s > 0) { // 读到数据 std::cout << "Client Say# " << buffer << std::endl; } else if (s == 0) { // 写端关闭了 (Client 退出) std::cout << "Client quit, Server quit." << std::endl; break; } else { perror("read"); break; } } close(fd); return 0; }2. Client 端 (client.cpp)
#include <iostream> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #define FIFO_FILE "./mypipe" int main() { std::cout << "Client: Trying to connect server..." << std::endl; // 1. 打开管道 (写方式) // 只要 Server 端创建了 FIFO,Client 直接打开即可 int fd = open(FIFO_FILE, O_WRONLY); if (fd < 0) { perror("open"); return 1; } std::cout << "Client: Connected! Please enter message:" << std::endl; // 2. 循环写入 std::string line; while (true) { std::cout << "> "; if (!std::getline(std::cin, line)) break; // Ctrl+D 退出 write(fd, line.c_str(), line.size()); } close(fd); return 0; }3. 运行步骤
- 编译:
g++ server.cpp -o server和g++ client.cpp -o client。 - 先在一个终端运行
./server。你会发现它打印完 "waiting for client..." 就卡住了。 - 在另一个终端运行
./client。 - 奇迹发生:Server 端瞬间提示 "Client connected!"。
- 你在 Client 输入什么,Server 就能收到什么。
第三部分:深入原理 ——open的秘密
你可能注意到了,Server 在open的时候卡住了。这是命名管道的一个重要特性。
同步规则:
- 读端打开 (
O_RDONLY):会阻塞,直到有另一个进程以写方式打开同一个 FIFO。 - 写端打开 (
O_WRONLY):会阻塞,直到有另一个进程以读方式打开同一个 FIFO。
为什么这样设计?
这就好比打电话。
- Server 拿起了听筒(读端打开),如果对面没人拨号(写端未打开),Server 拿着听筒听空气没有任何意义,所以系统让它先等着。
- Client 拨了号(写端打开),如果对面没人接(读端未打开),话说给谁听呢?所以也得等着。
- 只有两头都通了,
open才会同时返回,连接建立成功。
第四部分:匿名管道 vs 命名管道
这两种管道是 Linux IPC 的基础,我们做一个对比总结:
特性 | 匿名管道 (Pipe) | 命名管道 (FIFO) |
可见性 | 内存中,无文件名 | 磁盘上有文件名 |
通信范围 | 仅限血缘关系进程 (父子/兄弟) | 任意进程 (只要权限允许) |
创建方式 |
|
|
生命周期 | 随进程结束而销毁 | 随文件系统 (即使进程全退出了,文件还在) |
本质 | 都是内核中的一块缓冲区 | 同左 |