[Linux]进程间通信--管道

[Linux]进程间通信–管道

文章目录

  • [Linux]进程间通信--管道
    • 进程间通信的目的
    • 实现进程间通信的原理
    • 匿名管道
      • 匿名管道的通信原理
      • 系统接口
      • 管道特性
      • 管道的协同场景
      • 管道的大小
    • 命名管道
      • 使用指令创建命名管道
      • 使用系统调用创建命名管道

进程间通信的目的

  • 数据传输:一个进程需要将它的数据发送给另一个进程 。
  • 资源共享:多个进程之间共享同样的资源。
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

实现进程间通信的原理

进程是具有独立性的,一个进程是无法看到另一个进程的代码和数据的,为了让进程间通信,要做的工作就是让不同的进程看到同一份“资源”。

任何进程通信手段需要解决的问题如下:

  • 让不同的进程看到同一份“资源”
  • 让一方进行读取,另一方进行写入

不同的进程间通信手段本质的区别就是让不同的进程看到同一份“资源”的方式不同。

匿名管道

匿名管道是一种以文件为媒介的通信方式,匿名管道是一个内存级别的文件,拥有和普通文件一样的缓冲区,但是操作系统不会将缓冲区刷新至外设,匿名管道虽然是文件,但是由于没有文件路径,进程是无法通过系统文件接口来操作的,因此匿名管道通常用于父子进程之间使用。

匿名管道的通信原理

由于匿名管道没有文件路径,进程是无法通过系统文件接口来操作的特性,匿名管道必须通过父进程创建,子进程继承父进程文件描述符表的方式,使得不同的进程看到同一个文件:

image-20230909104154011

由于匿名管道只支持单向通信,在使用匿名管道进行通信时,父进程必须分别以读方式和写方式打开管道文件,子进程继承了文件描述符表后,一方关闭读端,一方关闭写端进行通信。

注意: 如果父进程只以读方式或者写方式打开,子进程继承文件描述符表后,也是同样的方式,子进程自身无法打开该管道,因此导致无法通信。

系统接口

Linux系统提供了创建匿名管道的系统接口pipe:

//pipe所在的头文件和声明
#include <unistd.h>int pipe(int pipefd[2]);
  • pipefd为输出型参数,用于接收以读方式和写方式打开管道的文件描述符。
  • pipefd[0]获取读端文件描述符,pipefd[1]获取写端文件描述符。
  • 成功返回0,失败返回-1,错误码被设置。

编写如下代码测试pipe接口:

#include <iostream>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <cstdio>
#include <cstdlib>using namespace std;int main()
{//创建管道int pipefd[2] = { 0 };int n = pipe(pipefd);if (n < 0)//出错判断{cout << "errno: " << errno << "strerror: " << strerror(errno) << endl;exit(1); }//创建子进程pid_t id = fork();assert(id != -1);//出错判断//进行通信 -- 父进程进行读取,子进程进行写入if (id == 0){//子进程close(pipefd[0]);const string str = "hello world";int cnt = 1;char buffer[1024];while(1){snprintf(buffer, sizeof(buffer), "%s, 我是子进程, 我的pid:%d, 计数器:%d", str.c_str(), getpid(), cnt++);write(pipefd[1], buffer, strlen(buffer));//向管道写入数据sleep(1);}close(pipefd[1]);exit(0);}//父进程close(pipefd[1]);char buffer[1024];while(1){read(pipefd[0], buffer, sizeof(buffer) - 1);//从管道读取数据cout << "我是父进程," << "child give me: " << buffer << endl;}close(pipefd[0]);return 0;
}

编译代码运行查看结果:

pipe1演示

从运行结果可以看出,建立管道后,父子进程就能够进行数据通信。

管道特性

  1. 单向通信,半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道
  2. 管道的本质是文件,因此管道的生命周期随进程
  3. 管道通信,通常适用于具有“血缘关系的进程”,诸如父子进程、兄弟进程等
  4. 管道的数据是以字节流的形式传输的,读写次数的多数不是强相关的
  5. 具有一定的协同机制

管道的协同场景

场景一: 如果管道内部的数据被读端读取完了,写端不写入,读端就只能等待

编写如下代码(如下代码只是在前文测试pipe接口的代码上做略微改动,主要改动已用-----标识)进行验证:

