进程间的通信-- 管道

一 进程通信原理

 我们知道进程间相互独立,具有独立性。那么我们要实现两个进程之间的通信就需要,让这两个进程看到同一个文件。然后一个进程对文件写入,一个进程对文件内容进行读取,这就是现实了进程间的通信。

二 进程通信的几种方式

管道:

  • 匿名管道
  • 命名管道

System V

  • System V 消息队列
  • System V 贡献内存
  • System V 信号量

POSIX IPC

  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

 三 管道的概念和特点

管道是进程间通信的一种方式,基本上也是最古老的通信方式。

  1. 管道通信是一种进程间通信的方式,它可以让一个进程的输出作为另一个进程的输入,实现数据的传输、资源的共享、事件的通知和进程的控制。
  2. 管道通信分为两种类型:匿名管道和命名管道。
  3. 匿名管道是只能在父子进程间使用的,它通过pipe()函数创建,并返回两个文件描述符,一个用于读,一个用于写。
  4. 命名管道是可以在任意进程间使用的,它通过mkfifo()或mknod()函数创建一个特殊的文件,然后通过open()函数打开,并返回一个文件描述符,用于读或写。
  5. 管道通信的特点是面向字节流、占用内存空间、只能单向传输、有固定的大小和缓冲区等。

三 管道的本质 

  管道通信的本质是利用内核提供的一块缓存区来实现不同进程间的数据传输、资源共享、事件通知和进程控制。

管道通信分为匿名管道和命名管道,它们都是一种特殊的文件,可以用普通的文件I/O函数进行操作。

1.匿名管道是通过pipe()函数创建并返回两个文件描述符,一个用于读,一个用于写。匿名管道只能在具有亲缘关系的进程间通信,通常是父子进程或兄弟进程。

2.命名管道是通过mkfifo()函数或mknod()函数创建一个特殊的文件,并通过open()函数打开并返回一个文件描述符,用于读或写。命名管道可以在任意进程间通信,只要知道它的路径名。

3.无论是匿名管道还是命名管道,它们都使用了环形缓冲区来存储数据。环形缓冲区是由16个内存页成的,每个内存页有一个pipe_buffer对象来管理。环形缓冲区有一个读指针和一个写指针来记录读写操作的位置。

4.当向管道写入数据时,从写指针指向的位置开始写入,并且将写指针向前移动。而从管道读取数据时,从读指针开始读入,并且将读指针向前移动。当对没有数据可读的管道进行读操作,或者对没有空闲空间的管道进行写操作时,会阻塞当前进程,除非设置了非阻塞标志(O_NONBLOCK)。

五 管道的使用 

  一般而言,我们使用管道有以下过程

  1. 创建管道
  2. 进行通信(传输数据/接受信息)
  3. 删除管道

六 管道的创建

6.1 匿名管道

pipe()函数是用来创建一个匿名管道的,它的原型是:

#include <unistd.h>
int pipe(int pipefd[2]); // 返回值:若成功返回0,失败返回-1

pipefd是一个输出型参数,pipe()函数会返回两个文件描述符,pipefd[0]用于读取管道中的数据,pipefd[1]用于向管道中写入数据。匿名管道只能在具有亲缘关系的进程间通信,通常是父子进程或兄弟进程。

6.2 命名管道

mkfifo()函数是用来创建一个命名管道的,它的原型是:

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode); // 返回值:成功返回0,出错返回-1

mkfifo()函数会在文件系统中创建一个特殊的文件,该文件用于提供FIFO功能,即命名管道。命名管道可以在无关的进程间通信,只要知道它的路径名。命名管道需要用open()函数打开,并返回一个文件描述符,用于读或写。

6.3 函数区别 

