1.匿名管道的限制匿名管道存在以下核心限制仅限亲缘关系进程只能用于父子进程等有血缘关系的进程间通信如通过fork()创建的子进程。单向通信数据只能单向流动一端写另一端读双向通信需创建两个管道。临时性存在于内存中进程结束后自动销毁。缓冲区有限大小固定通常为一个内存页如4KB易写满阻塞。引入命名管道的原因 为解决匿名管道的局限性命名管道允许任意进程无论是否有亲缘关系通过文件系统路径访问实现跨进程通信。2.什么是命名管道命名管道Named Pipe/FIFO是一种特殊的文件类型特点包括文件系统可见通过路径名如/tmp/myfifo标识任何进程可访问。遵循FIFO原则数据按写入顺序读取严格保持先进先出。突破亲缘限制不相关进程可通过路径名打开同一管道通信。双向通信支持部分场景下支持读写双向操作需显式设计。示例命名管道在文件系统中显示为特殊文件权限位带p如prw-r--r--。3.如何创建命名管道方法一命令行创建代码语言javascriptAI代码解释mkfifo 路径名 # 例如mkfifo /tmp/my_pipe生成一个具名管道文件权限默认受umask影响。示例方法二程序内创建使用mkfifo()函数代码语言javascriptAI代码解释#include sys/types.h #include sys/stat.h int mkfifo(const char *pathname, mode_t mode); // 成功返回0失败返回-1参数pathname管道路径如/tmp/my_pipe。mode权限标志如0666表示所有用户可读写。后续操作需用open()打开管道读模式O_RDONLY或写模式O_WRONLY。默认阻塞行为读端打开时写端阻塞反之亦然可通过O_NONBLOCK设为非阻塞。删除管道命令行rm 路径名或unlink 路径名。程序内unlink(pathname)。4.匿名管道和命名管道的区别特性匿名管道命名管道证据来源创建方式pipe(fd) 一步创建并打开mkfifo() 创建 open() 打开进程关系要求必须具有亲缘关系如父子进程任意进程均可访问持久性随进程结束销毁文件系统持久需手动删除通信方向仅单向可支持双向通信性能略快无文件系统操作稍慢涉及磁盘索引节点使用场景短期亲缘进程通信长期/跨进程通信如C/S架构关键补充语义一致性打开后两者操作方式相同如read()/write()。网络支持命名管道可跨机器通信匿名管道仅限本地。阻塞行为两者均受缓冲区影响但命名管道可通过O_NONBLOCK灵活控制阻塞。5. 命名管道的打开规则一、为读而打开 FIFOO_RDONLYO_NONBLOCK未设置默认阻塞行为调用open()会阻塞当前进程直到有另一个进程为写而打开同一 FIFO。原理内核需确保存在数据生产者否则读操作无意义。open以只读方式打开FIFO时要阻塞到某个进程为写而打开此FIFO。若没有指定O_NONBLOCK只读 open 要阻塞到某个其他进程为写而打开此 FIFO。O_NONBLOCK设置非阻塞行为open()立即成功返回返回文件描述符无论是否有写端打开。后续注意此时若管道无数据read()可能返回 0EOF或EAGAIN错误见下文读写规则。先以只读方式打开如果没有进程已经为写而打开一个FIFO只读 open() 成功并且 open() 不阻塞。若指定了O_NONBLOCK则只读 open 立即返回。二、为写而打开 FIFOO_WRONLYO_NONBLOCK未设置默认阻塞行为调用open()会阻塞当前进程直到有另一个进程为读而打开同一 FIFO。原理内核需确保存在数据消费者否则写操作可能无限等待。open以只写方式打开FIFO时要阻塞到某个进程为读而打开此FIFO。只写 open 要阻塞到某个其他进程为读而打开它。O_NONBLOCK设置非阻塞行为若无读端已打开open()立即失败返回-1并设置错误码ENXIO表示设备不存在。行为若已有读端打开则open()成功。先以只写方式打开如果没有进程已经为读而打开一个FIFO只写 open() 将出错返回 -1。若指定了O_NONBLOCK则只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO其errno置ENXIO。三、关键补充与深度解析O_RDWR读写模式的特殊性行为以O_RDWR模式打开时永不阻塞因进程自身已同时打开读写端 。风险可能导致自我死锁如写满后读阻塞实践中极少使用。读写操作的阻塞行为与open独立操作O_NONBLOCK未设置O_NONBLOCK设置read()空管道阻塞直到有数据写入立即返回EAGAIN或空数据write()满管道阻塞直到有空间部分写入或返回EAGAIN管道断裂与信号处理写端关闭读端read()返回 0EOF不阻塞 。读端关闭写端write()触发SIGPIPE信号默认终止进程错误码EPIPE。原子性与PIPE_BUF规则写入 ≤PIPE_BUF字节的数据保证原子性不与其他进程交织。典型值Linux 中PIPE_BUF为 4096 字节一页大小。四、内核实现原理选读阻塞的本质进程休眠在 FIFO inode 的等待队列中由另一端打开或数据变动时唤醒 。示例代码语言javascriptAI代码解释// Linux 内核片段读打开阻塞逻辑 if (PIPE_READERS(*inode) 0) wait_for_partner(inode, PIPE_WCOUNTER(*inode)); // 等待写端非阻塞的冲突处理写打开时若无读端内核直接返回ENXIO而非加入等待队列 若命名管道读端尚未打开而 O_NONBLOCK1写端打开失败并释放资源。总结与建议场景打开模式O_NONBLOCK结果读打开无写端存在O_RDONLY未设置阻塞读打开无写端存在O_RDONLY设置立即成功写打开无读端存在O_WRONLY未设置阻塞写打开无读端存在O_WRONLY设置立即失败ENXIO读写打开O_RDWR任意立即成功不依赖外部进程工程建议生产-消费模型推荐读端阻塞打开确保写端就绪写端非阻塞打开快速失败重试逻辑。超时控制若需阻塞但避免无限等待结合select()/poll()设置超时。错误处理始终检查open()返回值和errno尤其非阻塞模式。6. 代码示例下面为了更好理解命名管道我们直接来一段代码使用命名管道让两个无血缘关系的进程进行通信——一个进程写一个进程读。这里client.cc和server.cc代表两个没有血缘关系的进程在前面学习进程时我们知道.cc文件跑起来就是一个进程所以这里不多赘述。而我们命名管道的创建以及打开管道文件进行操作的代码则封装在comm.hpp中。Makefile则是我们配置的自动化工具。下面我们就来在comm.hpp中将代码封装起来首先需要将命名管道创建最后结束通信后还需要将管道回收因为命名管道不会随进程的生命周期所以需要我们手动回收代码如下代码语言javascriptAI代码解释class NamedFifo { public: NamedFifo(const std::string path, const std::string name) : _path(path), _name(name) { _filename _path / _name; // 创建命名管道 int n mkfifo(_filename.c_str(), 0666); if(n 0) { std::cerr mkfifo failed std::endl; } else { std::cout mkfifo success std::endl; } } ~NamedFifo() { // 回收命名管道 int n unlink(_filename.c_str()); if(n 0) { std::cerr remove fifo failed std::endl; } else { std::cout remove fifo success std::endl; } } private: std::string _path; std::string _name; std::string _filename; };由于我们要实现一个进程写一个进程读的单向通信所以我们先规定让客户端client.cc进程来写服务端server.cc进程来读那么读写操作我们还需要再封装一个类因为我们只要创建一个管道就行了。如果都封装在一个类中那么客户端和服务端都需要实例化出一个对象才能对管道读写通信但这样就会创建两个命名管道了因为只要构造函数就会创建命名管道而我们不需要两个命名管道我们只需要创建一个命名管道然后服务端和客户端分别以读写的方式打开这个管道文件就可以进行通信了所以我们可以再封装一个类来实现对打开的命名管道进行操作。代码如下代码语言javascriptAI代码解释class Fileoper { public: Fileoper(const std::string path, const std::string name) : _path(path), _name(name), _fd(-1) { _filename _path / _name; } void OpenForRead() { _fd open(_filename.c_str(), O_RDONLY); if(_fd 0) { std::cerr open fifo failed std::endl; } else { std::cout open fifo success std::endl; } } void OpenForWrite() { _fd open(_filename.c_str(), O_WRONLY); if(_fd 0) { std::cerr open fifo failed std::endl; } else { std::cout open fifo success std::endl; } } ~Fileoper() {} private: std::string _path; std::string _name; std::string _filename; int _fd; };由于我们需要打开指定路径的管道文件所以成员变量仍然需要和NamedFifo类一样但是我们打开管道文件后需要通过返回的文件描述符后续管理规管道文件所以我们还需要一个成员变量_fd来接收open返回的文件描述符。客户端需要从管道写入服务端需要从管道读取所以客户端以只写的方式打开管道文件而服务端以只读的方式打开管道文件。但是打开之后我们客户端和服务端还需要对管道进行读写操作所以我们还需要分别实现一个写函数和一个读函数代码如下代码语言javascriptAI代码解释void Write() { std::string message; while(true) { std::cout Please Enter#; std::getline(std::cin, message); write(_fd, message.c_str(), message.size()); } } void Read() { while(true) { char buffer[1024]; ssize_t n read(_fd, buffer, sizeof(buffer)-1); if(n 0) { buffer[n] 0; std::cout Client say# buffer std::endl; } else if(n 0) { std::cout Client quit! me too! std::endl; break; } else { std::cerr read error std::endl; break; } } }当然通信结束之后我们需要关闭文件描述符代码语言javascriptAI代码解释void Close() { close(_fd); }测试我们先定义两个宏代码语言javascriptAI代码解释#define PATH . #define FILENAME fifo我们想要在当前路径下创建一个fifo的管道文件服务端代码语言javascriptAI代码解释#include comm.hpp int main() { // 创建管道 NamedFifo f(PATH, FILENAME); // 文件操作 Fileoper reader(PATH, FILENAME); reader.OpenForRead(); reader.Read(); reader.Close(); return 0; }客户端代码语言javascriptAI代码解释#include comm.hpp int main() { Fileoper Writer(PATH, FILENAME); Writer.OpenForWrite(); Writer.Write(); Writer.Close(); return 0; }运行测试可以看到成功实现了两个没有血缘关系的进程的单向通信源码comm.hpp:代码语言javascriptAI代码解释#pragma once #include iostream #include string #include sys/types.h #include sys/stat.h #include unistd.h #include fcntl.h #define PATH . #define FILENAME fifo class NamedFifo { public: NamedFifo(const std::string path, const std::string name) : _path(path), _name(name) { _filename _path / _name; // 创建命名管道 int n mkfifo(_filename.c_str(), 0666); if (n 0) { std::cerr mkfifo failed std::endl; } else { std::cout mkfifo success std::endl; } } ~NamedFifo() { // 回收命名管道 int n unlink(_filename.c_str()); if (n 0) { std::cerr remove fifo failed std::endl; } else { std::cout remove fifo success std::endl; } } private: std::string _path; std::string _name; std::string _filename; }; class Fileoper { public: Fileoper(const std::string path, const std::string name) : _path(path), _name(name), _fd(-1) { _filename _path / _name; } void OpenForRead() { _fd open(_filename.c_str(), O_RDONLY); if(_fd 0) { std::cerr open fifo failed std::endl; } else { std::cout open fifo success std::endl; } } void OpenForWrite() { _fd open(_filename.c_str(), O_WRONLY); if(_fd 0) { std::cerr open fifo failed std::endl; } else { std::cout open fifo success std::endl; } } void Write() { std::string message; while(true) { std::cout Please Enter#; std::getline(std::cin, message); write(_fd, message.c_str(), message.size()); } } void Read() { while(true) { char buffer[1024]; ssize_t n read(_fd, buffer, sizeof(buffer)-1); if(n 0) { buffer[n] 0; std::cout Client say# buffer std::endl; } else if(n 0) { std::cout Client quit! me too! std::endl; break; } else { std::cerr read error std::endl; break; } } } void Close() { close(_fd); } ~Fileoper() {} private: std::string _path; std::string _name; std::string _filename; int _fd; };