【Linux】深入解析Linux命名管道(FIFO):原理、实现与实战应用

news2025/3/31 17:10:01

本文承接上文匿名管道:【Linux】深度解析Linux进程间通信:匿名管道原理、实战与高频问题排查-CSDN博客

深入探讨Linux进程间通信(IPC),以匿名管道为核心,详细阐述其通信目的、实现前提及机制。涵盖数据传输、资源共享等核心目的,说明共享OS资源与系统调用支持的实现前提。重点解析匿名管道和命名管道的机制、特征,包括内核缓冲区管理、文件描述符作用及进程通信的四种关键情况和五大特征。实战应用部分展示进程池、命令行管道的实现及代码示例,同时剖析关键技术细节与常见问题解决方案,为开发者提供全面的技术指导。

目录

deepseek 总结全文:

命名管道

原理:

匿名管道与命名管道的区别

命名管道的打开规则

命名管道的底层实现:

1.创建,删除命名管道:namedPipe.hpp

 2.打开管道

函数传参规则  

3.读写管道

4.开始通信

 让服务端与写入端进行调用:

写入端关闭,读取端也应该关闭

读取端关闭,写入端再写入时被关闭 -- 同匿名管道


deepseek 总结全文:

  • 命名管道(FIFO)的核心概念
    • 允许无亲缘关系的进程通过文件系统路径访问同一内核缓冲区进行通信。
    • 本质是特殊文件,数据不落盘,仅在内核缓冲区中传输。
    • 与匿名管道的区别:命名管道通过mkfifo创建并需显式打开,匿名管道通过pipe函数创建。

命名管道的操作规则

  • 打开规则
    • 读端阻塞直到有写端打开(非阻塞模式直接返回)。

    • 写端阻塞直到有读端打开(非阻塞模式返回ENXIO错误)。

  • 读写规则

    • 读端关闭后,写端会收到SIGPIPE信号(进程终止)。

    • 写端关闭后,读端read返回0,触发退出逻辑。

  • 底层实现与代码实战

    • C++封装类:通过NamePiped类管理命名管道的生命周期(创建、打开、读写、删除)。

    • 服务端与客户端设计

      • 服务端:创建管道、以读模式打开、循环读取数据。

      • 客户端:以写模式打开管道、接收用户输入并发送。

    • 同步机制:服务端需等待客户端打开管道后才能继续执行,实现隐式进程同步。

  • 关键代码逻辑

    • 创建与删除mkfifounlink系统调用。

    • 读写实现:基于readwrite系统函数,缓冲区大小为4KB。

    • 异常处理:对SIGPIPE信号的容错(如服务端检测到写端关闭后主动退出)。

  • 实际运行效果

    • 验证了命名管道在不同场景下的行为(如读写端关闭的响应)。

    • 通过终端截图展示了通信过程及错误处理逻辑。

命名管道

管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。

如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。命名管道是一种特殊类型的文件

原理:

既然是要支持两个毫无关系的进程能够进行通信,那么就需要保证他们打开同一个文件,那么他们就需要在同一个文件内核缓冲区即管道当中写入和读取内容,一个管道属于一个文件,想要找到一个文件就可以通过这个唯一性文件路径进行寻找。而通信时不需要将写入端写入的内容刷新到所打开的文件当中,因此这个文件属于一种特殊文件--->命名管道 ---> 与管道一样是内核级的通信。

文件系统的内容可以看这篇博客:深入理解Linux文件系统:从磁盘结构到inode与挂载-CSDN博客

如图:使用mkfifo指令创建文件,也可以使用mkfifo函数创建文件

如图:可以一直写入

while :;do sleep 1;echo "hello named pipe"; done >> myfifo

可以一直打印

如果先关闭读端(右边),那么左边的写端就会直接崩掉,因为echo是一个内建命令,内建命令是属于是bash在写,操作系统就会直接干掉bash,就会导致这个终端直接退出:

如图:

无论在myfifo中写入多少内容,他的文件大小始终是0 


匿名管道与命名管道的区别

  • 匿名管道由pipe函数创建并打开。
  • 命名管道由mkfifo函数创建,打开用open
  • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义

命名管道的打开规则

如果当前打开操作是为读而打开FIFO时

O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO

O_NONBLOCK enable:立刻返回成功

