从零认识命名管道:命名管道全解析
文章目录
- 1. 引入
- 问题:
- 回答:
- 2. 命名管道
- 2.1 指令集方式创建管道文件
- 2.2 系统调用方式创建命名管道
- 3. 源码解析(面向过程)
- Client
- Server
- 4. 结论分析(实验截图保留)
- 1️⃣ 写端未打开前,读端 open 会阻塞
- 2️⃣ 双方都成功 open 后通信开始
- 3️⃣ 实现非亲缘进程通信
- 4️⃣ 写端退出后,读端 read 返回 0 → 自动退出
- 5. 源码(面向对象)
- comm.hpp
- server.cc
- client.cc
- 6. 总结
1. 引入
通过对匿名管道(pipe)的学习,我们已经能解决存在血缘关系的进程之间的通信问题。
但如果是 完全无关的两个进程,例如:
- 进程 A
- 进程 B
它们之间想要通信该怎么办呢?
于是就引出了 —— 命名管道(Named Pipe / FIFO) 的概念。
问题:
Q:两个无关进程 A、B 都打开同一个文件,操作系统是否会在内核中把该文件加载两次?
回答:
不会!因为没必要。
只要两个进程打开同一路径下的同一个文件,它们看到的就是同一份资源,自然就能实现进程间通信。
因为这个文件有路径、有名字,而且路径唯一。
这个具有名字的管道文件,就是所谓的:
命名管道(FIFO)
它作为一个文件存在于文件系统中:
✔ 只会被打开,不会被刷新
✔ 不需要写入磁盘
✔ 操作系统保证其 FIFO 行为
2. 命名管道
2.1 指令集方式创建管道文件
使用 mkfifo 文件名 直接创建:
mkfifo fifo
文件类型显示为 p 说明它就是一个 FIFO:

echo 和 cat 实际就是两个独立进程,通过打开同一个 FIFO 文件实现通信:

删除命名管道文件:

2.2 系统调用方式创建命名管道
mkfifo() 系统调用用于在指定路径创建 FIFO 文件:

3. 源码解析(面向过程)
Client
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include "comm.hpp"
#include <unistd.h>
#include <fcntl.h>int main()
{// 打开管道文件int fd = open(FIFO_FILE, O_WRONLY);if (fd < 0){std::cerr << "open fifo error" << std::endl;return 2;}std::cout << "open fifo success" << std::endl;// 写文件std::string message;int cnt = 1;pid_t id = getpid();while (true){std::cout << "Please Enter#" << std::endl;std::getline(std::cin, message);message += (", message number:" + std::to_string(cnt) + ",[" + std::to_string(id) + "]");write(fd, message.c_str(), message.size()); // c_str方法 就是拿到string中的数据}// 关闭文件close(fd);return 0;
}
Server
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include "comm.hpp"
#include <unistd.h>
#include <fcntl.h>int main()
{umask(0); // 设置权限掩码int n = mkfifo(FIFO_FILE, 0666); // 创建管道文件,并设置权限if (n < 0){std::cout << "mkfile error" << std::endl;return 1;}std::cout << "mkfifo sucess" << std::endl;// 打开管道文件int fd = open(FIFO_FILE, O_RDONLY);if (fd < 0){std::cerr << "open fifo error" << std::endl;return 2;}std::cout << "open fifo success" << std::endl;// 读文件while (true){char buffer[1024];int number = read(fd, buffer, sizeof(buffer) - 1);if (number > 0){buffer[number] = 0; // 第number个位置充当\0std::cout << "Clent Say#" << buffer << std::endl; // 打印读到的内容}else if(number == 0){std::cout << "client quit!me too" << number << std::endl;break;}else{std::cerr << "read error" << std::endl;break;}}// 关闭文件close(fd);// 删除管道n = unlink(FIFO_FILE);if (n == 0){std::cout << "remove fifo success" << std::endl;}else{std::cout << "remove fifo failed" << std::endl;}return 0;
}
4. 结论分析(实验截图保留)
1️⃣ 写端未打开前,读端 open 会阻塞

