Linux 进程间通信之命名管道(FIFO):跨进程通信的实用方案

Linux 进程间通信之命名管道(FIFO):跨进程通信的实用方案
在这里插入图片描述

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


🎬 博主简介:

在这里插入图片描述

文章目录


前言:

在 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函数创建,打开用open
  • FIFO(命名管道)与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模型)

联系:

  1. 通信机制:两者都是操作系统提供的进程间通信(IPC)方式,基于内核缓冲区实现数据传输。
  2. 数据特性:都提供可靠的字节流服务,数据写入和读取的顺序一致,无消息边界。
  3. 行为相似:默认情况下,读写操作具有相似的阻塞语义(读空阻塞、写满阻塞),并支持非阻塞标志。
  4. 单向性:本质上都是单向通信管道(半双工),若要双向通信需创建两个管道。
  5. 原子性:在 PIPE_BUF 限制内,写入操作具有原子性(多个进程同时写时数据不会交错)。

二. 命名管道的创建方式

命名管道有两种创建方式:命令行创建和代码创建,本质都是在文件系统中生成一个 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)intmkfifo(constchar*pathname, mode_t mode);

代码示例(创建命名管道)

#include<stdio.h>#include<sys/stat.h>#include<sys/types.h>#include<errno.h>#defineFIFO_PATH"./myfifo"intmain(){// 创建命名管道,权限0644(所有者读+写,其他读)int ret =mkfifo(FIFO_PATH,0644);if(ret ==-1){// 若管道已存在,errno为EEXIST,可忽略该错误if(errno != EEXIST){perror("mkfifo error");return1;}printf("命名管道已存在\n");}else{printf("命名管道创建成功\n");}return0;}

三. 命名管道的打开规则(关键!)

命名管道的打开(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)

#include<stdio.h>#include<unistd.h>#include<sys/stat.h>#include<sys/types.h>#include<fcntl.h>#include<errno.h>#include<string.h>#defineFIFO_PATH"./tp"#defineBUF_SIZE1024// 错误处理宏#defineERR_EXIT(m)do{perror(m);exit(EXIT_FAILURE);}while(0)intmain(){// 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");return0;}

4.1.2 读端程序(file_reader.c)

#include<stdio.h>#include<unistd.h>#include<sys/stat.h>#include<sys/types.h>#include<fcntl.h>#include<errno.h>#include<string.h>#defineFIFO_PATH"./tp"#defineBUF_SIZE1024#defineERR_EXIT(m)do{perror(m);exit(EXIT_FAILURE);}while(0)intmain(){// 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");return0;}

4.1.3 编译与运行

# 编译两个程序 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)

#include<iostream>#include<cstdio>#include<sys/types.h>#include<sys/stat.h>#include<unistd.h>#include<fcntl.h>#include"comm.h"intmain(){// 1. 创建管道文件umask(0);// 重置文件创建掩码,确保权限生效int n =mkfifo(fifoname.c_str(),0666);if(n <0){perror("mkfifo");return1;}// 2. 打开管道文件(阻塞等待客户端连接)int rfd =open(fifoname.c_str(), O_RDONLY);if(rfd <0){perror("open");return2;}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;}elseif(n ==0){// 写端关闭了break;}else{perror("read");break;}}// 4. 关闭文件描述符close(rfd);// 5. 删除管道文件unlink(fifoname.c_str());return0;}

4.2.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"intmain(){// 1. 以写方式打开管道(阻塞,直到服务端打开读端)int wfd =open(fifoname.c_str(), O_WRONLY);if(wfd<0){perror("open");return1;}// 2. 循环输入并发送消息给服务端 std::string outstring;while(true){ std::cout <<"Please Enter@ "; std::cin >> outstring;write(wfd, outstring.c_str(), outstring.size());// 要不要写\0? 不需要!}// 3. 关闭文件描述符close(wfd);return0;}

4.2.4 编译与运行

# 编译程序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()即可完成数据传输;
  • 与匿名管道相比,命名管道的核心优势是通信范围无限制,生命周期随内核。
在这里插入图片描述


在这里插入图片描述

结尾:

🍓 我是草莓熊 Lotso!若这篇技术干货帮你打通了学习中的卡点: 👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长 ❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量 ⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用 💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑 🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解 技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标! 

结语:本文从概念、创建、打开规则到实战案例,全面覆盖了命名管道的核心知识点。命名管道适合对通信效率要求不极致、需跨进程交互的场景(如日志收集、简单命令交互)。如果需要更高效率的跨进程通信(如高频数据传输),可后续学习共享内存;若需消息结构化传输,可了解消息队列。创作不易,觉得有帮助的话,欢迎点赞、收藏、关注三连~ 后续会持续更新 Linux IPC 系列(共享内存、消息队列、信号量),带你从底层吃透进程间通信技术。