如果当前打开操作是为写而打开FIFO时

O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO O_NONBLOCK enable:立刻返回失败,错误码为ENXIO


命名管道的底层实现:

需要准备四个文件:

namedPipe.hpp --->用于管理命名管道       server.cc         client.cc

serverclient 是两个常见的角色名称,这种命名方式是为了模拟一种典型的客户端-服务器(Client-Server)架构

  • Server(服务器):通常是指提供服务的一方,它等待客户端的请求,并根据请求提供相应的服务。

  • Client(客户端):是指请求服务的一方,它主动发起请求,等待服务器的响应。

Makefile:

.PHONY: all
all: client server

client: client.cc
	g++ -o $@ $^ -std=c++11

server: server.cc
	g++ -o $@ $^ -std=c++11

.PHONY: clean
clean:
	rm -rf client server

1.创建,删除命名管道:namedPipe.hpp

#include<iostream>
#include<cstdio>
#include<string>
#include<cerrno>
//mkfifo
#include <sys/types.h>
#include <sys/stat.h>

const std::string comm_path = "./myfifo";

int CreateNamePipe(const std::string &path)
{
    // int mkfifo(const char *pathname--->文件路径名,缺省权限:mode);
    int res = mkfifo(path.c_str(), 0666);
    if(res != 0)
    {
        perror("mkfifo");
    }
    return res;
}

int RemovedNamePipe(const std::string &path)
{
    int res = unlink(path.c_str());
    if(res != 0)
    {
        perror("unlink");
    }
    return res;
}

server.cc中进行调用CreateNamePipe,运行结果:创建成功

主要用来删除特殊文件:unlink ---> 删除指定目录下的文件:成功时为0,错误错误码被设置

       #include <unistd.h>

       int unlink(const char *pathname);

server.cc中进行调用CreateNamePipe、RemovedNamePipe,运行结果:删除成功

做好以上的准备工作:

现在我们需要让客户端client进行写入,服务端server进行读取,并且服务端server管理管道命名管道的整个生命周期,修改namedPipe.hpp对管道进行封装:

const std::string comm_path = "./myfifo";

class NamePiped
{
public:
    NamePiped(const std::string path)
        : _fifo_path(path)
    {
        int res = mkfifo(path.c_str(), 0666);
        if (res != 0)
        {
            perror("mkfifo");
        }
    }
    ~NamePiped()
    {
        sleep(10);//十秒后关闭管道
        int res = unlink(_fifo_path.c_str());
        if (res != 0)
        {
            perror("unlink");
        }
    }

private:
    const std::string _fifo_path;
};

 2.打开管道

定义使用者(客户端)和创建者(服务端)的id,文件的fd,只读只写的宏

#define DefaultFd -1 //文件的fd
#define Creater 1    
#define User 2
#define Read O_RDONLY  //只读
#define Write O_WRONLY //只写
private:
    const std::string _fifo_path;
    int _id;
    int _fd;
  • 打开文件的方法不想被看到,给外界提供调用,告诉外界的服务端和客户端文件是否打开成功
  • 创建者才需要创建管道,使用者只需要初始化管道
  • 析构时服务端关闭管道和文件描述符  ,  客户端只关闭文件描述符
class NamePiped
{

private:

    //打开文件的方法不想被看到,给外界提供调用,告诉外界的服务端()和客户端是否打开成功
    bool OpenNamedPipe(int mode)
    {
        _fd = open(_fifo_path.c_str(), mode);
        if (_fd < 0)
            return false;
        return true;
    }

public:
    NamePiped(const std::string path, int who)
        : _fifo_path(path), _id(who), _fd(DefaultFd)
    {
        // 是创建者才需要创建管道,使用者只需要初始化管道就可以
        if (_id == Creater)
        {
            int res = mkfifo(path.c_str(), 0666);
            if (res != 0)
            {
                perror("mkfifo");
            }
            std::cout << "creater create named pipe" << std::endl;
        }
    }

    bool OpenForRead()
    {
        return OpenNamedPipe(Read); 
    }

    bool OpenForWrite()
    {
        return OpenNamedPipe(Write);
    }

