【Linux】进程间通信 -- 管道

对于进程间通信的理解

首先,进程间通信的本质是,让不同的进程看到同一份资源(这份资源不能隶属于任何一个进程,即应该是共享的)。而进程间通信的目的是为了实现多进程之间的协同。
但由于进程运行具有独立性(虚拟地址空间+页表 保证了进程运行的独立性),所以要实现进程间的通行难度会比较大。
管道通信作为进程间通信的一种方式,Linux原生就能提供。其通信方式又分为两种:匿名管道 和 命名管道。

匿名管道

匿名管道通信常用于父子进程间的通信。通过fork创建子进程,让具有血缘关系的进程能够进行通信。
其实现通信的步骤主要有3步:

  1. 父进程分别以读和写方式打开同一个文件
  2. fork()创建子进程
  3. 父子进程各自关闭自己不需要的文件描述符

在这里插入图片描述
如上图看管道本质还是文件。
既然管道通信,首先要能够创建出管道。pipe系统接口可以帮助创建管道。其参数pipefd是一个数组,

// pipefd[0]对应读端的文件描述符
// pipefd[1]对应写端的文件描述符
int pipe(int pipefd[2]);
// 匿名管道通信测试
void Test1()
{// 1.创建管道int pipefd[2] = {0};int ret = pipe(pipefd);if(ret != 0){perror("pipe");exit(1);}// 测试打开的文件描述符cout << "pipefd[0]: " << pipefd[0] << endl;cout << "pipefd[1]: " << pipefd[1] << endl;// 2.创建子进程pid_t pid = fork();if(pid > 0){// 3.构建单向通行的信道,父进程写入,子进程读取// 3.1.父进程 -- 写// 关闭读端close(pipefd[0]);int count = 0;while(true){// 不断写如变化的信息string msg = "hello world" + to_string(count++);write(pipefd[1], msg.c_str(), msg.size());sleep(1);if(count > 5){cout << "write quit" << endl;break;}}// 关闭写端close(pipefd[1]);// 4.等待子进程pid_t wpid = waitpid(pid, nullptr, 0);if(wpid == -1){perror("waitpid");exit(3);}}else if(pid == 0){// 3.2.子进程 -- 读// 关闭写端close(pipefd[1]);// 不断读取信息char receive[128] = {0};while(true){ssize_t size = read(pipefd[0], receive, 127);if(size > 0){cout << "receive: " << receive << endl;}else if(size == 0) {cout << "write quit, read quit" << endl;break;}else{perror("read");exit(4);}}// 关闭读端close(pipefd[0]);}else {perror("fork");exit(2);}
}

在这里插入图片描述
通过匿名管道我们还可以模拟进程池的设计。

// 简单的进程池设计
#define PROCESS_NUM 5using f = function<void()>;
unordered_map<int, f> task;void load()
{task[1] = [](){cout << "sub process[" << getpid() << "]->void Task1()" << endl;};task[2] = [](){cout << "sub process[" << getpid() << "]->void Task2()" << endl;};task[3] = [](){cout << "sub process[" << getpid() << "]->void Task3()" << endl;};task[4] = [](){cout << "sub process[" << getpid() << "]->void Task4()" << endl;};
}void sendTask(int fd, pid_t pid, int task_num)
{write(fd, &task_num, sizeof(task_num));cout << "process[" << pid << "] execute " << "task" << task_num << " by " << fd << endl;
}int waitTask(int fd)
{int task_num = 0;ssize_t size = read(fd, &task_num, sizeof(task_num));if(size == 0){return 0;}if(size == sizeof(task_num)){return task_num;}return -1;
}void Test2()
{load();vector<pair<int, pid_t>> process;// 创建多个进程for(int i = 0; i < PROCESS_NUM; ++i){// 创建管道int pipefd[2] = {0};int ret = pipe(pipefd);if(ret != 0){perror("pipe");exit(1);}// 创建子进程pid_t pid = fork();if(pid == 0){// 子进程 -- 读close(pipefd[1]);while(true){// 等待任务int task_num = waitTask(pipefd[0]);if(task_num == 0){break;}else if(task_num >= 1 && task_num <= task.size()){task[task_num]();}else{perror("waitTask");exit(3);}}exit(0);}else if (pid < 0){perror("fork");exit(2);}// 父进程读端关闭close(pipefd[0]);process.emplace_back(pipefd[1], pid);}// 父进程 -- 写srand((unsigned int)time(0));while(true){// 选择一个进程 -- 随机数方式的负载均衡int process_num = rand() % process.size();// 选择一个任务// int task_num = rand() % task.size() + 1;int task_num = 0;cout << "please enter your task num: ";cin >> task_num;// 派发任务sendTask(process[process_num].first, process[process_num].second, task_num);}// 关闭fdfor(const auto& e : process){close(e.first);}// 回收子进程for(const auto& e : process){waitpid(e.second, nullptr, 0);}
}

