Linux_管道通信

目录

一、匿名管道

1、介绍进程间通信

2、理解管道

3、管道通信

4、用户角度看匿名管道

5、内核角度看匿名管道 

6、代码实现匿名管道 

6.1 创建子进程

6.2 实现通信

7、匿名管道阻塞情况 

8、匿名管道的读写原子性

二、命名管道

1、命名管道 

1.1 命名管道通信

2、代码实现命名管道

结语 


前言:

        在Linux下有一种概念叫做管道,管道的作用是实现进程间通信功能,其本质是一个文件,该文件也被当成进程通信的缓冲区,即一个进程往缓冲区内写数据,另一个进程从缓冲区内读数据,这一过程称之为进程间通信。管道分为两种:匿名管道、命名管道,匿名管道只限于含亲缘关系的进程间通信,而命名管道可以让两个无亲缘关系的进程进行通信。

一、匿名管道

1、介绍进程间通信

        进程间通信在Linux下是个很重要的概念,他允许两个以上的进程进行相互传递数据,在如今的实际生活中,早已离不开进程间通信,因为日常生活中的网上冲浪就是一种进程通信的行为,只不过通信的进程在不同的主机上,但是不论是不同主机的进程通信还是同一主机的进程通信的逻辑都相差不差,即进程间要看到相同的资源。

2、理解管道

        管道就是一种在同一主机下的进程通信,他的作用就如同现实中的“管道”一样,只允许一边写数据一边读数据,因此单个管道是一种半双工的通信方式。在Linux下可以使用管道将两个以上的指令进行结合输出,操作指令如下:

        管道的作用就是将前一个指令的输出结果写进管道,让后一个指令从管道中读取内容并执行。


        管道示意图:

3、管道通信

         管道是进程通信中一种最古老的方式,因为指令就是进程,所以在上述例子中,ll进程和wc进程利用管道进行进程通信,只不过通信的过程让系统封装好了,因此在上层无法清晰的看到整个通信过程,但是能够明白管道的通信逻辑是让两个进程看到同一份资源,所以使用管道进行进程通信的具体结构图如下:

        上图中父进程创建了一个子进程,并且子进程通过拷贝父进程的PCB,也会让子进程指向管道文件,此时就满足了两个进程看到同一份资源的条件(ps:0、1、2是默认打开的3个文件描述符,他们对应上层的三个文件流stdin、stdout、stderr)。

        将带有亲缘进程间通信的管道称为匿名管道,值得注意的是:管道的原理就是在内存上开一个临时文件,而不是在磁盘上新建文件,所以当管道文件由系统自动识别并回收。

4、用户角度看匿名管道

        匿名管道只适用于亲缘进程间通信,在用户层面上只关心进程文件描述符和管道文件的关系,具体使用匿名管道通信的步骤如下:

        1、父进程在fork子进程前要先创建管道文件,并且以读、写方式打开管道文件,此时会返回两个文件描述符3、4。


         2、父进程fork出子进程,因此子进程的3、4文件描述符也会指向同一个管道文件。


        3、父进程关闭文件描述符3,子进程关闭文件描述符4(或者相反),这样做的原因是当下只有一个缓冲区存放通信的数据,若一个进程既能写又能读,则无法保证该进程读到的数据一定是对方发来的数据,因为有可能读到自己发送的数据。

         此时就可以进行父进程向子进程发送数据的通信方式了。

5、内核角度看匿名管道 

         从内核角度看匿名管道通信,父进程创建管道文件时,会以读、写两种方式打开管道文件,因此在内核中一个管道文件是被打开了两次,可以理解为创建了两个struct file的结构体来管理一个管道文件

        具体示意图如下:

        注意:上图父进程关闭文件描述符3,子进程关闭文件描述符4,所以当前管道文件的struct file的引用计数是2,只有关闭了父进程的文件描述符4和子进程的文件描述符3,系统才会回收管道文件。

6、代码实现匿名管道 

        系统已经为用户提供了实现匿名管道的接口,接口介绍如下:

#include <unistd.h>//pipe所需要的头文件//传一个数组给到pipe,pipe调用成功时返回0,失败返回-1
//调用成功pipefd数组的第一个元素是读的下标,第二个元素是写的下标
int pipe(int pipefd[2]);

        代码验证pipe接口:

