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

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

相关文章

【多线程】常见的锁策略

常见的锁策略 1. 乐观锁 vs 悲观锁2. 读写锁 vs 普通互斥锁3. 重量级锁 vs 轻量级锁4. 自旋锁&#xff08;Spin Lock&#xff09;vs 挂起等待锁5. 公平锁 vs 非公平锁6. 可重入锁 vs 不可重入锁7. Synchronized8. 相关面试题 1. 乐观锁 vs 悲观锁 悲观锁&#xff1a; 总是假设…

GStreamer MIME类型

MIME type的全称是 Multipurpose Internet Mail Extensions (MIME) &#xff0c;可以标志一个文件的类型。 Table of Audio Types Media Type Description All audio types. audio/* All audio types channels integer channel-mask bitmask format string layou…

NLP机器翻译全景:从基本原理到技术实战全解析

目录 一、机器翻译简介1. 什么是机器翻译 (MT)?2. 源语言和目标语言3. 翻译模型4. 上下文的重要性 二、基于规则的机器翻译 (RBMT)1. 规则的制定2. 词典和词汇选择3. 限制与挑战4. PyTorch实现 三、基于统计的机器翻译 (SMT)1. 数据驱动2. 短语对齐3. 评分和选择4. PyTorch实现…

MybatisPlus分页插件使用

一. 效果展示 二. 代码编写 2.1 pom <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.2</version> </dependency>2.2 添加配置类 Configuration MapperScan(…

软考知识汇总--结构化开发方法

文章目录 1 结构化开发2 耦合3 内聚4 设计原则5 系统文档6 数据流图6.1 数据流图的基本图形元素 7 数据字典 1 结构化开发 结构化方法总的指导思想是自顶向下、逐层分解&#xff0c;它的基本原则是功能的分解与抽象。它是软件工程中最早出现的开发方法&#xff0c;特别适合于数…

U3D外包开发框架及特点

U3D&#xff08;Unity3D&#xff09;是一款流行的跨平台游戏开发引擎&#xff0c;用于创建2D和3D游戏以及交互性应用程序。U3D有许多常用的开发框架和库&#xff0c;这些框架和库可以扩展其功能&#xff0c;使开发人员更轻松地构建游戏和应用程序。以下是一些常用的U3D开发框架…

Si3262 一款低功耗刷卡+触摸+mcu 三合一SOC芯片

Si3262是-款高度集成的低功耗soC芯片&#xff0c;其集成了基于RISC-V 核的低功耗MCU和工作在13.56MHz的非接触式读写器模块。 该芯片ACD模式下刷卡距离可达4-5cm&#xff08;天线决定&#xff09;&#xff0c;适用于智能门锁&#xff0c;电子锁&#xff0c;柜锁&#xff0c;桑拿…

Android相机调用-CameraX【外接摄像头】【USB摄像头】

Android相机调用有原生的Camera和Camera2&#xff0c;我觉得调用代码都太复杂了&#xff0c;CameraX调用代码简洁很多。 说明文档&#xff1a;https://developer.android.com/jetpack/androidx/releases/camera?hlzh-cn 现有查到的调用资料都不够新&#xff0c;对于外接摄像…

【C++】仿函数和priority_queue(优先级队列)

目录 一、仿函数 二、priority_queue(优先级队列) 1、概念&#xff1a; 2、使用&#xff1a; 3、数组中第K个最大元素 4、priority_queue的模拟实现 一、仿函数 ①、概念&#xff1a; 仿函数&#xff0c;即函数对象。一种行为类似函数的对象&#xff0c;调用者可以像函…

Java+Tif图片转Jpg

Tif转Jpg使用心得&#xff1a; 如果tif图片需要压缩&#xff0c;或者需要做转换&#xff0c;常用方法&#xff1a; File file1 new File("E:\\www\\ffw\\images\\73.jpg");byte[] bigContent Files.readAllBytes(file1.toPath());ByteArrayInputStream byteArrayIn…

解决Maven依赖下载问题:从阿里云公共仓库入手

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

安装RabbitMQ的各种问题(包括已注册成windows服务后,再次重新安装,删除服务重新注册遇到的问题)

一、安装Erlang&#xff08;傻瓜式安装&#xff09; 安装完成之后&#xff0c;配置环境变量&#xff1a; 1.新建系统变量名为&#xff1a;ERLANG_HOME 变量值为erlang安装地址 2. 双击系统变量path&#xff0c;点击“新建”&#xff0c;将%ERLANG_HOME%\bin加入到path中。 …

c++类与对象(中)

文章目录 前言一、构造函数1、构造函数介绍2、构造函数特性 二、析构函数1、析构函数介绍2、析构函数特性 三、拷贝构造函数1、拷贝构造函数介绍2、拷贝构造函数特征3、拷贝构造函数的应用 -- 求n天后的日期 四、赋值运算符重载1、运算符重载2、一些运算符重载的实现3、赋值运算…

C++数据结构X篇_12_树的基本概念和存储

学习二叉树之前先学习树的概念。 文章目录 1. 树的基本概念1.1 树的定义1.2 树的特点1.3 若干术语 2. 树的表示法2.1 图形表示法2.2 广义表表示法 3. 树的存储3.1 双亲表示法&#xff1a;保存父节点关系3.2 孩子表示法3.3 左孩子右兄弟表示法 1. 树的基本概念 之前所学均为线性…

前端构建工具 webpack 笔记

1、了解 webpack 1、定义&#xff1a;本质上&#xff0c;webpack 是一个用于现代 JavaScript 应用程序的静态模块打包工具&#xff0c;当 webpack 处理应用它会在内部从一个或多个入口点构建一个依赖图(dependency graph)&#xff0c;然后将你项目中所程序时&#xff0c;需的…

【javaSE】 反射与反射的使用

文章目录 &#x1f332;反射的定义&#x1f38d;反射的用途&#x1f334;反射基本信息&#x1f340;反射相关的类&#x1f6a9;Class类(反射机制的起源 )&#x1f388;Class类中的相关方法 &#x1f6a9;反射示例&#x1f388;获得Class对象的三种方式&#x1f388;反射的使用 …

Activating More Pixels in Image Super-Resolution Transformer(HAT)超分

摘要 基于Transformer的方法在低级视觉任务&#xff08;如图像超分辨率&#xff09;上表现出令人印象深刻的性能。然而&#xff0c;我们发现这些网络只能通过归因分析利用有限的输入信息空间范围。这意味着Transformer的潜力在现有网络中仍未得到充分利用。为了激活更多输入像…

Spring 的创建和日志框架的整合

目录 一、第一个 Spring 项目 1、配置环境 2、Spring 的 jar 包 Maven 项目导入 jar 包和设置国内源的方法&#xff1a; 3、Spring 的配置文件 4、Spring 的核心 API ApplicationContext 4、程序开发 5、细节分析 &#xff08;1&#xff09;名词解释 &#xff08;2&…

uboot 顶层Makefile-make xxx_deconfig过程说明三

一. uboot 的 make xxx_deconfig配置 本文接上一篇文章的内容。地址如下&#xff1a;uboot 顶层Makefile-make xxx_deconfig过程说明二_凌肖战的博客-CSDN博客 本文继续来学习 uboot 源码在执行 make xxx_deconfig 这个配置过程中&#xff0c;顶层 Makefile有关的执行思路。 …

STM32纯中断方式发送接收数据(串行通信;keil arm5;)

除了main文件其他文件均无修改&#xff0c;正常运行--在keil arm5内