Linux命名管道(FIFO)通信:从原理到实操,一文搞懂跨进程通信

🔥个人主页:Cx330🌸
❄️个人专栏:《C语言》《LeetCode刷题集》《数据结构-初阶》《C++知识分享》
《优选算法指南-必刷经典100题》《Linux操作系统》:从入门到入魔
🌟心向往之行必能至
🎥Cx330🌸的简介:

目录
前言:
在Linux进程间通信(IPC)中,管道是最基础、最常用的通信方式之一,但我们平时接触的“匿名管道”有一个致命缺陷——只能用于有血缘关系的进程(父进程与子进程、兄弟进程),无法实现无关联进程间的通信。
而命名管道(简称FIFO),正是为解决这个问题而生。它给管道赋予了一个“文件名”,通过文件系统作为媒介,让任意两个进程(无论是否有血缘关系)都能通过这个“命名”实现数据交互。今天这篇博客,就从原理到实操,手把手教你搞懂Linux命名管道,亲手实现跨进程通信。
一、先搞懂:命名管道(FIFO)是什么?
1. 命名管道的本质
命名管道和匿名管道的核心通信逻辑完全一致——都是基于“字节流”的半双工通信(同一时刻只能单向传输数据),底层都是内核中的缓冲区。
两者的核心区别的是:命名管道有一个可见的文件系统入口(一个以.fifo或.pipe为后缀的文件),这个文件只是一个“标识”,不存储实际数据(数据仍在内存缓冲区中);而匿名管道没有文件入口,只能通过父子进程继承文件描述符来通信。
简单来说:匿名管道是“隐式的、血缘专属”,命名管道是“显式的、通用的”,只要知道FIFO文件的路径,任意进程都能与之通信。
2. 命名管道的核心特点
- 跨进程通信:无血缘关系的进程(如两个独立的程序),只要知道FIFO文件路径,就能通过它通信;
- 半双工通信:数据只能单向流动,若要实现双向通信,需要创建两个FIFO(一个用于A→B,一个用于B→A);
- 基于文件标识:FIFO文件存在于文件系统中,进程通过打开这个文件,获取读写文件描述符,进而实现通信;
- 阻塞特性:默认情况下,打开FIFO的读端会阻塞,直到有进程打开写端;打开写端也会阻塞,直到有进程打开读端(可通过O_NONBLOCK设置为非阻塞);
- 生命周期:FIFO文件不会随进程退出而自动删除,需要手动删除(rm命令或unlink函数),否则会一直存在于文件系统中。