#include<iostream>
#include<unistd.h>
#include <errno.h> using namespace std;int main()
{int pipefd[2]={0};int n = pipe(pipefd);if(n == -1){perror("pipe");return -1;}cout<<pipefd[0]<<" "<<pipefd[1]<<endl;return 0;
}

        运行结果:

        从结果可以发现,pipe确实改变了数组pipefd元素的值,让该进程的3号文件描述符以读的方式打开管道文件,该进程的4号文件描述符以写的方式打开文件,说明底层已经完成了使用匿名管道通信的第一步,即下图:

6.1 创建子进程

        有了上述的代码基础,就可以创建子进程并建立进程间通信的整体架构了,代码如下:

#include <iostream>
#include <unistd.h>
#include <errno.h>using namespace std;int main()
{int pipefd[2] = {0};int n = pipe(pipefd);if (n == -1){perror("pipe");return -1;}// 下面实现的是子进程写,父进程读pid_t id = fork();if (id < 0)return -2;// chlidif (id == 0){close(pipefd[0]); // 关闭读,子进程只写// 子进程的通信过程cout << "子进程开始传输数据" << endl;sleep(3);close(pipefd[1]); // 通信完毕后关闭写端exit(0);}// fatherclose(pipefd[1]); // 关闭写,父进程只读// 父进程的通信过程cout << "父进程开始读取数据" << endl;sleep(3);close(pipefd[0]); // 通信完毕后关闭读端return 0;
}

         运行结果:

        上述代码完成的是匿名管道通信的第二步、第三步:

6.2 实现通信

        创建子进程并建立起进程通信的框架后,就可以进行父子进程通信了,所以需要在上述代码的通信过程中实现具体的通信,代码如下: 