pipe()函数和mkfifo()函数的区别主要有以下几点:

  • pipe()函数创建的管道是匿名的,只能在有亲缘关系的进程间通信;mkfifo()函数创建的管道是有名字的,可以在任意进程间通信
  • pipe()函数创建的管道是在内存中的,不占用磁盘空间;mkfifo()函数创建的管道是在文件系统中的,占用磁盘空间。
  • pipe()函数创建并打开了管道,返回两个文件描述符;mkfifo()函数只创建了管道,需要用open()函数打开,并返回一个文件描述符。
  • pipe()函数创建的管道默认是阻塞的,即读写操作会等待对方进程;mkfifo()函数创建的管道可以指定非阻塞标志(O_NONBLOCK),即读写操作会立即返回成功或失败。

 七 管道的通信

   7.1 匿名管道的通信 

使用步骤:

  1.      我们经过上文得知,匿名管道只适用于父子进程,那么我们就必须使用fork来创建一个子进程,
  2.    因为管道是单向通信,为了减少错误,我们把两个进程中不需要的端口进行关闭
  3.  然后进行数据传输
  4. 最后关闭所有端口。

   测试示例:

要求:先创建管道, 进而创建子进程, 父子进程使用管道进行通信

子进程向管道当中写“give father  data:......... ”, 

父进程从管道当中读出内容, 并且打印到标准输出

代码示例:
 

#include<iostream>
#include<cstring>
#include<cstdio>
#include<unistd.h>
#include<cstdlib>
using namespace std;int main(){//1 .创建管道int pipefd[2] = {0};int n = pipe(pipefd);if(n<0){cout<<"perror file"<<endl;return 1;}//2 .fork进行通信pid_t id=fork();if(id==0){//子进程负责写入,关闭读进程close(pipefd[0]);string tmp="give father data : ......... ";char buffer[1024];snprintf(buffer,sizeof(buffer),"%s",tmp.c_str());write(pipefd[1],buffer,strlen(buffer));close(pipefd[1]);exit(0);}//父进程关闭写进程close(pipefd[1]);char buffer[1024];n = read(pipefd[0],buffer,sizeof(buffer) - 1);if(n > 0){buffer[n] = '\0';cout << "i am father,child give me message: " << buffer <<endl;}else if(n == 0){cout << "i am father,i read to the end" << endl;}//3.关闭所有接口close(pipefd[0]);  return 0;
}

结果为:

ps:


必须在创建子进程之前创建匿名管道才能实现父子进程通信吗?为什么?

必须的!

必须在创建子进程之前创建匿名管道的原因有以下几点:

  • 匿名管道是通过 pipe 系统调用来创建的,该系统调用会返回两个文件描述符,分别表示管道的读端和写端。
  • 子进程会继承父进程打开的文件描述符,包括管道的读端和写端。这样,父子进程就可以通过共享的文件描述符来访问同一个管道。
  • 如果在创建子进程之后再创建匿名管道,那么父子进程就无法共享文件描述符也就无法通过同一个管道进行通信

7.2 命名管道的通信  

使用步骤:

  1.   在任意进程中,使用mkfifo()函数或mknod()函数创建一个特殊的文件,该文件用于提供命名管道的功能,需要指定一个路径名和一个权限模式。
  2.   并且需要使用open()函数打开该文件,并返回一个文件描述符,用于读或写。可以指定非阻塞标志(O_NONBLOCK),以避免在没有对方进程时阻塞。
  3. 使用普通的文件I/O函数(如write()、read()、printf()、scanf()等)从而实现通信步骤。
  4.  记得使用close()函数关闭管道端。

    测试示例:

要求:rPipe进程先创建命名管道

rPipe进程等待通信写入

wPipe进程先向管道当中写“give rPipe data:......... ”, 

r进程从管道当中读出内容, 并且打印到标准输出

 rPipe代码:

#include<unistd.h>
#include<cstring>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<errno.h>using namespace std;int main(){//1.创建命名管道,并判断是否创建成功if (mkfifo("./myfifo", 0644) < 0) {perror("mkfifo error!\n");return 1;}//2.以只读的方式打开管道,进行通信,并判断是否打开成功int fd=open("./myfifo",O_RDONLY);if(fd<0){cout<<"open r fail"<<endl;}//不断进行读取while(true){int n=0;char buffer[1024];n = read(fd,buffer,sizeof(buffer) - 1);if(n > 0){buffer[n] = '\0';cout<<"i am r,w give me message: "<< buffer<<endl;fflush(stdout);}else if(n == 0){cout<<"i am r,i read to the end"<<endl;break;}else{cout<<n<<endl;cout<<errno<<":"<<strerror(errno)<<endl;break;}}//3.关闭接口close(fd);//Linux系统调用函数删除管道unlink("myfifo");return 0;
}

