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.rhkb.cn/news/367785.html

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

相关文章

源代码层面分析Appium-inspector工作原理

Appium-inspector功能 Appium Inspector 基于 Appium 框架&#xff0c;Appium 是一个开源工具&#xff0c;用于自动化移动应用&#xff08;iOS 和 Android&#xff09;和桌面应用&#xff08;Windows 和 Mac&#xff09;。Appium 采用了客户端-服务器架构&#xff0c;允许用户通…

C++初学者指南-3.自定义类型(第一部分)-异常

C初学者指南-3.自定义类型(第一部分)-异常 文章目录 C初学者指南-3.自定义类型(第一部分)-异常简介什么是异常&#xff1f;第一个示例用途:报告违反规则的行为异常的替代方案标准库异常处理 问题和保证资源泄露使用 RAII 避免内存泄漏&#xff01;析构函数&#xff1a;不要让异…

Taogogo Taocms v3.0.2 远程代码执行漏洞(CVE-2022-25578)

前言 CVE-2022-25578 是一个存在于 Taogogo Taocms v3.0.2 中的代码注入漏洞。此漏洞允许攻击者通过任意编辑 .htaccess 文件来执行代码注入。 漏洞详情 漏洞描述&#xff1a;攻击者可以利用此漏洞上传一个 .htaccess 文件到网站&#xff0c;并在文件中注入恶意代码&#xf…

CesiumJS【Basic】- #058 绘制网格填充多边形(Entity方式)-使用shader

文章目录 绘制网格填充多边形(Entity方式)-使用shader1 目标2 代码2.1 main.ts绘制网格填充多边形(Entity方式)-使用shader 1 目标 使用Entity方式绘制绘制网格填充多边形 - 使用shader 2 代码 2.1 main.ts import * as Cesium from cesium;// 创建 Cesium Viewer 实例…

主流国产服务器操作系统技术分析

主流国产服务器操作系统 信创 "信创"&#xff0c;即信息技术应用创新&#xff0c;作为科技自立自强的核心词汇&#xff0c;在我国信息化建设的进程中扮演着至关重要的角色。自2016年起步&#xff0c;2020年开始蓬勃兴起&#xff0c;信创的浪潮正席卷整个信息与通信技…

程序员AI提效案例:统计B站课程耗时情况

文章目录 一&#xff0c;时长统计需求二&#xff0c;一波三折三&#xff0c;终极方案 AIJava总结 今天为了写一篇博客&#xff0c;这篇博客介绍了B站的一个Java项目&#xff0c;这个项目分为三个阶段&#xff1a; 初级篇高级篇运维篇 一&#xff0c;时长统计需求 我想根据每个…

Spring+SpringMVC+MyBatis整合

目录 1.SSM介绍1.1 什么是SSM&#xff1f;1.2 SSM框架1.2.1 Spring1.2.2 SpringMVC1.2.3 MyBatis 2.SSM框架整合2.1 建库建表2.2 创建工程2.3 pom.xml2.4 log4j.properties2.5 db.properties2.6 applicationContext-dao.xml2.7.applicationContext-tx.xml2.8 applicationContex…

昇思25天学习打卡营第9天|静态图模式的深度剖析与应用指南

目录 背景介绍 动态图模式 静态图模式 静态图模式的使用场景 静态图模式开启方式 基于装饰器的开启方式 基于context的开启方式 静态图的语法约束 JitConfig配置选项 静态图高级编程技巧 背景介绍 AI 编译框架主要包含两种运行模式&#xff0c;即动态图模式与静态图模…

Docker(八)-Docker运行mysql8容器实例

1.运行mysql8容器实例并挂载数据卷 -e:配置环境变量 --lower_case_table_names1 设置忽略表名大小写一定要放在镜像之后运行mysql8容器实例之前&#xff0c;先查看是否存在mysql8镜像以及是否存在已运行的mysql实例docker run -d -p 3306:3306 --privilegedtrue -v 【宿主机日…

【windows】电脑如何关闭Bitlocker硬盘锁