    ~NamePiped()
    {
        // 是创建者才可以删除管道,使用者不用管
        if (_id == Creater)
        {
            int res = unlink(_fifo_path.c_str());
            if (res != 0)
            {
                perror("unlink");
            }
            std::cout << "creater free named pipe" << std::endl;
        }
        if(_fd != DefaultFd) close(_fd);
    }

private:
    const std::string _fifo_path;
    int _id;
    int _fd;
};

服务端以读的方式打开文件 ,客户端以写的方式打开文件


//服务端
int main()
{
    NamePiped fifo(comm_path, Creater);
    // 服务端以读的方式打开文件
    fifo.OpenForRead();
    return 0;
}


// 客户端
int main()
{
    NamePiped fifo(comm_path, User);
    // 客户端以写的方式打开文件
    fifo.OpenForWrite();
    return 0;
}

函数传参规则  

  const &: const std::string &XXX   纯输入

    *      : std::string *            纯输出

    &      : std::string &            输入输出

3.读写管道

通信基本大小4KB

#define BaseSize 4096
    int ReadNamedPipe(std::string *out)
    {
        char buffer[BaseSize];
        //将_fd文件的内容读取到buffer
        int n = read(_fd, buffer, sizeof(buffer));
        if(n > 0)
        {
            buffer[n] = 0;
            *out = buffer;
        }
        return n;
    }
    //写入管道
    int WriteNamedPipe(const std::string &in)
    {
        // 向_fd文件写入内容,写in, 写入in的大小
        return write(_fd, in.c_str(), in.size());
    }

4.开始通信

 让服务端与写入端进行调用:

//服务端

int main()
{
    NamePiped fifo(comm_path, Creater);
    // 服务端以读的方式打开文件
    if (fifo.OpenForRead())
    {
        std::cout << "server open named pipe done" << std::endl;

        sleep(3);
        while (true)
        {
            std::string message;
            int n = fifo.ReadNamedPipe(&message);
            if (n > 0)
            {
                std::cout << "Client Say> " << message << std::endl;
            }
        }
    }
    return 0;
}

//客户端
int main()
{
    NamePiped fifo(comm_path, User);
    // 客户端以写的方式打开文件
    if (fifo.OpenForWrite())
    {
        std::cout << "client open named pipe done" << std::endl;
        while (true)
        {
            std::cout << "Please Enter> ";
            std::string message;
            std::getline(std::cin, message);
            fifo.WriteNamedPipe(message);
        }
    }
    return 0;
}

运行结果:

对于读取端服务端而言,已经将文件打开,但在写入端客户端未打开写入端时,是处于等待状态的,被阻塞在OpenForRead()调用中。直到客户端编译,打开了管道的写入端,服务端再继续执行,打开读入端。这是一种变相的进程同步。(与匿名管道的区别)

由上:客户端和服务端没有任何关系,实现了通信


写入端关闭,读取端也应该关闭

当写入端关闭后,读取端因为read的返回值为0,应该会退出;因此还需要修改服务端代码

int main()
{
    NamePiped fifo(comm_path, Creater);
    if (fifo.OpenForRead())
    {
        std::cout << "server open named pipe done" << std::endl;

        sleep(3);
        while (true)
        {
            std::string message;
            int n = fifo.ReadNamedPipe(&message);
            if (n > 0)
            {
                std::cout << "Client Say> " << message << std::endl;
            }
            else if(n == 0)
            {
                std::cout << "Client quit, Server Too!" << std::endl;
                break;
            }
            else
            {
                std::cout << "fifo.ReadNamedPipe Error" << std::endl;
                break;
            }
        }
    }
    return 0;
}

运行结果:写入端关闭,读取端也被关闭

读取端关闭,写入端再写入时被关闭 -- 同匿名管道

这是因为在写入端再次要写入内容的时候,OS判断出读取端已关闭检测出了异常,对写入端发送了13号SIGPIPE的信号,杀掉了写入端。


结语:

       随着这篇关于题目解析的博客接近尾声,我衷心希望我所分享的内容能为你带来一些启发和帮助。学习和理解的过程往往充满挑战,但正是这些挑战让我们不断成长和进步。我在准备这篇文章时,也深刻体会到了学习与分享的乐趣。

   

         在此,我要特别感谢每一位阅读到这里的你。是你的关注和支持,给予了我持续写作和分享的动力。我深知,无论我在某个领域有多少见解,都离不开大家的鼓励与指正。因此,如果你在阅读过程中有任何疑问、建议或是发现了文章中的不足之处,都欢迎你慷慨赐教。               

        你的每一条反馈都是我前进路上的宝贵财富。同时,我也非常期待能够得到你的点赞、收藏,关注这将是对我莫大的支持和鼓励。当然,我更期待的是能够持续为你带来有价值的内容,让我们在知识的道路上共同前行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2323528.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第十四届蓝桥杯省赛电子类单片机学习记录(客观题)