读端会停在 open() 内部等待写端打开:

2️⃣ 双方都成功 open 后通信开始

3️⃣ 实现非亲缘进程通信

4️⃣ 写端退出后,读端 read 返回 0 → 自动退出

5. 源码(面向对象)
comm.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include "comm.hpp"
#include <unistd.h>
#include <fcntl.h>#define FIFO_FILE "fifo"
#define PATH "."
#define FILENAME "fifo"
// 用宏来处理错误情况 \表示换行符
#define ERR_EXIT(m) \do \{ \perror(m); \exit(EXIT_FAILURE); \} while (0)class NamedFifo
{
private:std::string _path;std::string _name;std::string _fifoname;public:NamedFifo(const std::string &path, const std::string &name): _path(path), _name(name){_fifoname = _path + "/" + _name;umask(0); // 设置权限掩码int n = mkfifo(FIFO_FILE, 0666); // 创建管道文件,并设置权限if (n < 0){ERR_EXIT("mkfile");}else{std::cout << "mkfifo sucess" << std::endl;}}~NamedFifo(){int n = unlink(_fifoname.c_str());if (n == 0){ERR_EXIT("unlink");}else{std::cout << "remove fifo failed" << std::endl;}return;}
};class FileOper
{
private:std::string _path;std::string _name;std::string _fifoname;int _fd;public:FileOper(const std::string &path, const std::string &name): _path(path), _name(name){_fifoname = _path + "/" + _name;}void OpenForRead(){_fd = open(_fifoname.c_str(), O_RDONLY);if (_fd < 0){ERR_EXIT("open");}else{std::cout << "open fifo success" << std::endl;}}void OpenForWrite(){_fd = open(_fifoname.c_str(), O_WRONLY);if (_fd < 0){ERR_EXIT("open");}else{std::cout << "open fifo success" << std::endl;}}void Write(){std::string message;int cnt = 1;pid_t id = getpid();while (true){std::cout << "Please Enter#" << std::endl;std::getline(std::cin, message);message += (", message number:" + std::to_string(cnt) + ",[" + std::to_string(id) + "]");write(_fd, message.c_str(), message.size()); // c_str方法 就是拿到string中的数据}}void Read(){while (true){char buffer[1024];int number = read(_fd, buffer, sizeof(buffer) - 1);if (number > 0){buffer[number] = 0; // 第number个位置充当\0std::cout << "Clent Say#" << buffer << std::endl; // 打印读到的内容}else if (number == 0){std::cout << "client quit!me too" << number << std::endl;break;}else{std::cerr << "read error" << std::endl;break;}}}void Close(){if (_fd > 0)close(_fd);}~FileOper(){}
};
server.cc
#include "comm.hpp"
int main()
{NamedFifo fifo("/", FILENAME);// 文件操作FileOper readerfile(PATH, FILENAME);readerfile.OpenForRead();readerfile.Read();readerfile.Close();return 0;
}
client.cc
#include "comm.hpp"
int main()
{FileOper writefiler(PATH, FILENAME);writefiler.OpenForWrite();writefiler.Write();writefiler.Close();return 0;
}

6. 总结
命名管道(FIFO)与匿名管道相比:
| 特性 | 匿名管道 | 命名管道 |
|---|---|---|
| 是否需要血缘关系 | ✔ 需要 | ❌ 不需要 |
| 是否有路径 | ❌ 无 | ✔ 有 |
| 是否可被非亲缘进程使用 | ❌ 不行 | ✔ 可以 |
| 是否持久化(可见于文件系统) | ❌ 不可见 | ✔ 可见 |
| 生命周期 | 进程结束即消失 | 需要 unlink 删除 |
✨ 命名管道最大的优势:允许两个互不相关的进程通信。
完
