【Linux】进程间通信——命名管道

【Linux】进程间通信——命名管道

🔥 个人主页:大耳朵土土垚🔥 所属专栏:Linux系统编程
这里将会不定期更新有关Linux的内容,欢迎大家点赞,收藏,评论🥳🥳🎉🎉🎉

文章目录

1. 命名管道介绍

匿名管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。如果我们想在不相关的进程之间交换数据,可以使用命名管道来做这项工作。
在Linux系统中,命名管道(也称为FIFOFirst In First Out)是一种特殊的文件类型,它允许进程间进行通信。与匿名管道不同,命名管道存在于文件系统中,并且可以被任何有适当权限的进程访问。命名管道提供了一种方法,使得不相关的进程能够通过预先定义好的路径来交换数据。

2. 简单使用命名管道

创建命名管道

可以通过mkfifo命令或mknod命令来创建命名管道。使用mkfifo更为常见和简单:

mkfifo /path/to/your/fifo 

这里,/path/to/your/fifo是你要创建的命名管道的路径。创建后,这个路径将作为一个特殊类型的文件存在,其类型为p(pipe);如下图所示,在当前路径下创建名为mypipe的管道文件。

在这里插入图片描述

写入数据到命名管道

一个进程可以打开命名管道并写入数据。例如,使用echo命令向命名管道写入文本:

echo"Hello, FIFO!"> /path/to/your/fifo 

需要注意的是,如果此时没有其他进程正在读取该命名管道,则上述命令将会阻塞,直到有读者出现。

从命名管道读取数据

另一个进程可以从命名管道中读取数据。这通常也是通过标准输入输出重定向完成的:

cat< /path/to/your/fifo 

同样地,如果此时没有进程向命名管道写入数据,那么执行读操作的命令也会处于等待状态。

示例

假设我们想要创建一个简单的场景,其中一个shell会话发送消息给另一个shell会话。首先,在第一个终端窗口创建命名管道:

mkfifo myfifo 

然后,在第二个终端窗口启动读取过程:

cat< myfifo 

回到第一个终端窗口,向命名管道写入一些内容:

echo"This is a test message"> myfifo 

这时,第二个终端应该显示了刚刚写入的消息:“This is a test message”。

删除命名管道

一旦不再需要命名管道,可以直接使用rm命令删除它:

rm /path/to/your/fifo 

记住,命名管道必须在没有任何进程打开的情况下才能被成功删除。

命名管道非常适合用于那些需要跨多个会话或用户之间的简单IPC(进程间通信)的情况。对于更复杂的需求,可能需要考虑其他的IPC机制如消息队列、共享内存等。

3. 命名管道原理

与匿名管道类似,命名管道也是操作系统提供的可以共享的资源,不同的是命名管道是一个特殊的文件,记录在磁盘上也有自己的文件描述符; 但是它与普通文件又有不同,命名管道文件的内容不需要刷新到磁盘中,因为它仅需要进行通信即可,不需要耗费时间空间去将内容保存在磁盘中,所以它的文件大小一直是0。
此外由于命名管道是保存在磁盘上的文件,可以通过路径+文件名标识唯一性,使得它可以不同于匿名管道,在没有亲缘关系的进程之间也能进行通信。

在这里插入图片描述

4. 命名管道代码使用

  • 命名管道也可以从程序⾥创建,相关函数有:
intmkfifo(constchar*filename,mode_t mode);
与创建文件类似,其中,filename是需要创建的命名管道的路径已以及文件名,mode 是管道的权限,一般设置为0600,表示只有拥有者可读可写。如果创建成功,返回0;否则返回-1。
  • 删除命名管道相关函数有:
intunlink(constchar*filename);
删除一个命名管道。filename参数指定管道的路径名。如果删除成功,返回0;否则返回-1。
  • 命名管道创建成功就可以当作文件使用:

【打开文件】:

intopen(constchar*pathname,int flags, mode_t mode);
  • pathname: 文件路径。
  • flags: 打开标志(如 O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_TRUNC, O_APPEND 等)。
  • mode: 文件权限(当文件被创建时)。
  • 返回值: 成功时返回文件描述符,失败时返回 -1 并设置 errno。

【关闭文件】

intclose(int fd);
  • fd: 文件描述符。
  • 返回值: 成功时返回 0,失败时返回 -1 并设置 errno。

【文件读写】

 ssize_t read(int fd,void*buf, size_t count);
  • 从文件描述符读取数据。
  • fd: 文件描述符。
  • buf: 缓冲区,用于存储读取的数据。
  • count: 要读取的字节数。
  • 返回值: 成功时返回读取的字节数,到达文件末尾时返回 0,失败时返回 -1 并设置 errno。
 ssize_t write(int fd,constvoid*buf, size_t count);
  • 向文件描述符写入数据。
  • fd: 文件描述符。
  • buf: 缓冲区,包含要写入的数据。
  • count: 要写入的字节数。
  • 返回值: 成功时返回写入的字节数,失败时返回 -1 并设置 errno。