如果你的硬盘显示这样的一把锁&#xff0c;说明开启了Bitlocker硬盘加密。 Bitlocker硬盘锁&#xff0c;可以保护硬盘被盗&#xff0c;加密防止打开查看数据。 方法一&#xff1a;进入“控制面板->BitLocker 驱动器加密”进行设置。或者“控制面板\系统和安全->BitLocke…

数据库对比脚本,java如何对比两个数据库的表字段的不同

因为有时候开发环境和 测试环境&#xff0c;有时候会有不同的数据库表&#xff0c;比如有些加字段了&#xff0c;所以这个脚本就实现了对比两个数据库连接的数据库到底哪里不一样&#xff0c;输出到控制台 package com.junfun.pms;import lombok.extern.slf4j.Slf4j;import ja…

SQL执行慢排查以及优化思路

数据库服务器的优化步骤 当我们遇到数据库调优问题的时候&#xff0c;该如何思考呢&#xff1f;我把思考的流程整理成了下面这张图。 整个流程划分成了观察&#xff08;Show status&#xff09;和行动&#xff08;Action&#xff09;两个部分。字母 S 的部分代表观察&#xf…

Android常用加解密算法总结

Android开发中对于数据的传输和保存一定会使用加密技术&#xff0c;加密算法是最普遍的安保手段&#xff0c;多数情况数据加密后在需要使用源数据时需要再进行解密&#xff0c;但凡是都有例外。下面从可逆加密、不可逆、不纯粹加密三种方式记录一下常见的加解密算法。 加密技术…

HDFS学习

3.5 HDFS存储原理 3.5.1 冗余数据保存 作为一个分布式文件系统&#xff0c;为了保证系统的容错性和可用性&#xff0c;HDFS采用了多副本方式对数据进行冗余存储&#xff0c;通常一个数据块的多个副本会被分布到不同的数据节点上。 如图所示&#xff0c;数据块1被分别存放到…

Eslint与Prettier搭配使用

目录 前置准备 Eslint配置 Prettier配置 解决冲突 前置准备 首先需要安装对应的插件 然后配置settings.json 点开之后就会进入settings.json文件里&#xff0c;加上这两个配置 // 保存的时候自动格式化 "editor.formatOnSave": true, // 保存的时候使用prettier进…

【Qt之·类QTableWidget】

系列文章目录 文章目录 前言一、常用属性二、成员函数2.1 左上角空白区域 三、实例演示总结 前言 一、常用属性 二、成员函数 方法描述selectRow选中行removeRow移除行insertRow插入行rowCount总行数 2.1 左上角空白区域 QTableCornerButton即不属于列表头&#xff0c;也不…

像学Excel 一样学 Pandas系列-创建数据分析维度

嗨&#xff0c;小伙伴们。又到喜闻乐见的Python 数据分析王牌库 Pandas 的学习时间。按照数据分析处理过程&#xff0c;这次轮到了新增维度的部分了。 老样子&#xff0c;我们先来回忆一下&#xff0c;一个完整数据分析的过程&#xff0c;包含哪些部分内容。 其中&#xff0c…

【Python】成功解决TypeError: ‘float‘ object cannot be interpreted as an integer

【Python】成功解决TypeError: ‘float’ object cannot be interpreted as an integer 下滑即可查看博客内容 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我静心耕耘深度学习领域、真诚分享知识与智慧的小天地&#xff01;&#x1f387; &#x1f393; 博主…

【Web3项目案例】Ethers.js极简入门+实战案例:实现ERC20协议代币查询、交易

苏泽 大家好 这里是苏泽 一个钟爱区块链技术的后端开发者 本篇专栏 ←持续记录本人自学智能合约学习笔记和经验总结 如果喜欢拜托三连支持~ 目录 简介 前景科普-ERC20 Ethers极简入门教程&#xff1a;HelloVitalik&#xff08;非小白可跳&#xff09; 教程概览 开发工具 V…

vue3开发过程中遇到的一些问题记录

问题&#xff1a; vue3在使用 defineProps、defineEmits、defineExpose 时不需要import&#xff0c;但是 eslint会报错error defineProps is not defined no-undef 解决方法&#xff1a; 安装 vue-eslint-parser 插件&#xff0c;在 .eslintrc.js 文件中添加配置 parser: vue-e…