01.一个8位的DAC转换器&#xff0c;供电电压为3.3V&#xff0c;参考电压2.4V&#xff0c;其ILSB产生的输出电压增量是&#xff08;D&#xff09;V。 A. 0.0129 B. 0.0047 C. 0.0064 D. 0.0094 解析&#xff1a; ILSB&#xff08;最低有效位&#xff09;的电压增量计算公式…

vim的一般操作(分屏操作) 和 Makefile 和 gdb

目录 一. vim的基本概念 二. vim基础操作 2.1 插入模式 aio 2.2 [插入模式]切换至[正常模式] Esc 2.3[正常模式]切换至[末行模式] shift ; 2.4 替换模式 Shift R 2.5 视图&#xff08;可视&#xff09;模式 (可以快速 删除//注释 或者 增加//注释) ctrl v 三&…

Apache Shiro 统一化实现多端登录(PC端移动端)

Apache Shiro 是一个强大且易用的Java安全框架&#xff0c;提供了身份验证、授权、密码学和会话管理等功能。它被广泛用于保护各种类型的应用程序&#xff0c;包括Web应用、桌面应用、RESTful服务、移动端应用和大型企业级应用。 需求背景 在当今数字化浪潮的推动下&#xff…

NAT—地址转换(实战篇)

一、实验拓扑&#xff1a; 二、实验需求&#xff1a; 1.实现内网主机访问外网 2.实现外网客户端能够访问内网服务器 三、实验思路 1.配置NAT地址池实现内网地址转换成公网地址&#xff0c;实现内网主机能够访问外网。 2.配置NAT Sever实现公网地址映射内网服务器地址&…

用HTML和CSS生成炫光动画卡片

这个效果结合了渐变、旋转和悬浮效果的炫酷动画示例&#xff0c;使用HTML和CSS实现。 一、效果 二、实现 代码如下&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport&quo…

FPGA_YOLO(三)

上一篇讲的是完全映射&#xff0c;也就是block中的所包含的所有的卷积以及归一&#xff0c;池化卷积 举例总共6个等都在pl侧进行处理&#xff08;写一个top 顶层 里面conv 1 bn1 relu1 pool1 conv1*1 conv 2 bn2 relu2 pool2 conv1*1 ....总共6个 &#xff09;&#xff0c;…

旅游CMS选型:WordPress、Joomla与Drupal对比

内容概要 在旅游行业数字化转型进程中&#xff0c;内容管理系统&#xff08;CMS&#xff09;的选择直接影响网站运营效率与用户体验。WordPress、Joomla和Drupal作为全球主流的开源CMS平台&#xff0c;其功能特性与行业适配性存在显著差异。本文将从旅游企业核心需求出发&…

全面适配iOS 18.4!通付盾加固产品全面升级,护航App安全上架

引言&#xff1a; 苹果官方新规落地&#xff01; 自2025年4月24日起&#xff0c;所有提交至App Store Connect的应用必须使用Xcode 16或更高版本构建&#xff0c;否则将面临审核驳回风险&#xff01;Beta版iOS 18.4、iPadOS 18.4现已推出&#xff0c;通付盾iOS加固产品率先完成…

一台电脑最多能接几个硬盘?

在使用电脑时&#xff0c;硬盘空间不够是许多用户都会遇到的问题。无论是摄影师、剪辑师等需要大量存储空间的专业人士&#xff0c;还是游戏玩家、数据备份爱好者&#xff0c;都可能希望通过增加硬盘来扩展存储容量。然而&#xff0c;一台电脑究竟最多能接多少个硬盘&#xff1…

【玩转全栈】---- Django 基于 Websocket 实现群聊(解决channel连接不了)