✨把这些内容吃透超牛的!放松下吧✨ʕ˘ᴥ˘ʔづきらど

Read more

【大模型实战篇】基于Claude MCP协议的智能体落地示例

【大模型实战篇】基于Claude MCP协议的智能体落地示例

1. 背景         之前我们在《MCP(Model Context Protocol) 大模型智能体第一个开源标准协议》一文中,介绍了MCP的概念,虽然了解了其概念、架构、解决的问题,但还缺少具体的示例,来帮助进一步理解整套MCP框架如何落地。         今天我们基于claude的官方例子--获取天气预报【1】,来理解MCP落地的整条链路。 2. MCP示例         该案例是构建一个简单的MCP天气预报服务器,并将其连接到主机,即Claude for Desktop。从基本设置开始,然后逐步发展到更复杂的使用场景。         大模型虽然能力非常强,但其弊端就是内容是过时的,这里的过时不是说内容很旧,只是表达内容具有非实时性。比如没有获取天气预报和严重天气警报的能力。因此我们将使用MCP来解决这一问题。         构建一个服务器,该服务器提供两个工具:获取警报(get-alerts)和获取预报(get-forecast)。然后,将该服务器连接到MCP主机(在本例中为Claude for Desktop)。         首先我们配置下环

By Ne0inhk
AI Agent新范式:FastGPT+MCP协议实现工具增强型智能体构建

AI Agent新范式:FastGPT+MCP协议实现工具增强型智能体构建

AI Agent新范式:FastGPT+MCP协议实现工具增强型智能体构建 作者:高瑞冬 本文目录 * AI Agent新范式:FastGPT+MCP协议实现工具增强型智能体构建 * 一、MCP协议简介 * 二、创建MCP工具集 * 1. 获取MCP服务地址 * 2. 在FastGPT中创建MCP工具集 * 三、测试MCP工具 * 四、AI模型调用MCP工具 * 1. 调用单个工具 * 2. 调用整个工具集 * 五、私有化部署支持 * 1. 环境准备 * 2. 修改docker-compose.yml文件 * 3. 修改FastGPT配置 * 4. 重启服务 * 六、使用MCP-Proxy集成多个MCP服务 * 1. MCP-Proxy简介 * 2. 安装MCP-Proxy * 3. 配置MCP-Proxy * 4. 将MCP-Proxy与FastGPT集成 * 5. 高级配置

By Ne0inhk
AI革命先锋:DeepSeek与蓝耘通义万相2.1的无缝融合引领行业智能化变革

AI革命先锋:DeepSeek与蓝耘通义万相2.1的无缝融合引领行业智能化变革

云边有个稻草人-ZEEKLOG博客 目录 引言 一、什么是DeepSeek? 1.1 DeepSeek平台概述 1.2 DeepSeek的核心功能与技术 二、蓝耘通义万相2.1概述 2.1 蓝耘科技简介 2.2 蓝耘通义万相2.1的功能与优势 1. 全链条智能化解决方案 2. 强大的数据处理能力 3. 高效的模型训练与优化 4. 自动化推理与部署 5. 行业专用解决方案 三、蓝耘通义万相2.1与DeepSeek的对比分析 3.1 核心区别 3.2 结合使用的优势 四、蓝耘注册流程 五、DeepSeek与蓝耘通义万相2.1的集成应用 5.1 集成应用场景 1. 智能医疗诊断

By Ne0inhk
基于腾讯云HAI + DeepSeek快速设计自己的个人网页

基于腾讯云HAI + DeepSeek快速设计自己的个人网页

前言:通过结合腾讯云HAI 强大的云端运算能力与DeepSeek先进的 AI技术,本文介绍高效、便捷且低成本的设计一个自己的个人网页。你将了解到如何轻松绕过常见的技术阻碍,在腾讯云HAI平台上快速部署DeepSeek模型,仅需简单几步,就能获取一个包含个人简介、技能特长、项目经历及联系方式等核心板块的响应式网页。 目录 一、DeepSeek模型部署在腾讯云HAI 二、设计个人网页 一、DeepSeek模型部署在腾讯云HAI 把 DeepSeek 模型部署于腾讯云 HAI,用户便能避开官网访问限制,直接依托腾讯云 HAI 的超强算力运行 DeepSeek-R1 等模型。这一举措不仅降低了技术门槛,还缩短了部署时间,削减了成本。尤为关键的是,凭借 HAI 平台灵活且可扩展的特性,用户能够依据自身特定需求定制专属解决方案,进而更出色地适配特定业务场景,满足各类技术要求 。 点击访问腾讯云HAI控制台地址: 算力管理 - 高性能应用服务 - 控制台 腾讯云高性能应用服务HAI已支持DeepSeek-R1模型预装环境和CPU算力,只需简单的几步就能调用DeepSeek - R1

By Ne0inhk