在这里插入图片描述

命名管道

可以用mkfifo命令创建一个命名管道。如下图是一个命名管道的小实验。
在这里插入图片描述
也可以通过mkfifo接口进行命名管道文件的创建。
在这里插入图片描述
命名管道通信的测试。

// 1. log.hpp
#include <iostream>enum ErrLevel
{lev_0,lev_1,lev_2,lev_3,lev_4
};const std::string error[] = {"err_0","err_1","err_2","err_3","err_4"
};std::ostream& Log(const std::string& msg, int level)
{std::cout << " | " << (unsigned int)time(0) << " | " << error[level] << " | " << msg << " |";return std::cout;
}// 2. comm.hpp
#include <sys/types.h>
#include <sys/stat.h>
#include <wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstring>using namespace std;#include "log.hpp"#define MODE 0666
#define SIZE 128// 命名管道,通过文件路径,让不同进程能看到这同一份资源
string named_pipe_path = "/home/zs/linux/testcpp/fifo.ipc";// 3. server.cpp
static void getMsg(int fd)
{char buffer[SIZE];while(true){memset(buffer, '\0', sizeof(buffer));ssize_t size = read(fd, buffer, sizeof(buffer) - 1); // ssize_t - long intif(size > 0){cout << "[" << getpid() << "]" << "client say:" << buffer << endl;}else if(size == 0){cerr << "[" << getpid() << "]" << "read end of file, client quit, then server quit" << endl;break;}else{perror("read");break;}}
}void test()
{// 1.创建管道文件if(0 != mkfifo(named_pipe_path.c_str(), MODE)){perror("mkfifo");exit(1);}Log("创建管道文件成功", lev_0) << endl;// 2.文件操作int fd = open(named_pipe_path.c_str(), O_RDONLY);if(fd < 0){perror("open");exit(2);}Log("打开管道文件成功", lev_0) << endl;for(int i = 0; i < 3; ++i){pid_t pid = fork();if(pid == 0){// 3.通信getMsg(fd);exit(0);}}for(int i = 0; i < 3; ++i){waitpid(-1, nullptr, 0);}// 4.关闭文件close(fd);Log("关闭管道文件成功", lev_0) << endl;unlink(named_pipe_path.c_str()); // 通信完毕,删除管道文件Log("删除管道文件成功", lev_0) << endl;
}// 4. client.cpp
void test()
{// 1.获取管道文件int fd = open(named_pipe_path.c_str(), O_WRONLY);if(fd < 0){perror("open");exit(1);}// 2.通信string message;while(true){cout << "please enter your message: ";getline(cin, message);write(fd, message.c_str(), message.size());}// 关闭close(fd);
}

在这里插入图片描述

管道通信总结

  1. 管道常用来进行具有血缘关系的进程间的通信
  2. 管道让进程间协同,提供了访问控制
  3. 管道提供的是面向流式的通信服务
  4. 管道是基于文件的,文件的生命周期跟随进程,管道的生命周期也跟随进程
  5. 管道用于单向通信,属于半双工通信的一种特殊情况

管道本质是文件,又和传统的文件又不一样。管道文件不会将数据刷新到磁盘。
匿名管道通过父子继承的方式看到同一份资源,命名管道通过文件路径的唯一性看到同一份资源,从而达到不同进程间通信的目的。
对于管道文件:
如果写的一方很快,读的一方很慢,当管道写满时,写端必须等待;
如果写的一方很慢,读的一方很快,当管道没有数据时,读端必须等待;
如果写端先被关闭了,读端会读到文件结尾;
如果读端先被关闭了,操作系统会终止写端进程。

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

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

相关文章

【STM32】DMA(直接存储器访问)

一、DMA本质 在ADC中使用FIFO&#xff08;先进先出&#xff09;&#xff0c;当FIFO快满的时候&#xff0c;产生一个中断。在中断的时候将数据传输到SRAM&#xff0c;但是此时还是需要CPU的参与&#xff0c;但是CPU就不会一直在等待。【但是这个方法还是不能完全解决问题】 此时…

这个双11,阿里云经历了可能是历史级的大故障!

2023年11月12日17&#xff1a;44开始&#xff0c;阿里云发生严重故障&#xff0c;导致阿里巴巴大量产品无法连接&#xff0c;一时间&#xff0c;“阿里云盘崩了”、“淘宝又崩了”、“闲鱼崩了”、“钉钉崩了”等话题相继登上热搜。 此外&#xff0c;像纳思云充电桩、乐爽coole…

OSS服务和MinIO存储做一个区分解析

日落金山&#xff0c;明天我们继续… 什么是OSS服务和MinIO存储 OSS&#xff08;Object Storage Service&#xff09;和MinIO都是对象存储服务&#xff0c;但它们有一些区别。以下是对它们的简要分析&#xff1a; 1. 部署和管理&#xff1a; OSS&#xff1a; 由阿里云提供&a…

VPN创建连接 提示错误 628: 在连接完成前,连接被远程计算机终止。

提示错误 628: 在连接完成前&#xff0c;连接被远程计算机终止。 需要把这个地址配置一下&#xff1a; VPN类型&#xff1a;点对点PPTP 数据加密&#xff1a;如果没有加密的话&#xff0c; 要把这个选一下。

深入理解ResNet网络:实现与应用

Resnet 在深度学习领域&#xff0c;卷积神经网络&#xff08;CNN&#xff09;是一种非常重要的模型&#xff0c;它在图像识别、目标检测等领域取得了显著的成果。然而&#xff0c;随着网络层数的增加&#xff0c;梯度消失和梯度爆炸问题变得越来越严重&#xff0c;导致训练深层…

AI智剪:批量剪辑实战,技巧与实例

随着人工智能技术的不断发展&#xff0c;越来越多的领域开始应用AI技术提升工作效率和质量。其中&#xff0c;AI智剪技术在视频剪辑领域的应用也越来越广泛。AI智剪是一种基于人工智能技术的视频剪辑方法&#xff0c;通过机器学习算法对视频进行自动分析和处理&#xff0c;实现…

【2023春李宏毅机器学习】生成式学习的两种策略

文章目录 1 各个击破2 一步到位3 两种策略的对比 生成式学习的两种策略&#xff1a;各个击破、一步到位 对于文本生成&#xff1a;把每一个生成的元素称为token&#xff0c;中文当中token指的是字&#xff0c;英文中的token指的是word piece。比如对于unbreakable&#xff0c;他…

Linux CentOS7 添加网卡

一台主机中安装多块网卡&#xff0c;有许多优势。可以实现多项功能。 为了学习网卡参数的设置&#xff0c;可以为主机添加多块网卡。与添加磁盘一样&#xff0c;要在VMware中设置。利用图形化方式或命令行查看或设置网卡。本文仅作一初步讨论。有关网络参数的设置不在讨论之列…

2023年【四川省安全员A证】考试资料及四川省安全员A证考试试卷

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年四川省安全员A证考试资料为正在备考四川省安全员A证操作证的学员准备的理论考试专题&#xff0c;每个月更新的四川省安全员A证考试试卷祝您顺利通过四川省安全员A证考试。 1、【多选题】《建设工程安全生产管理…

让你的Mac体验更便捷,快速启动工具Application Wizard为你助力!

亲爱的Mac用户们&#xff0c;你是否经常感到在繁琐的软件启动过程中浪费了太多时间&#xff1f;你是否希望能够以更快的速度找到并启动你所需的应用程序&#xff1f;如果是的话&#xff0c;那么不要犹豫&#xff0c;让我们来介绍一款强大的软件快速启动工具——Application Wiz…

循环队列(出队、入队、判空、长度、遍历、取头)(数据结构与算法)

循环队列 涉及到移动、赋值原队列参数的函数参数列表如front&#xff0c;rear&#xff0c;都最好别用&引用&#xff0c;否则会修改原队列中的地址和数值如&#xff1a;SqQueue &Q 使用SqQueue Q作参数列表时&#xff0c;函数引入的只是一份副本&#xff0c;不会修改原队…

【GUI】-- 09 JComboBox JList、JTextField JPasswordField JTextArea

GUI编程 03 Swing 3.6 列表 下拉框 package com.duo.lesson06;import javax.swing.*; import java.awt.*;public class ComboBoxDemo01 extends JFrame {public ComboBoxDemo01() throws HeadlessException {Container contentPane getContentPane();JComboBox<Object&…