wPipe代码:

#include<iostream>
#include<unistd.h>
#include<string>
#include<cstring>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<cstdio>using namespace std;int main(){//2.打开管道进行通信int fd=open("./myfifo",O_WRONLY);if(fd<0){cout<<"open w fail"<<endl;}string tmp= "give rPipe data:......... ";char buffer[1024];snprintf(buffer,sizeof(buffer),"%s",tmp.c_str());write(fd,buffer,strlen(buffer));//3.关闭接口close(fd);return 0;
}

代码结果:

八 深入理解管道通信

8.1 再次理解

  • 我们知道文件描述符是一个数字,用来表示进程打开的文件。每个进程都有一个文件描述符表,用来存储文件描述符和对应的文件指针。

  • 管道是一种利用内核缓冲区实现进程间通信的方法,它可以让一个进程的输出作为另一个进程的输入,实现数据的单向传输。

  • 管道由pipe()系统调用创建,返回两个文件描述符,分别代表管道的读端和写端。通常,一个进程创建管道后,再fork出一个子进程,然后父子进程分别关闭不需要的管道端,建立通信连接

匿名管道操作流程: 

命名管道操作流程: 

实例中我们也可以看出,管道的本质其实就是个文件。所以,看待管道,就如同看待文件一样!管道的使用和文件一致,迎合了“Linux一切皆文件思想”!

8.2 管道的特点总结

  1. 管道通信是半双工的,有固定的读端和写端。
  2. 管道通信是先进先出的,数据被进程从管道读出后,在管道中该数据就不存在了。
  3. 管道通信是基于文件操作的,需要使用文件描述符来管理管道的读写。
  4. 管道通信是阻塞式的,当进程去读取空管道或者写入满管道时,进程会阻塞。
  5. 管道通信分为匿名管道和命名管道,匿名管道只能用于具有亲缘关系的进程间通信,命名管道可以用于任何进程间通信。
  6. 一般而言,进程退出,管道释放,所以管道的生命周期随进程
  7. 一般而言,内核会对管道操作进行同步与互斥
  8. 管道通信是面向字节流的,数据在管道中先进先出(FIFO)。当一个进程向管道写入数据时,数据会被存放在内核缓冲区中,直到另一个进程从管道读取数据或者缓冲区满为止。

8.3 管道的优缺点 

管道通信的优点有以下几点:

  1. 管道通信是简单易用的,只需要使用系统调用 pipe 或 mkfifo 就可以创建一个管道文件,然后使用文件操作函数来读写数据。
  2. 管道通信是安全的,匿名管道只能用于具有亲缘关系的进程间通信,命名管道可以通过文件权限来控制访问。
  3. 管道通信是面向字节流的,不需要事先约定数据的格式,也不需要考虑字节序的问题。


管道通信的缺点有以下几点:

  1. 管道通信是单向的,如果要实现双向通信,需要创建两个管道。
  2. 管道通信是阻塞式的,如果读端没有数据可读或者写端没有空间可写,进程会被阻塞。
  3. 管道通信是缓冲区有限的,如果写入数据过多而读出数据过少,会导致缓冲区满而无法继续写入。
  4. 管道通信是不可靠的,如果读端或者写端被关闭,另一端可能会收到错误的信号或者返回值。

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

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

相关文章

【C++私房菜】面向对象中的多态

文章目录 一、多态二、对象的静态类型和动态类型三、虚函数和纯虚函数1、虚函数2、虚析构函数3、抽象基类和纯虚函数4、多态的原理 四、重载、覆盖(重写)、隐藏(重定义)的对比 一、多态 OOP的核心思想是多态性(polymorphism)。多态性这个词源自希腊语&#xff0c;其含义是“多…

