【Linux 15】进程间通信的方式 - 管道

文章目录

  • 🌈 一、管道介绍
  • 🌈 二、匿名管道
    • ⭐ 1. 匿名管道的概念
    • ⭐ 2. 匿名管道的创建
    • ⭐ 3. 匿名管道的本质
    • ⭐ 4. 匿名管道的使用
    • ⭐ 5. 匿名管道的特点
    • ⭐ 6. 匿名管道的大小
  • 🌈 三、命名管道
    • ⭐ 1. 命名管道的概念
    • ⭐ 2. 命名管道的创建
    • ⭐ 3. 命名管道的使用
    • ⭐ 4. 命名管道的特点
  • 🌈 四、管道的特殊情况

🌈 一、管道介绍

什么是管道

  • 管道是一个进程连接到另一个进程的一个数据流

举个例子

  • 现在有个统计云服务器用户登录个数的指令:who | wc -l
    • who 用于查看当前云服务器的登录用户 (一行一个用户),wc -l 用于统计当前的行数。
  • who 和 wc -l 是两个不同的进程,它们却能很好的配合得出想要的结果,靠的就是管道 |

在这里插入图片描述
在这里插入图片描述

🌈 二、匿名管道

⭐ 1. 匿名管道的概念

  • 匿名管道不需要名字,只用保证具有亲缘关系的进程能够看到即可,常用于父子进程之间的通信
  • 创建子进程时,子进程会拷贝父进程的 PCB、地址空间、页表、文件描述符表 (struct files_struct)。
    • 子进程在拷贝时用的是浅拷贝,因此父子进程的文件描述符表中的 fd_array 数组中的指针指向的都是同一份文件资源,这才导致父子进程能看到同一份文件。
  • 由于进程间通信的本质是让多个进程看到同一份资源,此时父子进程看到同一个文件及缓冲区不就属于进程间通信了吗。

在这里插入图片描述

⭐ 2. 匿名管道的创建

1. 创建匿名管道函数

#include <unistd.h>int pipe(int pipefd[2]);
  • 功能:创建一条无名管道。
  • 参数:pipefd 是一个文件描述符数组且是个输出型参数,pipefd [0] 表示管道的读端,pipefd [1] 表示管道的写端。
  • 返回:创建匿名管道成功返回 0,失败则返回 - 1 并设置错误码。

2. 创建匿名管道实例

#include <cassert>
#include <unistd.h>
#include <iostream>using std::cin;
using std::cout;
using std::endl;int main()
{   // 创建管道int pipefd[2] = { 0 };int n = pipe(pipefd);   assert(0 == n);cout << "读端 fd: " << pipefd[0] << endl;cout << "写端 fd: " << pipefd[1] << endl;return 0;
}

在这里插入图片描述

⭐ 3. 匿名管道的本质

1. 读写端的本质

  • 读端和写端说白了就是两个不同 file 结构体,它们指向同一个文件管道文件
  • 读端的 file 结构体负责从管道文件中读消息,写端的 file 结构体负责往管道文件写消息。

2. 实现单向通信的过程

  1. 父进程向 OS 申请创建管道,设置管道的读写端。

在这里插入图片描述

  1. 父进程 fork 出子进程,由于子进程会对父进程进行拷贝,子进程也会持有管道的读写端。

在这里插入图片描述

  1. 保证管道单向通信的特性,父子进程不能同时持有管道的读写端。
    • 父进程向子进程发送数据:需要父进程关闭读端 pipefd[0],子进程关闭写端 pipefd[1]。
    • 子进程向父进程发送数据:需要父进程关闭写端 pipefd[1],子进程关闭读端 pipefd[0]。

在这里插入图片描述
在这里插入图片描述

⭐ 4. 匿名管道的使用

  • 实现一个子进程向父进程发送数据的单向通信管道。

1. 代码展示