基于上述函数我们就可以通过创建命名管道来实现不同进程间通信:

  • 首先我们需要创建命名管道:
//1.创建命名管道classInitFifo{public:InitFifo(){umask(0);if(mkfifo(gpipeFile.c_str(),gmode)<0)//gpipeFile和gmode是定义的全局变量const std::string gpipeFile = "./fifo";const mode_t gmode = 0600; std::cout<<"创建命名管道失败..."<<std::endl;else std::cout<<"创建命名管道成功..."<<std::endl;}~InitFifo(){if(unlink(gpipeFile.c_str())<0) std::cout<<"删除命名管道失败..."<<std::endl;else std::cout<<"删除命名管道成功..."<<std::endl;}};
  • 创建命名管道完成后,需要往命名管道写入内容:
先将需要使用的函数封装在一个Client类中
//2.使用命名管道发送信息classClient{public:Client():_fd(-1){}//打开命名管道文件boolOpenFifoForWrite(){ _fd =::open(gpipeFile.c_str(), O_WRONLY);//以只写方式打开if(_fd <0){ std::cout<<"打开命名管道文件失败..."<<std::endl;returnfalse;}returntrue;}//写入命名管道文件内容boolWriteFifo(const std::string& s){if(_fd >0){ ssize_t n =::write(_fd,s.c_str(),s.size());if(n <0){ std::cout<<"写入命名管道失败..."<<std::endl;returnfalse;}returntrue;} std::cout<<"命名管道不存在..."<<std::endl;returnfalse;}//关闭文件boolCloseFifo(){if(_fd >0){if(::close(_fd)<0){ std::cout<<"关闭命名管道文件失败..."<<std::endl;returnfalse;}returntrue;} std::cout<<"文件不存在..."<<std::endl;returnfalse;}private:int _fd;};
使用Client类封装好相关函数后,就可以定义对象来使用
intmain(){ Client c;//定义Client对象 c.OpenFifoForWrite();//打开文件//写入内容 std:: string message;while(true){ std::cout<<"Please Enter# "; std::getline(std::cin,message);//从标准输入中读取内容发送给命名管道 c.WriteFifo(message);} c.CloseFifo();//关闭文件return0;}
  • 从命名管道读取内容:
先将需要使用的函数封装在一个Server类中
//3.使用命名管道接收信息classServer{public:Server():_fd(-1){}//打开命名管道文件boolOpenFifoForRead(){ _fd =::open(gpipeFile.c_str(), O_RDONLY);//以只读方式打开if(_fd <0){ std::cout<<"打开命名管道文件失败..."<<std::endl;returnfalse;}returntrue;}//读取命名管道文件内容 std::string ReadFifo(){if(_fd >0){char buffer[gsize]; size_t n =::read(_fd,&buffer,gsize-1);if(n >0){ buffer[n]=0;return buffer;}else{ std::cout<<"没有内容读取..."<<std::endl;}}return"";}//关闭文件boolCloseFifo(){if(_fd >0){if(::close(_fd)<0){ std::cout<<"关闭命名管道文件失败..."<<std::endl;returnfalse;}returntrue;} std::cout<<"文件不存在..."<<std::endl;returnfalse;}private:int _fd;};
封装完成后,就可以定义Server对象使用
intmain(){ InitFifo i;//初始化命名管道 Server s;//定义Server对象 s.OpenFifoForRead();//打开文件//读取内容 std:: string message;while(true){ message = s.ReadFifo();if(message =="")//读到空,说明没有内容或者写端关闭,直接跳出循环break; std::cout<<message<<std::endl;} s.CloseFifo();//关闭文件return0;}

运行结果如下:

  • 先打开Server可执行程序,再打开Client可执行程序
先打开Server可执行程序,再打开Client可执行程序,就能实现进程间通信:
在这里插入图片描述
  • 先打开Client可执行程序,再打开Server可执行程序
如果先打开Client可执行程序,因为命名管道是在Server可执行程序中初始化的,所以无法实现通信,结果如下:
在这里插入图片描述
  • 先关闭Client可执行程序:
Client也就是写端关闭后,读端就会读到0,这时候就要主动设置退出循环,退出程序,否则Server程序会陷入死循环。

因为在Server程序中设置了读到空就跳出循环,所以先关闭Client程序后,Server程序也会成功退出:

while(true){ message = s.ReadFifo();if(message =="")break;//跳出死循环 std::cout<<message<<std::endl;}

结果如下:

在这里插入图片描述
  • 先关闭Server可执行程序:
读端也就是Server端关闭后,写端的进程会收到信号进而终止程序。
  • 如果先打开Server可执行程序,Client可执行程序还未打开:
Server进程会阻塞在打开命名管道文件那里直到Client可执行程序打开

命名管道的打开规则

• 如果当前打开操作是为读⽽打开FIFO时
◦ O_NONBLOCK disable(阻塞模式):阻塞直到有相应进程为写⽽打开该FIFO
◦ O_NONBLOCK enable(非阻塞模式):⽴刻返回成功
• 如果当前打开操作是为写⽽打开FIFO时
◦ O_NONBLOCK disable(阻塞模式):阻塞直到有相应进程为读⽽打开该FIFO
◦ O_NONBLOCK enable(非阻塞模式):⽴刻返回失败,错误码为ENXIO

上述命名管道的使用由先打开Server可执行程序,Client可执行程序还未打开,Server进程会阻塞在打开命名管道文件说明当前命名管道是使用阻塞模式打开的

5. 结语

匿名管道由pipe函数创建并打开。而命名管道由mkfifo函数创建,打开⽤openFIFO(命名管道)与pipe(匿名管道)之间唯⼀的区别在它们创建与打开的⽅式不同,⼀但这些⼯作完成之后,它们具有相同的语义。以上就是命名管道有关的所有内容啦~ 完结撒花~ 🥳🎉🎉

Read more

优选算法——二分查找

👇作者其它专栏 《数据结构与算法》《算法》《C++起始之路》 二分查找相关题解 1.二分查找 算法思路: a.定义left,right指针,分别指向数组的左右区间。 b.找到待查找区间的中间点mid,找到后分三种情况讨论:         i.arr[mid]==target说明正好找到,返回mid的值;         ii.arr[mid]>target说明[mid,right]这段区间都是大于target的,因此舍去右边区间,在左边[left,mid-1]的区间继续查找,即让right=mid-1,然后重复b过程;         iii.arr[mid]<target说明[left,mid]这段区间的值都是小于target的,因此舍去左边区间,在右边区间[mid+1,right]

By Ne0inhk
【2025 最新】 Python 安装教程 以及 Pycharm 安装教程(超详细图文指南,附常见问题解决)

【2025 最新】 Python 安装教程 以及 Pycharm 安装教程(超详细图文指南,附常见问题解决)

前言         Python 作为目前最热门的编程语言之一,在数据分析、人工智能、Web 开发等领域应用广泛。而 PyCharm 作为 JetBrains 推出的 Python 集成开发环境(IDE),以其强大的功能和友好的界面成为开发者的首选工具。         本文针对 2025 年最新版 Python(3.13.x)和 PyCharm(202x.x.x),提供Windows 10或11和macOS Sonoma双系统安装教程,从官网下载到环境配置一步到位,同时整理了安装过程中最常见的 10 类问题及解决方案,确保新手也能顺利完成环境搭建。 一、Python 安装教程(2025 最新版) 1. 下载 Python 安装包 步骤 1:访问 Python 官网

By Ne0inhk
【Python✨】Conda 虚拟环境 & 安装包路径修改:告别 C 盘占用,3 步轻松配置

【Python✨】Conda 虚拟环境 & 安装包路径修改:告别 C 盘占用,3 步轻松配置

在使用 Anaconda 或 Miniconda 时,默认的虚拟环境路径(envs)和安装包缓存路径(pkgs)常位于系统盘(如 C 盘),长期使用易占用过多空间。本文将详细介绍如何修改这两个路径,解决空间不足、权限冲突等问题,适用于 Windows、Linux、macOS 系统。 一、核心概念说明 在修改前,先明确两个关键路径的作用: 路径类型默认位置(以 Windows 为例)作用虚拟环境路径(envs_dirs)C:\Users\用户名\.conda\envs 或 Anaconda安装目录\envs存储通过 conda create 创建的虚拟环境(如 km3.8、glm3)安装包缓存路径(pkgs_

By Ne0inhk

Python实时快递物流跟踪爬虫:异步并发与智能解析技术全解析

引言:物流信息抓取的现代挑战 在电商蓬勃发展的今天,快递物流跟踪已成为日常需求。无论是企业供应链管理还是个人包裹查询,实时获取物流信息都至关重要。传统的人工查询方式效率低下,而通过Python爬虫自动化获取物流信息,不仅能提高效率,还能实现数据分析和监控预警。本文将深入探讨如何构建一个高效、稳定且智能的快递物流跟踪爬虫系统。 技术架构选型:为什么选择异步并发? 现代物流跟踪爬虫面临三大挑战: 1. 高并发需求:需要同时查询多个快递单号 2. 反爬虫对抗:主流物流网站都有严格的反爬措施 3. 数据解析复杂度:不同快递公司返回数据格式各异 针对这些挑战,我们选择以下技术栈: * 异步框架:aiohttp + asyncio 实现高并发 * 智能解析:Playwright 模拟真实浏览器行为 * 数据管理:SQLAlchemy + PostgreSQL 存储结构化数据 * 代理管理:智能代理池轮换机制 完整爬虫系统代码实现 1. 项目结构设计 text express_tracking/ ├── crawler/ │

By Ne0inhk