#include <iostream>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <cstdio>
#include <cstdlib>using namespace std;int main()
{//创建管道int pipefd[2] = { 0 };int n = pipe(pipefd);if (n < 0){cout << "errno: " << errno << "strerror: " << strerror(errno) << endl;exit(1); }//创建子进程pid_t id = fork();assert(id != -1);//进行通信 -- 父进程进行读取,子进程进行写入if (id == 0){//子进程close(pipefd[0]);const string str = "hello world";int cnt = 1;char buffer[1024];while(1){snprintf(buffer, sizeof(buffer), "%s, 我是子进程, 我的pid:%d, 计数器:%d", str.c_str(), getpid(), cnt++);write(pipefd[1], buffer, strlen(buffer));sleep(100); // ---------  模拟写入暂停  --------- }close(pipefd[1]);exit(0);}//父进程close(pipefd[1]);char buffer[1024];while(1){read(pipefd[0], buffer, sizeof(buffer) - 1);cout << "我是父进程," << "child give me: " << buffer << endl;}close(pipefd[0]);return 0;
}

编译代码运行查看结果:

pipe2演示

场景二: 如果管道内部的数据被写端写满了,读端不读取,写端无法继续写入

编写如下代码(如下代码只是在前文测试pipe接口的代码上做略微改动,主要改动已用-----标识)进行验证:

#include <iostream>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <cstdio>
#include <cstdlib>using namespace std;int main()
{//创建管道int pipefd[2] = { 0 };int n = pipe(pipefd);if (n < 0){cout << "errno: " << errno << "strerror: " << strerror(errno) << endl;exit(1); }//创建子进程pid_t id = fork();assert(id != -1);//进行通信 -- 父进程进行读取,子进程进行写入if (id == 0){//子进程close(pipefd[0]);const string str = "hello world";int cnt = 1;char buffer[1024];while(1){snprintf(buffer, sizeof(buffer), "%s, 我是子进程, 我的pid:%d, 计数器:%d", str.c_str(), getpid(), cnt++);write(pipefd[1], buffer, strlen(buffer));printf("cnt: %d\n", cnt); // ---------  显示写入过程  --------- //sleep(100);}close(pipefd[1]);exit(0);}//父进程close(pipefd[1]);char buffer[1024];while(1){sleep(100); // ---------  模拟读取暂停  --------- read(pipefd[0], buffer, sizeof(buffer) - 1);cout << "我是父进程," << "child give me: " << buffer << endl;}close(pipefd[0]);return 0;
}

编译代码运行查看结果:

pipe3演示

场景三: 写端关闭,读端读完了管道内部的数据时,再读就读到了文件的结尾。

编写如下代码(如下代码只是在前文测试pipe接口的代码上做略微改动,主要改动已用-----标识)进行验证:

#include <iostream>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <cstdio>
#include <cstdlib>using namespace std;int main()
{//创建管道int pipefd[2] = { 0 };int n = pipe(pipefd);if (n < 0){cout << "errno: " << errno << "strerror: " << strerror(errno) << endl;exit(1); }//创建子进程pid_t id = fork();assert(id != -1);//进行通信 -- 父进程进行读取,子进程进行写入if (id == 0){//子进程close(pipefd[0]);const string str = "hello world";int cnt = 1;char buffer[1024];while(1){snprintf(buffer, sizeof(buffer), "%s, 我是子进程, 我的pid:%d, 计数器:%d", str.c_str(), getpid(), cnt++);write(pipefd[1], buffer, strlen(buffer));printf("cnt: %d\n", cnt);sleep(1);if (cnt == 5) break; // ---------  写端关闭  --------- }close(pipefd[1]);exit(0);}//父进程close(pipefd[1]);char buffer[1024];while(1){int n = read(pipefd[0], buffer, sizeof(buffer) - 1);if (n > 0){cout << "我是父进程," << "child give me: " << buffer << endl;}else if (n == 0)// ---------  判断读取到文件末尾  --------- {cout << "读取完毕, 读到文件结尾" << endl;break;}else{cout << "读取出错" << endl;break;}}close(pipefd[0]);return 0;
}

编译代码运行查看结果:

pipe4演示

**场景四:**写端一直写,读端关闭,操作系统会给写端发送13号信号终止进程。

编写如下代码(如下代码只是在前文测试pipe接口的代码上做略微改动,主要改动已用-----标识)进行验证:

#include <iostream>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>using namespace std;int main()
{//创建管道int pipefd[2] = { 0 };int n = pipe(pipefd);if (n < 0){cout << "errno: " << errno << "strerror: " << strerror(errno) << endl;exit(1); }//创建子进程pid_t id = fork();assert(id != -1);//进行通信 -- 父进程进行读取,子进程进行写入if (id == 0){//子进程close(pipefd[0]);const string str = "hello world";int cnt = 1;char buffer[1024];while(1){snprintf(buffer, sizeof(buffer), "%s, 我是子进程, 我的pid:%d, 计数器:%d", str.c_str(), getpid(), cnt++);write(pipefd[1], buffer, strlen(buffer));printf("cnt: %d\n", cnt);sleep(1);}close(pipefd[1]);exit(0);}//父进程close(pipefd[1]);char buffer[1024];while(1){int cnt = 0;//sleep(100);int n = read(pipefd[0], buffer, sizeof(buffer) - 1);if (n > 0){cout << "我是父进程," << "child give me: " << buffer << endl;}else if (n == 0){cout << "读取完毕, 读到文件结尾" << endl;break;}else{cout << "读取出错" << endl;break;}//sleep(100);sleep(5);break;// ---------  读端关闭  --------- }close(pipefd[0]);int status = 0;waitpid(id, &status, 0);cout << "signal: " << (status & 0x7F) << endl;// --------- 回收子进程获取退出信号  --------- sleep(3);return 0;
}

编译代码运行查看结果:

pipe5演示

管道的大小

在Linux下,管道(Pipe)的大小受到操作系统的限制。具体来说,管道的大小由内核参数PIPE_BUF定义,通常是4096个字节。

  • 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
  • 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

命名管道

命名管道同样是内存级的文件,和匿名管道的区别就是命名管道可以在指定路径下创建,并且命名可以指定,因此命名管道可以给任何两个不同的进程用于通信。

使用指令创建命名管道

Linux下使用mkfifo 指令就可以在指定路径下创建命名管道。

image-20230910162001046

命名管道同样和匿名管道一样满足管道的协同场景:

namepipe2演示

写端尝试打开管道文件,没有读端,写端就会卡在打开文件这一步骤。

namepipe1演示

右侧读端开始会等待写端写入,后续关闭右侧读端,左侧写端进程直接被终止。

使用系统调用创建命名管道

//mkfifo所在的头文件和声明
#include <sys/types.h>
#include <sys/stat.h>int mkfifo(const char *pathname, mode_t mode);
  • pathname参数 – 创建命名管道的路径
  • mode参数 – 创建命名管道的文件权限
  • 成功返回0,失败返回-1,错误码被设置。

为了测试mkfifo接口编写代码进行测试,首先设置文件结构如下:

image-20230910164247561

makefile文件内容如下:

.PHONY:all
all:client serverclient:client.ccg++ -o $@ $^ -std=c++11server:server.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -rf client server

common.hpp主要用于让两个进程获取管道路径,具体内容如下:

#include <iostream>
#include <string>#define NUM 1024const std::string pipename = "./namepipe"; //管道的路径和管道名mode_t mode = 0666; //创建管道的文件权限

client.cc作为写端输入数据,具体内容如下:

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <cassert>
#include "commn.hpp"int main()
{// 打开管道文件int wfd = open(pipename.c_str(), O_WRONLY);if (wfd < 0){std::cerr << "errno : " << errno << "strerror : " << strerror(errno) << std::endl;exit(1);}//进行通信while(true){char buffer[NUM];std::cout << "请输入内容:";fgets(buffer, sizeof(buffer), stdin);//获取用户输入buffer[strlen(buffer) - 1] = 0;if (strcasecmp(buffer, "quit") == 0) break;//用户输入quit退出进程ssize_t size = write(wfd, buffer, strlen(buffer));assert(size >= 0);(void)size;}close(wfd);return 0;
}

