目录
前言:
上一篇博客我们讲解了匿名管道 这种进程间通信方式,不过它有个明显的限制:++只能在有亲缘关系的进程之间通信。++
那么,有没有办法让完全不相关的进程也能像使用管道一样,安全地交换数据呢?
今天我们就来介绍匿名管道的 "亲兄弟"------命名管道(FIFO),它与匿名管道有很多的相似之处,但突破了亲缘关系的限制。
一、命名管道
我们知道:通信的基础就是两个进程要看到同一份资源(内存)。
💦当进程A和进程B同时打开文件a/b/c.xx,操作系统会不会把该文件在内存中加载两次?
当然不会,因为操作系统不会加载重复的资源到内存,没有意义!
回想前面:代码在形成可执行动态链接时,如果多个进程用到一个库,操作系统也不会将动态库加载多份到内存中,而是只加载一份到内存,多个进程通过虚拟内存映射,共享同一份物理内存,彻底避免重复加载,节省内存。
所以,两个或多个进程同时打开一个文件,此时这些进程就会看到同一个文件,即同一份资源,不就可以通信了。
但我们知道该文件肯定和普通文件有一些区别,普通文件当写入数据就会刷写到磁盘,而进程进行通信,并不需要将文件数据刷写到磁盘,所以该文件跟普通文件有区别,但和匿名管道文件相似 。
由于该文件有具体路径和名字,所以叫做命名管道。

二、创建命名管道
**•**命名管道可以从命令行上创建,命令行方法是使用下面这个命令:
bash
mkfifo file

**•**删除该管道文件可以使用:
bash
unlink file
**•**命名管道也可以从程序里创建,相关函数有:
cpp
int mkfifo(const char *filename,mode_t mode);

参数说明:
**pathname:**即你想在哪个路径下创建;
mode: 指创建完管道后的权限,一般 mode = 0666即可。
对命名管道的操作与普通文件完全相同,想要写就以写方式打开,想要读就以读方式打开;
cpp
// 从管道读
int fd = open("fifo", O_RDONLY);
read(fd, buffer, strlen(buffer));
// 向管道写
int fd = open("fifo", O_CREAT | O_WRONLY);
write(fd, buffer, strlen(buffer));
二、命名管道与匿名管道区别
↗️ 创建方式:
• 匿名管道由pipe函数创建并打开。
• 命名管道由mkfifo函数创建,打开用open。
• FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些 工作完成之后,它们具有相同的语义。
↗️ 命名管道打开规则:
1、默认阻塞模式(最常用)
- 只打开读端(O_RDONLY)--> 会阻塞,直到有进程打开写端。
- 只打开写端(O_WRONLY)--> 会阻塞,直到有进程打开读端。

一句话总结:默认情况下,读、写两端必须都有人打开,open () 才会返回,否则一直等!
2、非阻塞模式 :打开时加 O_NONBLOCK
- 只读打开(O_RDONLY | O_NONBLOCK)--> 立即成功返回,不等写端。
- 只写打开(O_WRONLY | O_NONBLOCK)--> 立即失败返回 - 1(因为没人读)。
3、两端都打开,管道就进入通信状态
读和写都打开,开始正常工作,此时读写行为与匿名管道往前相同:
- 写慢:读阻塞;
- 读慢:写满阻塞;
- 写关:读到文件末尾,返回0;
- 读关:写进程收到信号,被操作系统杀掉。
实例一:用命名管道实现文件拷贝
cpp
#pragma once
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string>
#include <unistd.h>
#define PATH "." // 表示当前路径
#define FIFONAME "fifo" // 命名管道的名字
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE);\
}while (0)
读取文件abc内容,写入命名管道
cpp
int main()
{
mkfifo("tp", 0644); // 创建管道
// 打开想要拷贝的文件(读)
int infd;
infd = open("abc", O_RDONLY);
if (infd == -1)
ERR_EXIT("open");
// 以写方式打开管道
int outfd;
outfd = open("tp", O_WRONLY);
if (outfd == -1)
ERR_EXIT("open");
char buf[1024];
// 从文件读并写入管道
int n;
while ((n = read(infd, buf, 1024)) > 0)
{
write(outfd, buf, n);
}
close(infd);
close(outfd);
return 0;
}
读取管道,写入目标文件 abc.bak:
cpp
int main()
{
int outfd;
// 打开目标文件
outfd = open("abc.bak", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (outfd == -1)
ERR_EXIT("open");
// 以读方式打开管道
int infd;
infd = open("tp", O_RDONLY);
if (outfd == -1)
ERR_EXIT("open");
char buf[1024];
// 从管道读并写入文件
int n;
while ((n = read(infd, buf, 1024)) > 0)
{
write(outfd, buf, n);
}
close(infd);
close(outfd);
unlink("tp");
return 0;
}
效果演示:


三、实现两个进程的简单通信
实例二:两进程通信
cpp
// ------------------头文件---------------------
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string>
#include <unistd.h>
#include <cstdio>
#define PATH "." // 表示当前路径
#define FIFONAME "fifo" // 命名管道的名字
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE);\
}while (0)
以读方式打开管道,接收消息:
cpp
#include "comm.h"
int main()
{
// 创建命名管道
umask(0);
int n = mkfifo(FIFONAME, 0666);
if (n == 0)
std::cout << "mkfifo success!" << std::endl;
else if (n < 0)
ERR_EXIT("mkfifo");
// 打开文件进行读
while (true)
{
int fd = open(FIFONAME, O_RDONLY);
if (fd < 0)
{
ERR_EXIT("open");
}
else
{
char buffer[1024];
// 从管道读
ssize_t number = read(fd, buffer, sizeof(buffer) - 1);
if (number > 0)
{
buffer[number] = 0; // 手动放置'\0'
std::cout << "client say: " << buffer << std::endl;
}
else if (number == 0)
{
std::cout << "client quit!" << std::endl;
break;
}
else
ERR_EXIT("read");
}
close(fd);
}
// 删除管道
n = unlink("./fifo");
if (n == 0)
std::cout << "remove fifo success!" << std::endl;
else
std::cout << "remove fifo failed!" << std::endl;
return 0;
}
以写方式打开管道,发送消息:
cpp
#include "comm.h"
int main()
{
int fd = open(FIFONAME, O_WRONLY);
if (fd < 0)
{
ERR_EXIT("open");
}
else
{
std::string message;
int cnt = 1;
while (true)
{
std::cout << "Please Enter: ";
getline(std::cin, message);
message += (", message number:" + std::to_string(cnt++) + ",[" + std::to_string(getpid()) + "]");
// 写入管道,发送
write(fd, message.c_str(), message.size());
}
}
close(fd);
return 0;
}
效果演示:

四、总结
通过本文的学习,我们深入理解了命名管道(Named Pipe)这一重要的进程间通信机制。从基础概念到实际应用,从与匿名管道的对比到跨进程通信的实现,命名管道展现了其在Linux/Unix系统中不可替代的价值。
如果你在学习过程中遇到问题,欢迎在评论区留言交流。觉得文章有帮助的话,别忘了点赞收藏,转发给更多需要的朋友!!!