当前位置: 首页 > news >正文

Linux学习之路 -- 进程 -- 进程间通信 -- 管道通信

本文主要介绍进程通信中的管道通信。

前面我们学习进程的过程中,我们知道,进程是具有独立性的。这也就导致了进程不能够直接地把数据进行传递。为了实现进程之间地通信,我们就需要通过另外地方式来实现进程之间数据地传递。

1.进程通信的目的

首先,在正式学习进程间通信前,我们需要了解进程间通信的目的

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

总的来说,就是我们往往需要多个进程进行协作,完成一些事情。

2.管道通信的相关原理

1.一般规律

        假设我们有两个进程,我们要实现这两个进程之间的数据通信,肯定是不能把一个进程上的数据直接拷贝到另一个进程的空间上,这样的作法无法保证进程之间的独立性。所以我们需要通过一块(内存)空间来实现两个进程之间的数据。同时,这块空间还不能由通信双方任何一个提供,如果是由其中一个提供,那就允许另一个进程访问,这会破坏进程的独立性。

        所以进程间通信的本质就是让不同的进程看到同一块空间资源,这块资源一般由OS提供。而OS提供的“空间”有不同的样式,就决定了不同的通信方式。
2.实现方式

        前面我们在介绍文件时,我们了解到进程是通过文件描述符表来控制文件的。其中一个文件被不同的方式打开是要占据不同的文件描述符的,而我们再创建一个子进程时,文件描述符表也会跟着创建一份,但是里面的内容是和父进程一致的。这两个进程都会指向同一个文件。


这里父子进程就指向了同一块空间,并且这块空间是由操作系统提供,说明我们可以通过文件的方式,来实现进程之间的通信。这种通信的方式就叫做管道通信

管道通信只能被设计成单向的通信,也就是一个进程读,另一个进程写。正常情况下,我们要以读方式和写方式分别打开两次文件,在不同的进程中关闭不同的文件描述符,这样做是为了让父子进程都可以当作读端或写端。我们把父进程以读方式打开的文件描述符关闭,把子进程以写方式打开的文件描述符关闭,这样就可以实现父进程写,子进程读。

相关接口

为了支持管道通信,系统给我们提供相关的系统接口

<1>pipe

int pipefd[2]是输出型参数,用于存放两个fd,分别是以读和写方式打开的文件描述符。通过该接口,我们就不需要向磁盘中刷新和向磁盘中创建文件。通俗的说,就是创建内存级的文件,叫匿名文件(管道)。这个文件不用把数据加载到磁盘,也不用实现标准输入、输出、错误等等。

匿名管道通信的特点,就是只能让有血缘关系的进程,进行进程间通信(常用于父子进程)

这个接口如果返回零,那么就表示调用成功,如果失败了,就返回-1。如果成功调用,那么pipefd[0]中存放的是读端的文件描述符,而pipefd[1] 中存放的是写端的文件描述符。

下面我们可以用一段代码验证上述的结论

#include<stdio.h>
#include<unistd.h>int main()
{int pipefd[2];int n = pipe(pipefd);if(n < 0) return 1;printf("%d %d\n",pipefd[0],pipefd[1]);return 0;
}

运行结果

下面简单实现一下,用父子进程间进行通信

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>void Write(int wfd)
{const char* str = "hello linux";char buffer[1024];int pid = getpid();while(1){snprintf(buffer,sizeof(buffer),"pid:%d str:%s\n",pid,str);write(wfd,buffer,sizeof(buffer));}}
void Read(int rfd)
{int cnt = 20;char buffer[1024];while(cnt--){ssize_t n = read(rfd, buffer,sizeof(buffer));(void)n;printf("%s", buffer);sleep(1);}
}int main()
{int pipefd[2];int n = pipe(pipefd);if(n < 0) return 1;printf("%d %d\n",pipefd[0],pipefd[1]);pid_t id = fork();//写端if(id == 0){close(pipefd[0]);Write(pipefd[1]);exit(0);}//父进程close(pipefd[1]);Read(pipefd[0]);wait(NULL);
}

 运行结果

 这样我们就实现父子进程的简单通信。

关于管道通信的几种情况。

<1>管道内部没有数据 && 子进程不关闭自己的写端文件fd,读端(父进程)就要阻塞等待,直到管道有数据。

<2>管道内部被写满了 && 读端(父进程)不关闭自己的fd,写段(子进程)写满之后,就要阻塞等待。管道的默认大小是4kB(unbantu 20.04版本下)。在这种情况下,读端会尽可能多的读取数据,当读取到一定数量的数据时,写端又会重新向管道写入数据。

<3>对于写端而言,不写了&&关闭了管道,读端会将pipe中的数据读完,最后就会读到返回值为0,表示读结束,类似与读到文件的结尾。

<4>读端不读&&关闭,写端在写,OS会直接终止写入的进程(子进程),通过信号(13 SIGPIPE)进程终止。(下图是让读端关闭(父进程),写端(子进程)继续写,并打印出退出码和退出信号)。