server.cc作为读端用于接收写端的输入并打印,具体内容如下:

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include "commn.hpp"int main()
{umask(0);// 创建管道文件int n = mkfifo(pipename.c_str(), mode);if (n < 0){std::cerr << "errno : " << errno << "strerror : " << strerror(errno) << std::endl;exit(1);}std::cout << "create fifo file success" << std::endl;// 以读方式打开管道文件int rfd = open(pipename.c_str(), O_RDONLY);if (rfd < 0){std::cerr << "errno : " << errno << "strerror : " << strerror(errno) << std::endl;exit(2);}// 进行通信while (true){char buffer[NUM];ssize_t size = read(rfd, buffer, sizeof(buffer) - 1);buffer[size] = 0;if (size > 0){std::cout << "client send me :" << buffer << std::endl;//输出接收的信息}else if (size == 0){std::cout << "client quit, me too!" << std::endl;break;}else{std::cerr << "errno : " << errno << "strerror : " << strerror(errno) << std::endl;break;}}close(rfd);unlink(pipename.c_str()); // 删除文件return 0;
}

编译代码运行查看结果:

namepipe3演示

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

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

相关文章

docker笔记7:Docker微服务实战

1.通过IDEA新建一个普通微服务模块 建Module docker_boot 改POM <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi…

【Maven教程】(四)坐标与依赖:坐标概念,依赖配置、范围、传递性和最佳实践 ~

Maven 坐标与依赖 1️⃣ 什么是Maven 坐标2️⃣ 坐标详解3️⃣ 依赖的配置4️⃣ 依赖范围5️⃣ 传递性依赖6️⃣ 依赖调解7️⃣ 可选依赖8️⃣ 最佳实践8.1 排除依赖8.2 归类依赖8.3 优化依赖 &#x1f33e; 总结 正如前面文章所述&#xff0c;Maven 的一大功能是管理项目依赖…

Spring Boot与Kubernetes:现代云部署的完美组合

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

Python实操 PDF自动识别并提取Excel文件

最近几天&#xff0c;paddleOCR开发了新的功能&#xff0c;通过将图片中的表格提取出来&#xff0c;效果还不错&#xff0c;今天&#xff0c;作者按照步骤测试了一波。 首先&#xff0c;讲下这个工具是干什么用的&#xff1a;它的功能主要是针对一张完整的PDF图片&#xff0c;可…

【MySQL】CRUD (增删改查) 基础

CRUD&#xff08;增删改查&#xff09;基础 一. CRUD二. 新增 &#xff08;Create&#xff09;1. 单行数据 全列插入2. 多行数据 指定列插入 三. 查询&#xff08;Retrieve&#xff09;1. 全列查询2. 指定列查询3. 查询字段为表达式4. 别名5. 去重&#xff1a;DISTINCT6. 排序…

软考备考-程序员-备考笔记

软考备考-程序员-备考笔记 持续更新中》》》》》 更新日志&#xff1a; 刷题软件&#xff1a; 希赛网&#xff1a;每日一练和往年真题是免费的 软考通&#xff1a;完全免费 软考真题&#xff1a;完全免费 51CTO题库:微信小程序&#xff0c;不用下载&#xff0c;顺手 试卷题…

持安科技何艺:基于可信验证的应用访问安全模型 | CCS2023演讲分享

近日&#xff0c;2023CCS成都网络安全大会在成都举办&#xff0c;大会由四川省互联网信息办公室指导&#xff0c;成都市互联网信息办公室、成都高新技术产业开发区管理委员会联合主办&#xff0c;无糖信息技术有限公司承办。 持安科技创始人兼CEO何艺受邀参与2023CCS大会金融安…

【继RNN之后的一项技术】Transfomer 学习笔记

谷歌团队在17年的神作&#xff0c;论文17年6月发布 https://arxiv.org/abs/1706.03762 被NIPS2017收录&#xff0c;目前引用量已经逼近3w。 以下内容参考李沐老师的课程《动⼿学深度学习(Pytorch版)》 简介 注意力 自主性&#xff1a;有目的的搜索某样东西&#xff08;键&…

Xilinx FPGA 超温关机保护

在UG480文档&#xff0c;有关于FPGA芯片热管理的介绍。 首先需要理解XADC中的 Over Temperature&#xff08;OT&#xff09;和User Temperature的关系。片上温度测量用于关键温度警告&#xff0c;也支持自动关机&#xff0c;以防止设备被永久损坏。片上温度测量在预配置和自动关…

C++:类和对象(三)

本文主要介绍初始化列表、static成员、友元、内部类、匿名对象、拷贝对象时编译器的优化。 目录 一、再谈构造函数 1.构造函数体赋值 2.初始化列表 3.explicit关键字 二、static成员 1.概念 2.特性 三、友元 1.友元函数 2.友元类 四、内部类 五、匿名对象 六、拷…