【黑马程序员】1、TypeScript介绍_黑马程序员前端TypeScript教程,TypeScript零基础入门到实战全套教程

课程地址&#xff1a;【黑马程序员前端TypeScript教程&#xff0c;TypeScript零基础入门到实战全套教程】 https://www.bilibili.com/video/BV14Z4y1u7pi/?share_sourcecopy_web&vd_sourceb1cb921b73fe3808550eaf2224d1c155 目录 1、TypeScript介绍 1.1 TypeScript是什…

信号通信与消息队列实现的通信:2024/2/23

作业1&#xff1a;将信号和消息队列的课堂代码敲一遍 1.1 信号 1.1.1 信号默认、捕获、忽略处理(普通信号) 代码&#xff1a; #include <myhead.h> void handler(int signo) {if(signoSIGINT){printf("用户键入 ctrlc\n");} } int main(int argc, const ch…

Windows Server 2019 IIS HTTPS证书部署流程详解

一、下载SSL证书 1、下载IIS 类型的证书 以阿里云证书为例&#xff1a; 2、解压已下载的SSL证书压缩包 二、导入SSL证书 1、在服务器上使用WinR组合键&#xff0c;打开运行对话框&#xff0c;输入mmc&#xff0c;单击确定 打开控制台操作界面&#xff0c;如下&#xff1a; …

可视化 RAG 数据 — EDA for Retrieval-Augmented Generation

目录 一、说明 二、准备好 三、准备文件 四、拆分和创建数据集的嵌入 五、构建 LangChain 六、问一个问题 七、可视化 八、下一步是什么&#xff1f; 九、引用 一、说明 像 GPT-4 这样的大型语言模型 &#xff08;LLM&#xff09; 在文本理解和生成方面表现出令人印象深刻的能力…

介绍 CI / CD

目录 一、介绍 CI / CD 1、为什么要 CI / CD 方法简介 1、持续集成 2、持续交付 3、持续部署 2、GitLab CI / CD简介 3、GitLab CI / CD 的工作原理 4、基本CI / CD工作流程 5、首次设置 GitLab CI / CD 6、GitLab CI / CD功能集 一、介绍 CI / CD 在本文档中&#x…

Python中format()方法的基本使用,第一种用法 <模板字符串>.format(<参数列表>)。

第一种用法&#xff1a; <模板字符串>.format(<参数列表>) 解析&#xff1a; 其中&#xff1a; <模板字符串>是包含占位符或者叫槽&#xff08;用花括号 {} 表示&#xff09;的字符串&#xff0c;用来指定最终格式化后的字符串的样式和结构。<参数列表…

STM32 系统滴答时钟启动过程 SysTick_Config

STM32 系统滴答时钟启动过程 SysTick_Config 1. 系统滴答时钟1.1 简介1.2 配置1.3 启动和更新 1. 系统滴答时钟 1.1 简介 SysTick&#xff1a;系统滴答时钟&#xff0c;属于Cortex-M4内核中的一个外设&#xff0c;24bit向下递减计数。 Systick定时器常用来做延时&#xff0c;…

二次元风格个人主页HTML源码

源码介绍 直接上传服务器压缩包解压就完事了&#xff0c;修改index.html内代码即可&#xff0c;注释写的很全&#xff0c;替换图片在文件夹img&#xff0c;只有前端&#xff0c;没有后台&#xff0c;大佬如果需要&#xff0c;可以自行添加后台。本源码非常适合个人工作室主页。…

CMake管理CUDA并使用cuSOLVER等

一、出现问题 我在使用官方案例的时候&#xff0c;使用VS2022CMake管理编译的时候出现如下的错误&#xff1a; 官方CMakeLists.txt&#xff1a; cmake_minimum_required(VERSION 3.9)set(ROUTINE bicgstab)project("${ROUTINE}_example"DESCRIPTION "GPU-Acce…

在Linux服务器上部署一个单机项目