3. 命名管道与匿名管道的对比
为了更清晰区分,做一个简单对比表,避免混淆:
对比维度 | 匿名管道(pipe) | 命名管道(FIFO) |
|---|---|---|
通信范围 | 有血缘关系的进程(父子、兄弟) | 任意进程(无血缘关系也可) |
标识方式 | 无文件标识,通过文件描述符继承 | 有文件系统入口(FIFO文件) |
创建方式 | 通过pipe()系统调用创建 | 通过mkfifo()系统调用或mkfifo命令创建 |
生命周期 | 随进程退出而销毁(文件描述符关闭) | 随FIFO文件删除而销毁,进程退出不影响文件 |
使用场景 | 父子进程间简单通信(如shell管道) | 无关联进程间通信(如两个独立程序交互) |
二. 命名管道的创建方式
命名管道有两种创建方式:命令行创建和代码创建,本质都是在文件系统中生成一个 FIFO 类型的文件。
2.1 命令行创建(mkfifo 命令)
直接通过mkfifo命令创建命名管道,语法简单,适合快速测试:
# 创建名为myfifo的命名管道 mkfifo myfifo # 查看管道文件(类型标识为p) ls -l myfifo 
我们可以发现,这里创建的管道文件(最前面的标识是p开头),就算我们是不停的在往里面进行写操作,但是它的文件大小是一直不变的,验证了我们上面图中所说的它不是一个普通文件。
2.2 代码创建(mkfifo 函数)
通过 mkfifo()系统调用在代码中创建命名管道,需指定管道路径和权限,原型如下:
#include <sys/stat.h> #include <sys/types.h> // 参数:pathname-管道路径;mode-权限(如0644) int mkfifo(const char *pathname, mode_t mode); 代码示例(创建命名管道)
#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; } 2.3 命名管道的打开规则
命名管道的打开(open())行为与普通文件不同,核心是 “读端与写端的同步”—— 仅当管道的读端和写端都被打开后,通信才能正常进行,具体规则如下:
O_RDONLY:以只读方式打开(读端),默认阻塞,直到有进程以O_WRONLY方式打开;O_WRONLY:以只写方式打开(写端),默认阻塞,直到有进程以O_RDONLY方式打开;- O_RDWR:以读写方式打开,不阻塞,直接打开(同时具备读和写权限,可实现单向通信的 “自我循环”)
注意:实际开发中,建议读端以O_RDONLY打开,写端以O_WRONLY打开,避免使用O_RDWR(可能导致通信逻辑混乱)。
三、实操实现:手搓命名管道通信
server.c:作为服务端,监听管道,接收客户端消息并打印;client.c:作为客户端,向管道发送消息,实现双向交互。
3.1 前置准备(Makefile && comm.h)
Makefile:
all:client server client:client.cc g++ -o $@ $^ -std=c++11 server:server.cc g++ -o $@ $^ -std=c++11 .PHONY:clean clean: rm -f client servercomm.h:
#pragma once #include <string> const std::string fifoname="fifo";3.2 服务端程序(server.c)
#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; }3.3 客户端程序(client.c)
#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() { int wfd=open(fifoname.c_str(),O_WRONLY); if(wfd<0) { perror("open"); return 1; } std::string outstring; while(true) { std::cout<<"Please Enter@ "; std::cin>>outstring; write(wfd,outstring.c_str(),outstring.size());// 要不要写'\0'? 不需要! } close(wfd); return 0; }3.4 编译运行

四、命名管道的实际应用场景
命名管道因其“跨进程、简单易用”的特点,在Linux开发中应用广泛,常见场景包括:
- 后台服务与前台程序通信:如后台守护进程(如日志服务),通过FIFO接收前台程序发送的日志指令,实现日志的动态控制;
- 多个独立程序的数据交互:如两个不同的服务程序(如数据库服务和缓存服务),通过FIFO传递数据,无需复杂的IPC机制;
- shell脚本间通信:在shell脚本中,通过mkfifo命令创建FIFO,实现两个脚本之间的数据传输(比匿名管道更灵活);
- 简单的客户端/服务器模型:基于FIFO实现简单的C/S架构,客户端向FIFO写入请求,服务器从FIFO读取请求并处理,返回结果。
五、常见坑点总结(必看)
- 忘记处理阻塞:打开FIFO时未设置O_NONBLOCK,导致进程一直阻塞,误以为程序卡死;
- 忽略SIGPIPE信号:读端关闭后,写端继续写入,导致进程崩溃;
- FIFO文件残留:未用unlink()删除FIFO,下次创建时报错;
- 路径不一致:读端和写端使用的FIFO路径不同,导致无法通信;
- 权限问题:mkfifo()设置的权限过低(如0600),导致其他进程无法打开FIFO,建议设置为0666;
- 数据错乱:未处理字节流无边界问题,读端读取的数据不完整或拼接错误。
六、总结
命名管道(FIFO)是Linux跨进程通信的基础方式,核心是“通过文件系统标识,实现任意进程间的字节流通信”。它继承了匿名管道的简单性,又解决了匿名管道“只能用于血缘进程”的缺陷,是入门IPC开发的必学知识点。
本文通过“原理→API→实操→避坑”的流程,手把手教你实现命名管道的单方向和双向通信,代码可直接复用。重点掌握FIFO的阻塞特性、SIGPIPE信号处理和文件删除问题,就能避免绝大多数踩坑场景。
实际开发中,若需要更高效、更复杂的通信(如消息优先级、双向通信的更优实现),可后续学习消息队列、共享内存等IPC机制,但命名管道的核心思想(字节流、文件标识),是理解所有IPC机制的基础。
最后,附上本文所有实操代码的仓库链接,方便大家下载测试和修改:fifo