#include <cstring>
#include <cassert>
#include <unistd.h>
#include <iostream>
#include <sys/wait.h>
#include <sys/types.h>using std::cin;
using std::cout;
using std::endl;int main()
{/* 1.创建管道 */int pipefd[2] = {0};int n = pipe(pipefd);assert(0 == n);/* 2.创建子进程 */pid_t id = fork();if (id < 0) // 子进程创建失败return 1;/* 3.建立子写,父读的单向通信的管道 */// 父进程关闭写端,子进程关闭读端if (0 == id){// 子进程关闭读端close(pipefd[0]); // 子进程从写端向管道写入消息for (int count = 10; count >= 0; count--){char message[1024];snprintf(message, sizeof(message) - 1,"hello father, I am child, pid: %d, count: %d",getpid(), count);write(pipefd[1], message, strlen(message));sleep(1);}// 子进程退出exit(0); }/* 4.父进程从读端从管道读取消息并打印 */close(pipefd[1]); // 父进程关闭写端char buffer[1024];while (true){ssize_t n = read(pipefd[0], buffer, sizeof(buffer) - 1);if (n > 0) 		// 读取成功{buffer[n] = '\0';cout << "child say: " << buffer << " to me!" << endl;}else if (0 == n)// 读取结束{break;}}pid_t rid = waitpid(id, nullptr, 0); // 父进程等待子进程退出if (rid == id)                       // 等待成功cout << "wait success" << endl;return 0;
}

2. 结果展示

在这里插入图片描述

⭐ 5. 匿名管道的特点

  • 匿名管道有如下 5 种特性。

1. 匿名管道只能用于具有共同祖先的进程

  • 匿名管道只能用于具有亲缘关系的进程之间进行通信,常用于父子进程之间的通信。

2. 匿名管道提供的是流式服务

  • 流式概念:提供一个通信的信道,写端就负责写,读端就负责读。但是,具体写多少、读多少完全由上层决定。底层就只提供一个数据通信的信道。它不关心数据本身的一些细节或格式,这叫做面向字节流。
  • 流式服务:数据没有明确的分割,一次拿多少数据都行。

3. 匿名管道内部自带同步与互斥机制

  • 同步:父子进程在执行时,具有一定的顺序性。
  • 互斥:父子进程不管谁是写端,谁是读端,同时只能有一个访问管道文件。

4. 匿名管道的生命周期随进程结束而结束

  • 管道本质上是通过文件进行通信的,也就是说管道依赖于文件系统。
  • 当所有打开该文件的进程都退出后,该文件也就会被释放掉,所以说匿名管道的生命周期随进程的结束而结束。

5. 匿名管道采用半双工的通信方式

  • 匿名管道是一个只允许单向通信的共享资源文件。
    • 即一个管道只允许进程 A 写进程 B 读,或进程 B 写进程 A 读。
  • 如果想实现双向通信,只需要建立两个匿名管道即可。
    • 即管道 1 用于进程 A 写进程 B 读,管道 2 用于进程 B 写进程 A 读。

在这里插入图片描述

⭐ 6. 匿名管道的大小

  • 管道文件的容量也是有极限的,如果管道已满,那么写端将阻塞或写失败,可以通过以下 2 种方式获取管道的大小。

1. 编写代码验证

  • 现在已经知道了写端在管道满时会阻塞等待,可以根据这个特性获取管道文件的大小。
    • 实现方式:定义一个值为 0 的计数器,写端每次往管道种写入 1 个字节,然后让计数器 +1,直到写端阻塞住时的计数器的值就是管道的大小。
