
🔥草莓熊Lotso: 个人主页
❄️个人专栏: 《C++知识分享》 《Linux 入门到实践:零基础也能懂》
✨生活是默默的坚持,毅力是永久的享受!
🎬 博主简介:

文章目录
- 前言:
- [一. 命名管道核心概念:什么是 FIFO?](#一. 命名管道核心概念:什么是 FIFO?)
-
- [1.1 命名管道的定义](#1.1 命名管道的定义)
- [1.2 命名管道的核心特性](#1.2 命名管道的核心特性)
- [1.3 命名管道和匿名管道的区别与联系](#1.3 命名管道和匿名管道的区别与联系)
- [二. 命名管道的创建方式](#二. 命名管道的创建方式)
-
- [2.1 命令行创建(mkfifo 命令)](#2.1 命令行创建(mkfifo 命令))
- [2.2 代码创建(mkfifo 函数)](#2.2 代码创建(mkfifo 函数))
- [三. 命名管道的打开规则(关键!)](#三. 命名管道的打开规则(关键!))
- [四. 命名管道实战案例](#四. 命名管道实战案例)
-
- [4.1 案例 1:命名管道实现文件拷贝](#4.1 案例 1:命名管道实现文件拷贝)
-
- [4.1.1 写端程序(file_writer.c)](#4.1.1 写端程序(file_writer.c))
- [4.1.2 读端程序(file_reader.c)](#4.1.2 读端程序(file_reader.c))
- [4.1.3 编译与运行](#4.1.3 编译与运行)
- [4.2 案例 2:命名管道实现 Server-Client 通信](#4.2 案例 2:命名管道实现 Server-Client 通信)
-
- [4.2.1 前置准备(Makefile && comm.h)](#4.2.1 前置准备(Makefile && comm.h))
- [4.2.2 服务端程序(server.c)](#4.2.2 服务端程序(server.c))
- [4.2.3 客户端程序(client.c)](#4.2.3 客户端程序(client.c))
- [4.2.4 编译与运行](#4.2.4 编译与运行)
- [五. 命名管道使用避坑指南和总结](#五. 命名管道使用避坑指南和总结)
- 结尾:
前言:
在 Linux IPC(进程间通信)体系中,匿名管道解决了亲缘进程(父子、兄弟进程)间的通信问题,但存在一个核心限制 ------ 仅支持具有共同祖先的进程通信。而命名管道(FIFO)作为匿名管道的扩展,通过文件系统中的 "命名" 标识,打破了亲缘关系的束缚,实现了无关联进程间的双向数据传输。本文将从命名管道的核心概念、创建方式、通信原理和实战案例出发,带你吃透这一实用的跨进程通信技术。
一. 命名管道核心概念:什么是 FIFO?
1.1 命名管道的定义
命名管道(Named Pipe),又称 FIFO(First In First Out),是一种特殊的文件系统对象(类型为p),其核心本质与匿名管道一致 ------ 内核中的一块缓冲区,但通过文件路径作为标识,让任意进程都能通过该路径访问管道,实现跨进程通信。
与匿名管道相比,命名管道的核心差异的是 "命名":
- 匿名管道 :无文件路径,仅通过
pipe()创建的文件描述符在亲缘进程间共享; - 命名管道 :有明确的文件路径(如
/tmp/myfifo),任意进程可通过open()打开该路径,实现通信。
1.2 命名管道的核心特性
- 跨进程通信:支持无亲缘关系的进程(如两个独立的应用程序)通信,突破匿名管道的亲缘限制;
- 半双工通信:数据单向流动,如需双向通信需创建两个命名管道;
- 基于文件操作 :遵循 Linux "一切皆文件" 思想,通过
open()/read()/write()/close()等标准文件接口操作; - 生命周期随内核:命名管道创建后,即使创建进程退出,管道文件仍存在于文件系统中,需手动删除(unlink()或rm命令);
- 同步与互斥 :内核自动保证管道操作的同步(如读阻塞、写阻塞)和互斥(同一时间仅允许一个进程写)。

1.3 命名管道和匿名管道的区别与联系
匿名管道由pipe函数创建并打开。命名管道由mkfifo函数创建,打开用openFIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,⼀但这些工作完成之后,它们具有相同的语义。为了更清晰地理解命名管道的定位,整理两者的核心差异更详细的对比如下表所示
| 特性 | 匿名管道 | 命名管道 (FIFO) |
|---|---|---|
| 定义 | 一种半双工通信通道,通常用于具有亲缘关系的进程之间(如父子进程)。 | 一种特殊的文件(存在于文件系统中),允许无亲缘关系的进程间通信。 |
| 创建方式 | 通过 pipe() 系统调用创建,返回两个文件描述符(读端和写端)。 |
通过 mkfifo() 或 mknod() 创建,在文件系统中具有路径名。 |
| 标识 | 无名,仅通过文件描述符引用。 | 有名,通过文件系统中的路径名标识。 |
| 进程关系 | 仅适用于有共同祖先(如父子进程)的进程。 | 适用于任意进程,无论是否有亲缘关系。 |
| 通信方向 | 单向(半双工),数据只能从一个方向流动。 | 通常也是单向(半双工),但可通过打开两个管道实现双向通信。 |
| 持久性 | 随进程存在而存在,所有相关进程关闭管道后自动销毁。 | 随文件系统存在,可显式删除(unlink),即使没有进程打开也不会消失。 |
| 打开方式 | 无需显式打开,创建时直接获得文件描述符。 | 必须像普通文件一样用 open() 打开,使用路径名。 |
| 数据模型 | 字节流,无消息边界。 | 字节流,无消息边界。 |
| 阻塞行为 | 默认阻塞:读空管道或写满管道会使进程阻塞(可设置非阻塞)。 | 默认阻塞:读空 FIFO 或写满 FIFO 会使进程阻塞(可设置非阻塞)。 |
| 典型应用场景 | 父子进程间的简单通信,如命令管道 ` | ` 在 shell 中的使用。 |
| 系统限制 | 容量通常有限(如 64KB),取决于系统实现。 | 容量通常有限,类似匿名管道,但受文件系统影响。 |
| 对比维度 | 匿名管道(pipe) | 命名管道(FIFO) |
|---|---|---|
| 通信范围 | 仅亲缘进程(父子、兄弟) | 任意进程(无亲缘关系) |
| 创建方式 | pipe()系统调用 |
mkfifo()函数或mkfifo命令 |
| 标识方式 | 文件描述符(fd[0]、fd[1]) | 文件系统路径(如./myfifo) |
| 生命周期 | 随进程(所有进程关闭描述符后释放) | 随内核(需手动unlink()删除) |
| 打开方式 | 无需open(),创建即打开 |
需通过open()打开路径 |
| 核心用途 | 亲缘进程间简单通信 | 无关联进程间通信(如C/S模型) |
联系:
- 通信机制:两者都是操作系统提供的进程间通信(IPC)方式,基于内核缓冲区实现数据传输。
- 数据特性:都提供可靠的字节流服务,数据写入和读取的顺序一致,无消息边界。
- 行为相似:默认情况下,读写操作具有相似的阻塞语义(读空阻塞、写满阻塞),并支持非阻塞标志。
- 单向性:本质上都是单向通信管道(半双工),若要双向通信需创建两个管道。
- 原子性 :在
PIPE_BUF限制内,写入操作具有原子性(多个进程同时写时数据不会交错)。
二. 命名管道的创建方式
命名管道有两种创建方式:命令行创建和代码创建,本质都是在文件系统中生成一个 FIFO 类型的文件。
2.1 命令行创建(mkfifo 命令)
直接通过mkfifo命令创建命名管道,语法简单,适合快速测试:
bash
# 创建名为myfifo的命名管道
mkfifo myfifo
# 查看管道文件(类型标识为p)
ls -l myfifo

我们可以发现,这里创建的管道文件(最前面的标识是p开头),就算我们是不停的在往里面进行写操作,但是它的文件大小是一直不变的,验证了我们上面图中所说的它不是一个普通文件。另外当我们直接终止读端的时候,这个时候的情况就相当于读端关闭,写端还在一起写,所以操作系统直接杀死了我们的进程。
2.2 代码创建(mkfifo 函数)
通过mkfifo()系统调用在代码中创建命名管道,需指定管道路径和权限,原型如下:
cpp
#include <sys/stat.h>
#include <sys/types.h>
// 参数:pathname-管道路径;mode-权限(如0644)
int mkfifo(const char *pathname, mode_t mode);
代码示例(创建命名管道)
cpp
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#define FIFO_PATH "./myfifo"
int main()
{
// 创建命名管道,权限0644(所有者读+写,其他读)
int ret = mkfifo(FIFO_PATH, 0644);
if (ret == -1)
{
// 若管道已存在,errno为EEXIST,可忽略该错误
if (errno != EEXIST)
{
perror("mkfifo error");
return 1;
}
printf("命名管道已存在\n");
}
else
{
printf("命名管道创建成功\n");
}
return 0;
}
三. 命名管道的打开规则(关键!)
命名管道的打开(open())行为与普通文件不同,核心是 "读端与写端的同步"------ 仅当管道的读端和写端都被打开后,通信才能正常进行,具体规则如下:
| 打开方式 | 行为描述 |
|---|---|
读方式打开(O_RDONLY) |
- 若管道无写端打开:阻塞,直到有进程以写方式打开该管道; - 若指定 O_NONBLOCK:不阻塞,直接返回成功 |
写方式打开(O_WRONLY) |
- 若管道无读端打开:阻塞,直到有进程以读方式打开该管道; - 若指定 O_NONBLOCK:不阻塞,返回失败(errno=ENXIO) |
读写方式打开(O_RDWR) |
不阻塞,直接打开(同时具备读和写权限,可实现单向通信的 "自我循环") |
注意 :实际开发中,建议读端以
O_RDONLY打开,写端以O_WRONLY打开,避免使用O_RDWR(可能导致通信逻辑混乱)。
四. 命名管道实战案例
下面通过两个独立的程序(writer.c写端、reader.c读端)演示命名管道的跨进程通信,实现 "写端输入数据,读端接收并打印" 的功能。
4.1 案例 1:命名管道实现文件拷贝
通过两个程序配合,实现文件拷贝功能:
file_writer.c:读取本地文件,将内容写入命名管道;file_reader.c:从命名管道读取内容,写入目标文件。
4.1.1 写端程序(file_writer.c)
cpp
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#define FIFO_PATH "./tp"
#define BUF_SIZE 1024
// 错误处理宏
#define ERR_EXIT(m) do { perror(m); exit(EXIT_FAILURE); } while(0)
int main() {
// 1. 创建命名管道
int ret = mkfifo(FIFO_PATH, 0644);
if (ret == -1 && errno != EEXIST)
{
ERR_EXIT("mkfifo error");
}
// 2. 打开本地文件(待拷贝的源文件abc)
int infd = open("abc", O_RDONLY);
if (infd == -1)
{
ERR_EXIT("open source file error");
}
// 3. 以写方式打开命名管道(阻塞,直到读端打开)
int outfd = open(FIFO_PATH, O_WRONLY);
if (outfd == -1) {
ERR_EXIT("open fifo error");
}
// 4. 读取源文件内容,写入管道
char buf[BUF_SIZE];
int n;
while ((n = read(infd, buf, BUF_SIZE)) > 0)
{
// 写入管道(保证数据完整写入)
if (write(outfd, buf, n) != n)
{
ERR_EXIT("write to fifo error");
}
}
// 5. 关闭文件描述符
close(infd);
close(outfd);
printf("文件内容写入管道完成\n");
return 0;
}
4.1.2 读端程序(file_reader.c)
cpp
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#define FIFO_PATH "./tp"
#define BUF_SIZE 1024
#define ERR_EXIT(m) do { perror(m); exit(EXIT_FAILURE); } while(0)
int main()
{
// 1. 以读方式打开命名管道(阻塞,直到写端打开)
int infd = open(FIFO_PATH, O_RDONLY);
if (infd == -1)
{
ERR_EXIT("open fifo error");
}
// 2. 创建目标文件(拷贝后的文件abc.bak)
int outfd = open("abc.bak", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (outfd == -1)
{
ERR_EXIT("open target file error");
}
// 3. 从管道读取内容,写入目标文件
char buf[BUF_SIZE];
int n;
while ((n = read(infd, buf, BUF_SIZE)) > 0)
{
if (write(outfd, buf, n) != n) {
ERR_EXIT("write to target file error");
}
}
// 4. 关闭文件描述符,删除命名管道
close(infd);
close(outfd);
unlink(FIFO_PATH); // 手动删除管道文件
printf("文件拷贝完成\n");
return 0;
}
4.1.3 编译与运行
bash
# 编译两个程序
gcc file_writer.c -o writer
gcc file_reader.c -o reader
# 终端1:运行读端(阻塞等待写端)
./reader
# 终端2:运行写端(开始拷贝)
./writer
运行结果:
- 写端读取
abc文件内容,写入命名管道tp; - 读端从
tp读取内容,写入abc.bak; - 拷贝完成后,读端删除管道文件,两个程序退出。
4.2 案例 2:命名管道实现 Server-Client 通信
实现一个简单的 C/S 模型:
server.c:作为服务端,监听管道,接收客户端消息并打印;client.c:作为客户端,向管道发送消息,实现双向交互。
4.2.1 前置准备(Makefile && comm.h)
Makefile

comm.h

4.2.2 服务端程序(server.c)
cpp
#include <iostream>
#include <cstdio>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include "comm.h"
int main()
{
// 1. 创建管道文件
umask(0); // 重置文件创建掩码,确保权限生效
int n = mkfifo(fifoname.c_str(), 0666);
if(n < 0)
{
perror("mkfifo");
return 1;
}
// 2. 打开管道文件(阻塞等待客户端连接)
int rfd = open(fifoname.c_str(), O_RDONLY);
if(rfd < 0)
{
perror("open");
return 2;
}
char inbuffer[1024];
// 3. 进行通信, 循环接收客户端消息
while(true)
{
ssize_t n = read(rfd, inbuffer, sizeof(inbuffer)-1);
if(n > 0)
{
inbuffer[n] = 0;
std::cout << "client say# " << inbuffer <<std::endl;
}
else if(n == 0)
{
// 写端关闭了
break;
}
else{
perror("read");
break;
}
}
// 4. 关闭文件描述符
close(rfd);
// 5. 删除管道文件
unlink(fifoname.c_str());
return 0;
}
4.2.3 客户端程序(client.c)
cpp
#include <iostream>
#include <string>
#include <cstdio>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "comm.h"
int main()
{
// 1. 以写方式打开管道(阻塞,直到服务端打开读端)
int wfd = open(fifoname.c_str(), O_WRONLY);
if(wfd<0)
{
perror("open");
return 1;
}
// 2. 循环输入并发送消息给服务端
std::string outstring;
while(true)
{
std::cout << "Please Enter@ ";
std::cin >> outstring;
write(wfd, outstring.c_str(), outstring.size()); // 要不要写\0? 不需要!
}
// 3. 关闭文件描述符
close(wfd);
return 0;
}
4.2.4 编译与运行
bash
# 编译程序
make
# 终端1:启动服务端(阻塞等待客户端)
./server
# 终端2:启动客户端(输入消息交互)
./client
必须先打开服务端再打开客服端,大家可以自己去尝试一下。



- 我们可以加一点代码更好的去验证一下观察上面的现象啥的



五. 命名管道使用避坑指南和总结
- 必须手动删除管道文件 :命名管道创建后会残留于文件系统,若不删除,下次创建会报错(errno=EEXIST),建议在通信结束后用
unlink()删除; - 避免单进程同时读写 :虽然可通过
O_RDWR打开管道实现单进程读写,但会破坏半双工特性,容易导致数据混乱; - 处理阻塞场景 :读端未打开时写端会阻塞,写端未打开时读端会阻塞,若需非阻塞操作,可在
open()时添加O_NONBLOCK标志; - 数据完整性保证 :当写入数据量
≤PIPE_BUF(默认 4096 字节)时,内核保证写入原子性;超过则不保证,需在应用层处理分包; - 权限设置合理 :创建管道时权限需开放给通信进程(如 0664 允许同组进程访问),避免因权限不足导致
open()失败。
总结:命名管道(FIFO)是匿名管道的重要扩展,其核心价值在于突破了亲缘进程的限制,通过文件系统路径实现任意进程间的通信,且沿用了 Linux 标准的文件操作接口,上手成本低。
- 命名管道是带文件路径的内核缓冲区,支持跨进程通信
- 需通过
mkfifo()或mkfifo命令创建,open()时需遵循读 / 写端同步规则; - 实战中可实现文件拷贝、C/S 通信等场景,配合
read()/write()即可完成数据传输; - 与匿名管道相比,命名管道的核心优势是通信范围无限制,生命周期随内核。


结尾:
html
🍓 我是草莓熊 Lotso!若这篇技术干货帮你打通了学习中的卡点:
👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长
❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量
⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用
💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑
🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解
技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标!
结语:本文从概念、创建、打开规则到实战案例,全面覆盖了命名管道的核心知识点。命名管道适合对通信效率要求不极致、需跨进程交互的场景(如日志收集、简单命令交互)。如果需要更高效率的跨进程通信(如高频数据传输),可后续学习共享内存;若需消息结构化传输,可了解消息队列。创作不易,觉得有帮助的话,欢迎点赞、收藏、关注三连~ 后续会持续更新 Linux IPC 系列(共享内存、消息队列、信号量),带你从底层吃透进程间通信技术。
✨把这些内容吃透超牛的!放松下吧✨ ʕ˘ᴥ˘ʔ づきらど