学习视频&#xff1a; 14-11 群聊&#xff08;一&#xff09;_哔哩哔哩_bilibili 目录 Websocket 连接不了&#xff1f; 收发数据 断开连接 完整代码 聊天室的实现 聊天室一 聊天室二 settings 配置 consumer 配置 多聊天室 Websocket 连接不了&#xff1f; 基于这篇博客&…

如何快速解决django报错:cx_Oracle.DatabaseError: ORA-00942: table or view does not exist

我们在使用django连接oracle进行编程时&#xff0c;使用model进行表映射对接oracle数据时&#xff0c;默认表名组成结构为&#xff1a;应用名_类名&#xff08;如&#xff1a;OracleModel_test&#xff09;&#xff0c;故即使我们库中存在表test&#xff0c;运行查询时候&#…

本地安装git

下载git 通过官网 下载 &#xff1a;Git - Downloading Package 若此页面无法直达&#xff0c;请删掉download/win尝试 2.双击运行安装 选择安装目录&#xff1a; 选择配置&#xff0c;默认不动 git安装目录名 默认即可 Git 的默认编辑器&#xff0c;建议使用默认的 Vim 编辑器…

小程序内表格合并功能实现—行合并

功能介绍&#xff1a;支付宝小程序手写表格实现行内合并&#xff0c;依据动态数据自动计算每次需求合并的值&#xff0c;本次记录行内合并&#xff0c;如果列内合并&#xff0c;同理即可实现 前端技术&#xff1a;grid布局 display&#xff1a;grid 先看实现效果: axml&…

SSE协议介绍和python实现

概述&#xff1a; SSE&#xff08;Server-Sent Events&#xff09;协议是一种允许服务器向客户端实时推送更新的技术&#xff0c;基于HTTP协议&#xff0c;常用于实时数据推送特点&#xff1a; 单向通信&#xff1a;服务器向客户端推送数据&#xff0c;客户端无法发送数据。基…

甘肃旅游服务平台+论文源码视频演示

4 系统设计 4.1系统概要设计 甘肃旅游服务平台并没有使用C/S结构&#xff0c;而是基于网络浏览器的方式去访问服务器&#xff0c;进而获取需要的数据信息&#xff0c;这种依靠浏览器进行数据访问的模式就是现在用得比较广泛的适用于广域网并且没有网速限制要求的小程序结构&am…

WebRTC中音视频服务质量QoS之FEC+NACK调用流程

WebRTC中音视频服务质量QoS之FECNACK调用流程 WebRTC中音视频服务质量QoS之FECNACK调用流程 WebRTC中音视频服务质量QoS之FECNACK调用流程前言一、WebRTC中FEC基础原理1. FEC基础操作 异或操作XOR2、 FEC中 行向和纵向 计算3、 WebRTC中 媒体包分组和生成FEC的包数① kFecRateT…

神经网络知识点整理

目录 ​一、深度学习基础与流程 二、神经网络基础组件 三、卷积神经网络&#xff08;CNN&#xff09;​编辑 四、循环神经网络&#xff08;RNN&#xff09;与LSTM 五、优化技巧与调参 六、应用场景与前沿​编辑 七、总结与展望​编辑 一、深度学习基础与流程 机器学习流…

远程办公新体验:用触屏手机流畅操作电脑桌面

在数字化浪潮的推动下&#xff0c;远程办公已从“应急选项”转变为职场常态。无论是居家隔离、差旅途中&#xff0c;还是咖啡厅临时办公&#xff0c;高效连接公司电脑的需求从未如此迫切。然而&#xff0c;传统的远程控制软件常因操作复杂、画面卡顿或功能限制而影响效率。如今…

【面试八股】:常见的锁策略

常见的锁策略 synchronized &#xff08;标准库的锁不够你用了&#xff09;锁策略和 Java 不强相关&#xff0c;其他语言涉及到锁&#xff0c;也有这样的锁策略。 1. 悲观锁&#xff0c;乐观锁&#xff08;描述的加锁时遇到的场景&#xff09; 悲观锁&#xff1a;预测接下来…

【python】OpenCV—Hand Detection

文章目录 1、功能描述2、代码实现3、效果展示4、完整代码5、参考6、其它手部检测和手势识别的方案 更多有趣的代码示例&#xff0c;可参考【Programming】 1、功能描述 基于 opencv-python 和 mediapipe 进行手部检测 2、代码实现 导入必要的库函数 import cv2 import media…