#include <cassert>
#include <unistd.h>
#include <iostream>
#include <sys/wait.h>
#include <sys/types.h>using std::cout;
using std::endl;int main()
{/* 1.创建管道 */int pipefd[2] = {0};int n = pipe(pipefd);assert(0 == n);/* 2.创建子进程 */pid_t id = fork();/* 3.只让子进程不停的向管道写入 */if (0 == id){// 子进程关闭读端close(pipefd[0]); // 子进程不停的从写端向管道写入消息int count = 0;while (true){char ch = 'a';write(pipefd[1], &ch, 1);   // 每次向管道中写入 1 个字节的内容cout << "write ......: " << ++count << endl;}exit(0); // 子进程退出}/* 4.父进程停止从管道种读取数据 */close(pipefd[1]); 					 // 父进程关闭写端pid_t rid = waitpid(id, nullptr, 0); // 父进程等待子进程退出if (rid == id)                       // 等待成功cout << "wait success" << endl;return 0;
}

在这里插入图片描述

2. 使用 ulimit -a 指令查看

在这里插入图片描述

🌈 三、命名管道

⭐ 1. 命名管道的概念

  • 匿名管道的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
  • 如果想实现多个不相关的进程之间的通信,可以使用命名管道
  • 命名管道是一种特殊类型的文件 (FIFO 文件),命名管道有自己的路径 + 文件名。
  • 进程通过命名管道唯一名字 (路径 + 文件名) 打开同一个管道文件,让多个进程看到同一份资源,即可实现进程间通信。

⭐ 2. 命名管道的创建

  • 创建命名管道的方式有如下 2 种。

1. 通过 mkfifo 指令创建命名管道

  • 可以在命令行中输入如下指令创建一个命名管道,命名管道的名字可自定义。
mkfifo filename	

在这里插入图片描述

2. 通过 mkfifo 函数创建命名管道

#include <sys/stat.h>
#include <sys/types.h>int mkfifo(const char* pathname, mode_t mode);
  • 参数:
    • pathname:如果该参数是个路径,则在指定路径下创建命名管道;如果只是个文件名,则在当前路径下创建命名管道。
    • mode:创建命名管道时,要赋予该命名管道文件的默认权限。
  • 返回:如果创建命名管道成功则返回 0,失败则返回 -1。
#include <cassert>
#include <sys/stat.h>
#include <sys/types.h>const char* pathname = "name_pipe";int main()
{// 在当前路径创建名为 name_pipe 的命名管道并赋予其 0666 的权限int n = mkfifo(pathname, 0666);assert(0 == n);return 0;
}

在这里插入图片描述

⭐ 3. 命名管道的使用

  • 命名管道有如下 2 种使用方式。

1. 通过指令使用命名管道

  • 使用输出重定向的方式往命名管道中写入文件。
    • 如:使用 echo “hello pipe” > name_pipe 向之前创建的命名管道写入数据。
  • 使用输入重定向的方式从命名管道中读取文件。
    • 如:使用 cat < name_pipe 从命名管道中读取数据,然后打印到屏幕上。

在这里插入图片描述

2. 通过代码使用命名管道

  • 由于命名管道就是个管道文件,那么只要一个进程以读方式打开管道文件,另一个进程以写方式打开管道文件,即可实现进程之间的互相通信。
    • 例:实现一个两个进程打开同一份命名管道,客户端进程负责向管道写入,服务端进程负责从管道读取。
  1. server.cpp :创建命名管道,以读方式打开命名管道,作为读端从命名管道中读取数据并打印。
#include <fcntl.h>
#include <cassert>
#include <unistd.h>
#include <iostream>
#include <sys/stat.h>
#include <sys/types.h>using std::cout;
using std::endl;#define FILENAME "fifo" // 命名管道的名字int main()
{// 创建命名管道int n = mkfifo(FILENAME, 0666);assert(0 == n);// 以只方式打开命名管道文件int rfd = open(FILENAME, O_RDONLY); assert(rfd >= 0);// 从管道中读取数据char buffer[1024];while (true){ssize_t s = read(rfd, buffer, sizeof(buffer) - 1);if (s > 0)       // 读取成功{buffer[s] = '\0';cout << "client say: " << buffer << endl;}else if (0 == s) // 写端关闭{break;}}// 关闭读端close(rfd);return 0;
}
  1. client.cpp :以写方式打开命名管道,作为写端获取用户输入并向命名管道中写入数据。
#include <string>
#include <fcntl.h>
#include <cassert>
#include <unistd.h>
#include <iostream>
#include <sys/stat.h>
#include <sys/types.h>using std::cin;
using std::cout;
using std::endl;
using std::string;
using std::getline;#define FILENAME "fifo" // 命名管道的名字int main()
{// 以写方式打开命名管道文件int wfd = open(FILENAME, O_WRONLY);assert(wfd >= 0);// 获取用户输入并将其写入管道string message;while (true){   cout << "请输入: ";getline(cin, message);ssize_t s = write(wfd, message.c_str(),  message.size());assert(s >= 0);}// 关闭写端close(wfd);return 0;
}
  • 结果展示:

在这里插入图片描述

⭐ 4. 命名管道的特点

1. 任何进程都可以使用命名管道

  • 命名管道不受进程间亲缘关系的限制,任何进程都可以通过管道的名称来访问管道进行通信

2. 匿名管道提供的是流式服务

  • 流式概念:提供一个通信的信道,写端就负责写,读端就负责读。但是,具体写多少、读多少完全由上层决定。底层就只提供一个数据通信的信道。它不关心数据本身的一些细节或格式,这叫做面向字节流。
  • 流式服务:数据没有明确的分割,一次拿多少数据都行。

3. 匿名管道内部自带同步与互斥机制

  • 同步:父子进程在执行时,具有一定的顺序性。
  • 互斥:父子进程不管谁是写端,谁是读端,同时只能有一个访问管道文件。

4. 命名管道不随进程的终止而消失

  • 命名管道是有全局唯一的名称 (路径 + 文件名),其是作为文件系统中的特殊文件存在的。
  • 即使创建它的进程终止,只要该管道文件未被删除,其他进程依然可以通过命名管道的名字来使用它。

5. 命名管道提供半/全双工通信

  • 命名管道可以是单向通信,也可以是双向通信,具体取决于管道的打开方式和通信协议。

🌈 四、管道的特殊情况

  1. 正常情况下,如果管道文件没数据了,读端必须等待,直到有数据 (写端写入数据) 了为止 。
  2. 正常情况下,如果管道文件被写满了,写端必须等待,直到有空间 (读端读出数据) 了为止 。
  3. 写端关闭,读端一直读取:读端会读到 read 的返回值为 0,表示读到了文件的结尾。
  4. 读端关闭,写端一直写入:OS 不会做任何浪费时空的事情,会直接向目标进程发送 SIGPIPE (13 号) 信号将写端进程终止。

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

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

相关文章

通讯录管理系统(C语言)

需求及功能分析 本系统主要划分为8个子系统&#xff0c;如下图所示。 增加联系人模块删除联系人模块查找联系人模块插入联系人模块保存联系人模块加载联系人模块显示联系人模块退出模块 具体代码 #include <stdio.h> #include <string.h> #include <stdlib…

前端八股文 $set

为什么会有$set vue2中对数组中新增的属性是监听不到的 如图 vue 插件中有但是 视图中没有刷新 解决方法 解决就是 $set() 就是在数组中新增属性的时候可以重新渲染视图 具体的写法 写法 就是 第一个 是在那个对象上新增 第二个参数 是新增的属性 第三个参数是 新增的属性…

R语言画散点图-饼图-折线图-柱状图-箱线图-等高线图-曲线图-热力图-雷达图-韦恩图(三D)

R语言画散点图-饼图-折线图-柱状图-箱线图-等高线图-曲线图-热力图-雷达图-韦恩图&#xff08;三D&#xff09; 散点图使用 plotly 包示例解析效果 使用 scatterplot3d 包示例解析效果 饼图使用 plotly 包示例解析效果 使用 plotrix 包示例解析效果 折线图使用 plotly 包示例解…

在STM32嵌入式中C/C++语言对栈空间的使用

像STM32这样的微控制器在进入main函数之前需要对栈进行初始化。可以说栈是C语言运行时的必要条件。我们知道栈实际上是一块内存空间&#xff0c;那么这块空间都用来存储什么呢&#xff1f;有什么办法能够优化栈空间的使用&#xff1f; 栈空间保存的内容 栈是一个先入后出的数据…

mac无法清空废纸篓怎么办 mac废纸篓清空了如何找回 cleanmymac误删文件怎么恢复

废纸篓相当于“一颗后悔药”&#xff0c;用于临时存储用户删除的文件。我们从从Mac上删除的文件&#xff0c;一般会进入废纸篓中。如果我们后悔了&#xff0c;可以从废纸篓中找回来。然而&#xff0c;有时我们会发现mac无法清空废纸篓&#xff0c;这是怎么回事?本文将探讨一些…

【数据结构初阶】顺序表

hi&#xff0c;我们又见面啦&#xff01;happy~~~ 目录 前言&#xff1a; 一、线性表 二、顺序表 1、概念 2、与数组的区别 3、分类 4、动态顺序表的实现 SeqList.h 见下 SeqList.c 见下 test.c 见下 【注意】 ————————————— 致回不去的童年 ———…

前端调试技巧:动态高亮渲染区域

效果&#xff1a; 前端界面的渲染过程、次数&#xff0c;会通过高亮变化来显示&#xff0c;通过这种效果排除一些BUG 高亮 打开方式 F12进入后点击ESC&#xff0c;进入rendering&#xff0c;选择前三个即可&#xff08;如果没有rendering&#xff0c;点击橘色部分勾选上&…

swiftui使用ScrollView实现左右滑动和上下滑动的效果,仿小红书页面

实现的效果如果所示&#xff0c;顶部的关注用户列表可以左右滑动&#xff0c;中间的内容区域是可以上下滚动的效果&#xff0c;点击顶部的toolbar也可以切换关注/发现/附近不同页面&#xff0c;实现翻页效果。 首页布局 这里使用了NavigationStack组件和tabViewStyle样式配置…

zerotier安装后设备在线,两个设备无法ping通

来源 组 NAS&#xff0c; 软路由&#xff0c;内网穿透&#xff0c;远程访问&#xff0c;安装了 zerotier&#xff0c;无法ping通 方法 修改windows防火墙&#xff0c;Configure the Windows firewall to allow pings。 Search for and open Windows Firewall.Select Advance…

Node.js快速入门

Node.js 1、Node.js介绍与安装 官网&#xff1a;https://nodejs.cn/ 介绍&#xff1a;简单的说 Node.js 就是运行在服务端的 JavaScript。 Node.js 是一个基于 Chrome JavaScript 运行时建立的一个平台。 Node.js 是一个事件驱动 I/O 服务端 JavaScript 环境&#xff0c;基…

Python | Leetcode Python题解之第274题H指数

题目&#xff1a; 题解&#xff1a; class Solution:def hIndex(self, citations: List[int]) -> int:left,right 0,len(citations)while left<right:# 1 防止死循环mid (leftright1)>>1cnt 0for v in citations:if v>mid:cnt1if cnt>mid:# 要找的答案在…

哈希 -- 简单实现

在STL库中&#xff0c;有map和set两个关联式容器&#xff0c;这两个容器的底层都是以红黑树为底层。但是在后续的发展过程中&#xff0c;我们发现有些场景的数据不适合用红黑树进行存储&#xff0c;所以有人就发明了底层为哈希表的map和set,称为unordered_map 和 unordered_set…

C语言中的控制语句(一):if语句

文章目录 &#x1f34a;自我介绍&#x1f34a;if 的单分支语句&#x1f34a;a.if 单分支判断&#x1f34a;b.if单分支选择判断 &#x1f34a;if多分支语句&#x1f34a;if多分支选择判断 你的点赞评论就是对博主最大的鼓励 当然喜欢的小伙伴可以&#xff1a;点赞关注评论收藏&a…

MMCV 核心组件分析(一):整体概述

概述 MMCV 是计算机视觉研究的基础库&#xff0c;并提供以下功能。

(11)Python引领金融前沿:投资组合优化实战案例

1. 前言 本篇文章为 Python 对金融的投资组合优化的示例。投资组合优化是从一组可用的投资组合中选择最佳投资组合的过程&#xff0c;目的是最大限度地提高回报和降低风险。 投资组合优化是从一组可用的投资组合中选择最佳投资组合的过程&#xff0c;目的是最大限度地提高回报…

单例模式_Golang

目录 一、单例模式 1.1 基本概念 1.2 使用场景 二、Golang实现 2.1 懒汉模式&#xff08;Lazy Loading&#xff09; 一、单例模式 1.1 基本概念 一个类只能生成一个实例&#xff0c;且该类能自行创建这个实例的一种模式,这个定义个人感觉可以拆的通俗一些,在项目的生命周…

电子电器架构 - SOA架构软件平台

电子电器架构 - SOA架构软件平台 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无…

Spring通过工厂方法进行配置

在Spring的世界中&#xff0c; 我们通常会利用 xml配置文件 或者 annotation注解方式来配置bean实例&#xff01; 在第一种利用 xml配置文件 方式中&#xff0c; 还包括如下三小类 反射模式&#xff08;我们前面的所有配置都是这种模式&#xff09;工厂方法模式Factory Bean模…

【Spark官方文档部分翻译】RDD编程指南(RDD Programming Guide)

写在前面 内容如何选择 本翻译只翻译本人认为精华的部分&#xff0c;本人认为的Spark的一些核心理念&#xff0c;编程思想。一些特别基础的操作包括但不限于搭建环境就不在此赘述了。 配套版本 本系列基于Spark 3.3.1&#xff0c;Scala 2.12.10&#xff0c;进行翻译总结 原…

Linux+InternStudio 关卡(test)

任务地址&#xff1a; https://github.com/InternLM/Tutorial/blob/camp3/docs/L0/Linux/task.md 文档 https://github.com/InternLM/Tutorial/blob/camp3/docs/L0/Linux/readme.md 任务 ssh连接 端口映射 gradio页面 笔记&#xff1a; 1.端口映射阶段&#xff1a;输入密…