#include <iostream>
#include <unistd.h>
#include <errno.h>
#include <cstring>using namespace std;
#define NUM 1024// child
void Writer(int wfd)
{string s = "你好父进程,我是子进程";pid_t self = getpid();int number = 0, time = 5;char buffer[NUM];while (time--){sleep(1);// 构建发送字符串buffer[0] = 0;snprintf(buffer, sizeof(buffer), "%s-%d-%d", s.c_str(), self, number++);cout << buffer << endl;// 发送/写入给父进程, system callwrite(wfd, buffer, strlen(buffer)); }cout<<"子进程数据发送完成"<<endl;
}// father
void Reader(int rfd)
{char buffer[NUM];while (true){buffer[0] = 0;ssize_t n = read(rfd, buffer, sizeof(buffer) - 1); // 预留\0的位置if (n > 0){buffer[n] = 0; // 手动添加\0cout << "父进程收到子进程的消息[" << getpid() << "]# " << buffer << endl;}else if (n == 0){printf("father read file done!\n");break;}elsebreak;cout << endl;}
}int main()
{int pipefd[2] = {0};int n = pipe(pipefd);if (n == -1){perror("pipe");return -1;}// 下面实现的是子进程写,父进程读pid_t id = fork();if (id < 0)return -2;// chlidif (id == 0){close(pipefd[0]); // 关闭读,子进程只写// 子进程的通信过程cout << "子进程开始传输数据" << endl;Writer(pipefd[1]);sleep(1);cout << "子进程关闭写端" << endl;close(pipefd[1]); // 通信完毕后关闭写端exit(0);}// fatherclose(pipefd[1]); // 关闭写,父进程只读// 父进程的通信过程cout << "父进程开始读取数据" << endl;Reader(pipefd[0]);close(pipefd[0]); // 通信完毕后关闭读端return 0;
}

         运行结果:

7、匿名管道阻塞情况 

        1、读写端正常,当管道为空,读端就会阻塞,比如在上述代码中让子进程停留在write函数之前,观察父进程read函数,测试结果如下: 

         发现父进程阻塞在read函数处,原因就是管道为空但读写端都未关闭,read函数读取不到任何数据会阻塞在此处。


        2、读写端正常,当管道写满时,写端就会阻塞(因为管道也是有大小的),比如在上述代码中让父进程停留在read函数之前,这样就会让子进程一直往管道里写数据,最终导致管道被写满,导致write函数阻塞住,测试结果如下:

        从结果看,子进程一直在写数据,但是父进程没有读取数据,最终导致子进程阻塞在write处。


        3、读端正常,把写端的文件描述符关闭,在读端把管道内的数据读完后,下一次读取会导致read函数直接返回0,不会导致read阻塞,将上述代码的写端先关闭,读端不关闭,观察写端关闭后read函数的返回值,测试结果如下:

        从结果看到,子进程写端关闭后,父进程的read函数就会返回0。


        4、 写端正常,把读端的文件描述符关闭,此时操作系统会直接把整个写端进程以信号的方式终止,终止信号为13号,可以通过waitpid函数来获取子进程的终止信号,测试结果如下:

8、匿名管道的读写原子性

        原子性即保证数据的完整性,比如写端写了hello world,则读端必须要等写端把整个hello world写完才能读,不能在写端写了一个hello时就去读。对于写入原子性,Linux规定当单次写入的数据小于等于PIPE_BUF(即管道的容量)则保证这一次的写入是原子性的,大于则不能保证原子性。对于读取原子性,可以理解为用户控制得好则读取就是原子性的,控制不好读取的大小则读取就不是原子性的。

二、命名管道

1、命名管道 

        上述的匿名管道的局限性在于只能作用于亲缘进程间通信,而现实生活中大多数进程间通信都不是亲缘关系,因此就引出另一个概念:命名管道,他允许两个以上的非亲缘进程进行通信,他是一种特殊的文件形式。因为管道文件是内存级别的,所以无论往里面写多少数据,管道文件的大小始终是0,原因就是管道文件的数据没有被写到磁盘中,仅仅在内存中就被消耗了。

        在Linux指令中,通常用‘|’来表示匿名管道,同样命名管道也可以直接用指令创建出来,测试如下:


        命名管道实现通信的逻辑就是让两个进程通过该“namepipe”命名管道文件进行通信,即发送端把数据写进该文件里,读取端从该文件里读数据,并且这两个进程可以是无亲缘关系的,因为他们只需要找到namepipe文件的路径即可完全通信。

1.1 命名管道通信

        这里用两个窗口模拟两个进程,一个窗口给命名管道发送数据,另一个窗口从管道中读取数据,以此完成通信,测试结果如下:

         从结果发现,依靠命名管道完成了两个进程的通信。

2、代码实现命名管道

        和实现匿名管道逻辑一样,系统为上层提供了一个接口mkfifo来创建命名管道,指令介绍如下:

#include <sys/types.h>
#include <sys/stat.h>int mkfifo(const char *pathname, mode_t mode);
//pathname表示创建命名管道的路径
//mode表示命名管道的权限
//mkfifo成功返回0,失败返回-1

        有了此接口,就可以实现简单的无亲缘进程间通信了,我们可以创建两个独立的主进程(发送方和接收方),然后通过mkfifo生成的命名管道进行通信。


        发送方代码如下:

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstring>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>using namespace std;int main()
{int fd = open("my_namepipe", O_WRONLY);if (fd == -1){perror("open");return -1;}string line;while (true){cout<<"客户端发送的数据:";cin>>line;int n = write(fd,line.c_str(),strlen(line.c_str()));if(n==-1){perror("write");return -1;}}close(fd);return 0;
}

        命名管道文件由接收方在通信前生成,接收方代码如下:

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstring>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>using namespace std;int main()
{int mode = 0666;int n = mkfifo("./my_namepipe", mode);if (n == -1){perror("mkfifo");return -1;}int fd = open("my_namepipe", O_RDONLY);if (fd == -1){perror("open");return -1;}char buff[1024];while (true){int n = read(fd, buff, sizeof(buff) - 1);if (n > 0){buff[n] = 0;cout << "服务器接收到的数据:" << buff << endl;}}close(fd);if(unlink("my_namepipe")==-1){perror("unlink");return -1;}return 0;
}

        运行结果:

        注意事项:读端、写端双方必须都打开了命名管道文件才会都往下执行,也就是说只要有一方没有打开命名管道文件,则另一方就会阻塞在open处。 

结语 

         以上就是关于管道的讲解,管道分两种:匿名管道、命名管道,两者都可进行进程间通信,只不过匿名管道适用于亲缘进程,而命名管道可以使用非亲缘进程,并且系统提供这两个管道的接口给予用户使用,用户也可以在指令层面上使用他们,他们通信的本质都是让两个进程看到同一份资源,对该资源的读写就是通信的过程。

        最后如果本文有遗漏或者有误的地方欢迎大家在评论区补充,谢谢大家!!

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

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

相关文章

国内外大模型集合

为了满足日益增长的AI需求&#xff0c;我们精心打造了一站式大模型导航网站&#xff0c;旨在成为连接您与全球顶尖人工智能模型的桥梁。无论您是科研工作者、开发者还是对AI充满好奇的探索者&#xff0c;这里都有您所需。 国内大模型精选 通义千问 —— 阿里巴巴集团倾力打造…

某业帮六月校招后端笔试

题目一 解题思路 签到题&#xff0c;dp就行。 题目二 解题思路 这个比较烦人&#xff0c;需要处理额外的引号和括号。用DFS&#xff0c;对于每个间隙&#xff0c;插入与不插入都搜一遍。 题目三 解题思路&#xff1a; 双指针&#xff0c;左右各一个指针&#xff0c;对比长度&…

OpenLCA、GREET、R语言的生命周期评价方法、模型构建

原文链接&#xff1a;OpenLCA、GREET、R语言的生命周期评价方法、模型构建教程https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247608240&idx6&sn1b5758206d500399fe7cc69e800f61fe&chksmfa826657cdf5ef413d31557941a1c5db5cc84bba8d0f408c469e05a4118c…

#LinuxC高级 笔记一

linux命令 什么是嵌入式&#xff1f; 以应用为中心&#xff0c;以计算机技术为基础&#xff0c;软件硬件可裁剪&#xff0c;适用于对功能、可靠性、成本、体积、功耗有严格要求的专用计算机系统 计算机系统组成&#xff1f; 硬件、软件 操作系统&#xff1f; ios windows harmo…

k8s_集群搭建_k8s管理前端_dashboard安装部署---分布式云原生部署架构搭建017

然后再去安装一下一个dashboard,有了这个以后,操作k8s集群就不用 一直敲命令了 可以看到上面的命令拿过来,然后 执行就可以了 然后如果执行慢,可以直接先去下载,使用wget,然后再去 也可以在浏览器访问,把内容拿到,然后 下面是内容: # Copyright 2017 The Kubernetes Author…

登 Cell 子刊!清华大学张强锋课题组开发 SPACE 算法,组织模块发现能力领先同类工具

多细胞生物中的细胞尽管共享相同的基因组&#xff0c;但因其内部基因调控网络的差异以及与周围微环境中相邻细胞的外部信号交流&#xff0c;使得它们在形态、基因表达和功能上展现出显著的多样性。为了将细胞类型信息与其在组织内的空间位置相关联&#xff0c;空间转录组学 (Sp…

Vue86-Vuex中的getters属性

一、getters的使用 1-1、index.js中getters的书写 计算属性computed靠return获得返回值&#xff01; 1-2、组件中getters的调用 state是数据源&#xff0c;getters是拿着数据源里的东西进行一番加工。像极了&#xff1a;data和computed 二、小结

vue 启动项目报错Syntax Error: Error: PostCSS received undefined instead of CSS string

启动vue项目然后报错如下图 这个是跟node版本有关系 因为要开发不同的项目使用不同的node版本&#xff0c;所以就用nvm切换&#xff0c;所以导致了node-sass编译问题 执行这个命令就可以 npm install node-sass or npm rebuild node-sass node-sass对于node高版本和低版本切…

智能胎教仪,科技与教育的融合-N9301胎教仪语音方案

随着科学技术的不断进步&#xff0c;人们对婴幼儿教育的认知也日趋成熟和全面。其中&#xff0c;胎教作为一种重要的早期教育方式&#xff0c;近年来备受瞩目。而胎教仪语音芯片的研发&#xff0c;正是为了满足这一需求&#xff0c;为胎儿的健康成长提供更加便捷的胎教方案。 一…

Tomcat服务部署安装

一、Tomcat基础 1.Tomcat简介 Tomcat服务器是一个免费的开放源代码的Web应用服务器&#xff0c;Tomcat虽然和Apache或者Nginx这些Web服务器一样&#xff0c;具有处理HTML页面的功能&#xff0c;然而由于其处理静态HTML的能力远不及Apache或者Nginx&#xff0c;所以Tomcat通常…

“LNMP环境搭建实战指南:从零开始配置CentOS 7下的Nginx、MySQL与PHP“

目录 1.前言 2.准备工作 2.1.环境信息 2.2.关闭SELinux和firewalld 3.安装Nginx 3.1.运行以下命令&#xff0c;安装Nginx 3.2.运行以下命令&#xff0c;查看Nginx版本 4.安装MySQL 4.1.更新秘钥 4.2.配置MySQL的YUM仓库 4.3.安装MySQL 4.4.查看MySQL版本 4.5.启动…

Springboot+Vue3开发学习笔记《1》

SpringbootVue3开发学习笔记《1》 博主正在学习SpringbootVue3开发&#xff0c;希望记录自己学习过程同时与广大网友共同学习讨论。 一、前置条件 博主所用版本&#xff1a; IDEA需要破解&#xff0c;破解工具链接容易挂&#xff0c;关注私聊我单发。 Spring Boot是Spring提…

若依前后端分离 前端路由登录页 如何进行跳转

路由守卫&#xff0c;看这篇文章 http://t.csdnimg.cn/HkypThttp://t.csdnimg.cn/HkypT

MySQL存储与优化 一、MySQL架构原理

1.MySQL体系架构 MySQL Server架构自顶向下大致可以分网络连接层、服务层、存储引擎层和系统文件层 (1)网络连接层 客户端连接器&#xff08;Client Connectors&#xff09;&#xff1a;提供与MySQL服务器建立的支持。目前几乎支持所有主流的服务端编程技术&#xff0c;例如常…

有趣的递归(Recursion),一些直观的示例

从前有座山, 山上有座庙, 庙里有个老和尚在给小和尚讲故事: “从前有座山, 山上有座庙, 庙里有个老和尚在给小和尚讲故事: …” 反复而纠结的定义 看完这个故事, 对递归你已经有了印象, 很好, 这样已足够. 如果你不幸是个喜欢精确定义的人, 那么答案可能无法让你满意: 你想知…

java考试题20道

选择题 编译Java源代码文件的命令是javac javac命令是将Java源代码文件进行编译得到字节码文件(.class文件) java命令是在JVM上运行得到的字节码文件 下面是一个示例&#xff1a; javac test.java -------> test.class java test ------> 运行test.class文件下列那…

vue3 在el-input的光标处插入文本

点击文本框下方的按钮&#xff0c;将相应的文本插入光标处的实现&#xff1a; <el-input type"textarea" rows"4" v-model"formula" blur"handleBlur" clearable></el-input><el-button-group class"short_btn&q…

63、基于深度学习网络的数字分类(matlab)

1、基于深度学习网络的数字分类的原理及流程 基于深度学习网络的数字分类是一种常见的机器学习任务&#xff0c;通常使用的是卷积神经网络&#xff08;CNN&#xff09;来实现。下面是其原理及流程的简要说明&#xff1a; 数据收集&#xff1a;首先&#xff0c;需要收集包含数字…

【shell script】

文章目录 一、基础shell script二、脚本运行方式的差异三、判断式1.利用test命令2.利用判断符号[] 四、if&#xff0c;case语句1.if...then2.case...esac 五、函数function六、循环1.while和until循环2.for循环 一、基础shell script 在“shell”部分&#xff0c;那是在命令行…

Flutter——最详细(Badge)使用教程

背景 主要常用于组件叠加上圆点提示&#xff1b; 使用场景&#xff0c;消息数量提示&#xff0c;消息红点提示 属性作用backgroundColor红点背景色smallSize设置红点大小isLabelVisible是否显示offset设置红点位置alignment设置红点位置child设置底部组件 代码块 class Badge…