android上架备案公钥和md5获取工具

最近很多公司上架遇到了一个问题&#xff0c;就是要提供app的备案证明&#xff0c;现在android上架都需要备案了&#xff0c;但是我们的证书都是通过工具生成的&#xff0c;哪里知道公钥和md5那些东西呢&#xff1f;无论安卓备案还是ios备案都需要提供公钥和md5。 包括ios的备案…

上海控安携汽车网络安全新研产品出席AUTOSEMO“恒以致远,共创共赢”主题研讨会

8月31日&#xff0c;AUTOSEMO“恒以致远&#xff0c;共创共赢”主题研讨会在天津成功召开。本次大会由中国汽车工业协会软件分会中国汽车基础软件生态标委会&#xff08;简称&#xff1a;AUTOSEMO&#xff09;与天津市西青区人民政府联合主办。现场汇聚了100余位来自产学研政企…

前端如何将后台数组进行等分切割

前端如何切割数组 目标&#xff1a;前端需要做轮播&#xff0c;一屏展示12个&#xff0c;后端返回的数组需要进行切割&#xff0c;将数据以12为一组进行分割 环境&#xff1a;vue3tselement plus 代码如下&#xff1a; function divideArrayIntoEqualParts(array, chunkSiz…

lv3 嵌入式开发-4 linux shell命令(文件搜索、文件处理、压缩)

目录 1 查看文件相关命令 1.1 常用命令 1.2 硬链接和软链接 2 文件搜索相关命令 2.1 查找文件命令 2.2 查找文件内容命令 2.3 其他相关命令 3 文件处理相关命令 3.1 cut 3.2 sed 过滤 3.3 awk 匹配 4 解压缩相关命令 4.1 解压缩文件的意义 4.2 解压缩相关命令 1 …

智慧公厕破解公共厕所管理的“孤岛现象”

在现代社会中&#xff0c;公共厕所是城市管理中的一项重要任务。然而&#xff0c;经常会出现公厕管理的“孤岛现象”&#xff0c;即每个公厕都是独立运作&#xff0c;缺乏统一的管理和监控机制。针对这一问题&#xff0c;智慧公厕的出现为解决公共厕所管理难题带来了新的方案。…

【Unity3D】UI Toolkit样式选择器

1 前言 UI Toolkit简介 中介绍了样式属性&#xff0c;UI Toolkit容器 和 UI Toolkit元素 中介绍了容器和元素&#xff0c;本文将介绍样式选择器&#xff08;Selector&#xff09;&#xff0c;主要包含样式类选择器&#xff08;Class Selector&#xff09;、C# 类选择器&#xf…

C++ 结构体

前文 C中的结构体是一种非常有用的数据类型&#xff0c;它允许我们将不同的变量组合在一起&#xff0c;形成一个自定义的数据结构。 结构体在C中的应用非常广泛&#xff0c;它可以用来表示和管理各种实体、对象或数据的属性。比如&#xff0c;在一个学生管理系统中&#xff0c…

阿里云和腾讯云2核2G服务器价格和性能对比

2核2G云服务器可以选择阿里云服务器或腾讯云服务器&#xff0c;腾讯云轻量2核2G3M带宽服务器95元一年&#xff0c;阿里云轻量2核2G3M带宽优惠价108元一年&#xff0c;不只是轻量应用服务器&#xff0c;阿里云还可以选择ECS云服务器u1&#xff0c;腾讯云也可以选择CVM标准型S5云…

实现在一张图片中寻找另一张图片的目标

OpenCV库中的SIFT特征检测算法和FLANN&#xff08;快速最近邻搜索库&#xff09;匹配算法来找到一个图片中的元素在另一个图片中的位置&#xff0c;并在源图片中标出它们的位置。 以下是一个简单的例子&#xff0c;使用OpenCV库&#xff0c;利用SIFT特征检测算法&#xff0c;在…

《向量数据库》——向量数据库Milvus 和大模型出联名款AI原生Milvus Cloud

大模型技术的发展正加速对千行百业的改革和重塑,向量数据库作为大模型的海量记忆体、云计算作为大模型的大算力平台,是大模型走向行业的基石。而电商行业因其高度的数字化程度,成为打磨大模型的绝佳“战场”。 在此背景下,Zilliz 联合亚马逊云科技举办的【向量数据库 X 云计…