管道的几种的特性

<1>自带同步机制,也就是执行时有一定的顺序。

<2>有血缘关系之间的通信

<3>管道是面向字节流的(读端和写端的次数没有直接的联系)

<4>父子进程退出,管道自动的释放,文件的声明周期是随进程的。

<5>管道只能单向通信。半双工的一种特殊情况

而我们学习的命令行管道,本质上也就是本文所述的管道。而我们在使用命令行管道时,一个命令就是一个进程,一个竖划线就是一个管道,这些进程的父进程都是bash进程。

应用场景:进程池。

由于我们每次创建进程都要向系统中进行申请,这个过程比较麻烦,所以我们可以直接先申请多个进程,由一个主进程进行控制,每个进程都和父进程之间创建管道,这个就叫进程池。在创建完毕后,我们可以进程池内的进程分配任务,一个进程不能执行全部的任务,而是要让所有的进程都执行一些任务,这个分配规则就叫负载均衡。

命名管道

匿名管道适用于父子进程之间的通信,而我们如果要在完全不相干的两个进程之间进行通信,就需要使用命名管道。

如上图所示,当我们进程A和进程B以不同的方式打开file.txt时,正常来说会生成两个文件缓冲区,但是由于两个文件缓冲区内容是一样的,所以我们就只需要一个文件缓冲区即可。由于此时的文件缓冲区是不需要向文件中刷新数据的(会浪费空间,而且没必要),所以文件缓冲区就可以作为一个管道,实现两个进程之间的通信。

如何保障两个打开的是同一个文件呢(也就是确保同一缓冲区)?我们可以使用文件的路径+文件名的方式锁定文件,这样可以保证打开的就是同一文件。

具体方法:

我们可以使用mkfifo命令创建管道,然后在实现两个进程之间的通信。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 小程序滑动单元格
  • VS Code安装配置ssh服务结合内网穿透远程连接本地服务器详细步骤
  • 永久旋转 PDF 文件的 3 种简便方法
  • [图解]需要≠需求-《分析模式》漫谈
  • 5G:下一代无线通信技术的全面解析
  • DePT: Decoupled Prompt Tuning
  • 3. 数据结构——栈的操作实现(考研专业课学习)
  • [算法题]01 矩阵
  • MybatisPlus使用指南
  • git上传本地代码到新建分支
  • 00067期 matlab中的asv文件
  • Vue 3 中的观察者效果:从 watch 到 watchEffect、watchSyncEffect 和 watchPostEffect
  • 超全面!Midjourney用户手册中文版!详解模型、命令、参数与高级用法
  • MySQL 数据库经验总结
  • HttpUtils工具类(一)常见的HttpUtils工具类及如何自定义java的http连接池
  • [deviceone开发]-do_Webview的基本示例
  • Effective Java 笔记(一)
  • ERLANG 网工修炼笔记 ---- UDP
  • Go 语言编译器的 //go: 详解
  • jquery cookie
  • JS创建对象模式及其对象原型链探究(一):Object模式
  • laravel 用artisan创建自己的模板
  • Laravel5.4 Queues队列学习
  • Laravel深入学习6 - 应用体系结构:解耦事件处理器
  • magento 货币换算
  • Python_OOP
  • Spring Boot快速入门(一):Hello Spring Boot
  • tab.js分享及浏览器兼容性问题汇总
  • Vue.js 移动端适配之 vw 解决方案
  • vue2.0项目引入element-ui
  • 初识 beanstalkd
  • 基于Mobx的多页面小程序的全局共享状态管理实践
  • 将回调地狱按在地上摩擦的Promise
  • 聚类分析——Kmeans
  • 设计模式 开闭原则
  • 我建了一个叫Hello World的项目
  • 一些css基础学习笔记
  • - 转 Ext2.0 form使用实例
  • 字符串匹配基础上
  • 最简单的无缝轮播
  • AI算硅基生命吗,为什么?
  • 阿里云IoT边缘计算助力企业零改造实现远程运维 ...
  • ​探讨元宇宙和VR虚拟现实之间的区别​
  • # Spring Cloud Alibaba Nacos_配置中心与服务发现(四)
  • #define
  • #我与Java虚拟机的故事#连载04:一本让自己没面子的书
  • #我与Java虚拟机的故事#连载16:打开Java世界大门的钥匙
  • #在 README.md 中生成项目目录结构
  • (145)光线追踪距离场柔和阴影
  • (160)时序收敛--->(10)时序收敛十
  • (6)添加vue-cookie
  • (Matalb时序预测)PSO-BP粒子群算法优化BP神经网络的多维时序回归预测
  • (Redis使用系列) Springboot 使用redis实现接口Api限流 十
  • (ZT)薛涌:谈贫说富
  • (回溯) LeetCode 131. 分割回文串