目录 一、jdk安装 二、tomcat安装 三、MySQL安装 四、部署项目 一、jdk安装 1. 上传jdk安装包 jdk-8u151-linux-x64.tar.gz 进入opt目录&#xff0c;将安装包拖进去 2. 解压安装包 这里需要解压到usr/local目录下&#xff0c;在这里我新建一个文件夹保存解压后的文件 [r…

使用 ES|QL 优化可观察性:简化 Kubernetes 和 OTel 的 SRE 操作和问题解决

作者&#xff1a;Bahubali Shetti 作为一名运营工程师&#xff08;SRE、IT 运营、DevOps&#xff09;&#xff0c;管理技术和数据蔓延是一项持续的挑战。 简单地管理大量高维和高基数数据是令人难以承受的。 作为单一平台&#xff0c;Elastic 帮助 SRE 将无限的遥测数据&#…

责任链模式与spring容器的搭配应用

背景 有个需求&#xff0c;原先只涉及到一种A情况设备的筛选&#xff0c;每次筛选会经过多个流程&#xff0c;比如先a功能&#xff0c;a功能通过再筛选b功能&#xff0c;然后再筛选c功能&#xff0c;以此类推。现在新增了另外一种B情况的筛选&#xff0c;B情况同样需要A情况的筛…

算法学习(十一)拓扑排序

拓扑排序 1. 概念 对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序&#xff0c;是将G中所有顶点排成一个线性序列&#xff0c;使得图中任意一对顶点u和v&#xff0c;若边<u,v>∈E(G)&#xff0c;则u在线性序列中出现在v之前。通常&#xff0c;这样的线性…

【Java程序员面试专栏 数据结构】三 高频面试算法题:栈和队列

一轮的算法训练完成后,对相关的题目有了一个初步理解了,接下来进行专题训练,以下这些题目就是汇总的高频题目,因为栈和队列这两哥们结构特性比较向对应,所以放到一篇Blog中集中练习 题目题干直接给出对应博客链接,这里只给出简单思路、代码实现、复杂度分析 题目关键字…

ChatGPT助你成功求职:智能引导下的职场新起点【文章底部添加可得内推码汇总表】

在当今竞争激烈的就业市场中&#xff0c;如何有效地进行求职已成为许多人面临的挑战。然而&#xff0c;随着人工智能的不断发展&#xff0c;ChatGPT作为一种强大的语言模型&#xff0c;不仅可以为我们提供信息&#xff0c;还可以成为求职过程中的得力助手。在这篇文章中&#x…

每日一学—由面试题“Redis 是否为单线程”引发的思考

文章目录 &#x1f4cb; 前言&#x1f330; 举个例子&#x1f3af; 什么是 Redis&#xff08;知识点补充&#xff09;&#x1f3af; Redis 中的多线程&#x1f3af; I/O 多线程&#x1f3af; Redis 中的多进程&#x1f4dd; 结论&#x1f3af;书籍推荐&#x1f525;参与方式 &a…

K线实战分析系列之五:刺透形态——多方反攻信号

K线实战分析系列之五&#xff1a;刺透形态——多方反攻信号 一、刺透形态二、类似刺透形态三、刺透形态的总结 一、刺透形态 阴线在前&#xff0c;阳线在后显示市场曾经跌到了低位&#xff0c;但是在盘中又将价格收回&#xff0c;并且多方收复了前一天大部分的失地 二、类似刺…

[SUCTF 2019]EasySQL1 题目分析与详解

一、题目介绍 1、题目来源&#xff1a; BUUCTF网站&#xff0c;网址&#xff1a;https://buuoj.cn/challenges 2、题目描述&#xff1a; 通过以上信息&#xff0c;拿到flag。 二、解题思路 首先打开靶机&#xff0c;尝试输入1查看回显&#xff0c;回显如图所示&#xff1a;…

利用psutil库检查脚本是否在运行

摘要 如果要判断某一脚本是否在运行&#xff0c;可以通过psutil库获取所有进程的cmdline&#xff0c;并判断指定的文件名是否在cmdline中。 目录 1.psutil库简介 2.检查代码及说明 2.1检查思路 2.2异常捕获 2.3执行方法 1.psutil库简介 psutil 是一个跨平台&#xff08;…