【Linux】Linux进程间通信:命名管道(FIFO)的模拟实现重要知识点梳理
前言:欢迎各位光临本博客,这里小编带你直接手撕**,文章并不复杂,愿诸君**耐其心性,忘却杂尘,道有所长!!!!
《C语言》
《C++深度学习》
《Linux》
《数据结构》
《数学建模》
文章目录
- 一、命名管道(FIFO):跨进程通信的“桥梁”
- 1. 命名管道的核心特性
- 2. 命名管道的核心接口(代码级)
- (1)创建管道:mkfifo
- (2)删除管道:unlink
- 3. 命名管道实战:Server与Client通信
- (1)服务器端(server.cc):读数据
- (2)客户端(client.cc):写数据
- 4. 通信关键机制与问题解决
- (1)`open`阻塞机制
- (2)写端关闭的处理
- (3)常见Bug:数据打印乱码
- 5. 命名管道优化:封装成类(C++)
- (1)封装头文件(Fifo.hpp)
- (2)封装Read/Write接口
- (3)优化后Server与Client代码
- 6. 命名管道扩展:实现文件拷贝
- (4)宏定义优化:简化错误处理
一、命名管道(FIFO):跨进程通信的“桥梁”
命名管道(FIFO)是Linux中一种简单的进程间通信方式,核心优势是能让无血缘关系的进程(比如两个独立的程序)互相传递数据,就像搭建了一条专用“数据管道”。
1. 命名管道的核心特性
- 有固定文件名,存储在文件系统中(可用
ls
命令查看),但实际不存数据(数据在内存中)。 - 通信时需“两端配合”:必须有一个进程写(open为写模式)、一个进程读(open为读模式),否则
open
会阻塞(等另一端准备好)。
2. 命名管道的核心接口(代码级)
操作FIFO主要依赖3个核心接口:mkfifo
(创建管道)、open/close/read/write
(读写数据)、unlink
(删除管道)。
(1)创建管道:mkfifo
- 功能:在文件系统中创建一个FIFO文件,作为进程通信的“标识”。
- 头文件:需包含
<sys/stat.h>
和<sys/types.h>
。 - 接口参数与返回值:
pathname
:FIFO的文件名(比如"./myfifo"
),进程通过这个路径找到管道。mode
:管道的权限(比如0664
,表示所有者、组用户可读写,其他用户可读)。- 返回值:成功返回0,失败返回-1(比如文件已存在)。
(2)删除管道:unlink
- 功能:删除FIFO文件,释放管道占用的资源(避免文件残留)。
- 接口参数与返回值:
pathname
:要删除的FIFO文件名(和mkfifo
的路径一致)。- 返回值:成功返回0,失败返回-1(比如文件不存在)。
3. 命名管道实战:Server与Client通信
FIFO通信需要两个独立程序:server.cc
(读数据)和client.cc
(写数据),必须同时运行才能通信。
(1)服务器端(server.cc):读数据
核心逻辑:创建管道 → 打开管道(读模式) → 读取数据 → 关闭管道 → 删除管道。
代码细节如下:
- 创建管道:先判断
mkfifo
是否成功,失败则报错(比如管道已存在)。
- 删除管道:一般在程序结束前调用
unlink
,确保资源释放(即使程序异常退出,也建议在后续处理中删除)。
- 打开、读取、关闭管道:用
open
打开管道(读模式),read
循环读取数据,close
关闭文件描述符。
- 注意:
open
为读模式时,会阻塞等待,直到有客户端以写模式打开管道,才会返回。
- 注意:
(2)客户端(client.cc):写数据
核心逻辑:打开管道(写模式) → 写入数据 → 关闭管道(无需创建/删除管道,由服务器负责)。
代码细节如下:
- 客户端只需用
open
打开已存在的FIFO(写模式),直接write
写入数据即可;写完后close
,服务器会收到read
返回0的信号(表示写端关闭)。
4. 通信关键机制与问题解决
(1)open
阻塞机制
FIFO的open
有个“等待特性”:
- 若进程以“读模式”打开管道(
O_RDONLY
),会阻塞到有进程以“写模式”打开。 - 若进程以“写模式”打开管道(
O_WRONLY
),会阻塞到有进程以“读模式”打开。
效果如下:
只有两端都打开管道,才能开始通信(写端写数据,读端立刻能读到)。
(2)写端关闭的处理
当客户端(写端)close
后,服务器(读端)的read
会返回0(表示“无更多数据”)。此时需要在服务器代码中判断,避免无限循环读0。
修改前(无提示,读端会一直循环):
修改后(判断read
返回0,提示“写端关闭”并退出):
(3)常见Bug:数据打印乱码
若直接用printf("%s", buf)
打印读取的buf
,可能出现乱码——因为read
读取的是“二进制数据”,不一定以\0
结尾(C语言中%s
需要\0
标识字符串结束)。
Bug效果:
解决方法:手动给buf
加\0
(注意预留\0
的位置,比如buf
大小1024,最多读1023字节)。
修改代码:
修改后效果(无乱码):
5. 命名管道优化:封装成类(C++)
为了简化代码、避免重复操作(比如每次都写mkfifo
、open
、close
),可以用C++类封装FIFO的操作,核心思路是:
- 构造函数:创建管道、打开文件描述符。
- 析构函数:关闭文件描述符、删除管道。
- 提供
Read
和Write
成员函数,供外部调用。
(1)封装头文件(Fifo.hpp)
- 构造函数:创建管道并打开(根据“读/写角色”决定
open
模式)。
- 析构函数:关闭文件描述符、删除管道(确保资源释放)。
- 管道名优化:用宏定义统一管道路径,避免硬编码(方便修改)。
(2)封装Read/Write接口
- 读接口:循环读取数据,自动处理
\0
(避免乱码)。
- 写接口:接收字符串,直接写入管道。
(3)优化后Server与Client代码
- 服务器端(只需创建Fifo对象,调用
Read
):
- 客户端(创建Fifo对象,调用
Write
):
6. 命名管道扩展:实现文件拷贝
利用FIFO的通信能力,可以实现“客户端传文件、服务器存文件”的功能。核心逻辑是:
- 客户端:打开要拷贝的文件 → 读取文件内容 → 写入FIFO。
- 服务器:从FIFO读取内容 → 写入新文件。
代码示例(服务器端核心逻辑):
(4)宏定义优化:简化错误处理
频繁判断“函数返回值是否为-1”会让代码冗余,可定义宏来封装“错误打印+进程退出”的逻辑。
- 未用宏时(代码冗余):
- 定义宏(
\
是C语言的“换行符续接”,让宏跨多行更易读):
- 用宏后(代码简洁,直接调用
CHECK
即可):