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

img

Halo,这里是Ppeua。平时主要更新C语言,C++,数据结构算法…感兴趣就关注我吧!你定不会失望。

本篇导航

  • 0. 进程间通信原理
  • 1. 匿名管道
    • 1.1 通信原理
    • 1.2 接口介绍
  • 2. 命名管道
    • 2.1 接口介绍
  • 3. 共享内存
    • 3.1 通信原理
    • 3.2 接口介绍

在这里插入图片描述

0. 进程间通信原理

进程间的是相互独立的。那么想要让两个进程间进行通信,本质是让其看到同一份资源。因为进程具有独立性,所以大多时候让两个或多个进程看到同一份资源是最费力的

根据看到资源的方式不同,将进程通信划分为以下几种:

  1. 匿名管道通信
  2. 命名管道通信
  3. 共享内存

其中,匿名管道通信与命名管道通信的本质都是让进程看到同一份内存级文件

内存级文件是一个仅存储在内存中的文件.不会刷新到磁盘中

1. 匿名管道

1.1 通信原理

管道实际上是一份内存级文件,其被创建出来,通过文件的方式去访问.

内存模型如下:

image-20231211213419971

其中file_r为写缓冲区,file_w为读缓冲区(缓冲区本质上也为一个内存级文件)

创建管道时,系统会为其分配两个fd.一个为读端,一个为写端.但是 管道只能进行单向通信.

为了方便控制,通常情况下,我们会手动关闭我们不需要的那个fd.(以下为了方便测试,规定由父进程写读,子进程写)

那么在匿名管道通信时如何让多个进程看到同一份内存级文件呢?

子进程会继承父进程的大多数资源,file_struct也在其中.但因为操作系统节省资源的特性,文件并不会被创建多份

所以可以通过创建子进程的方法来让多个进程看到同一份资源

所以 匿名管道的特点之一:仅能在有关系的进程中进行通信(父子进程,兄弟进程)

创建一个子进程时,内存模型如下:

image-20231211214629837

(通信本质是让不同进程看到同一份资源,所以资源的准备需要在进程创建之前!!!)

此时子进程也能够访问这个内存级文件了.这时双方就可以根据fd,按照访问文件的方式去访问这个内存级文件.也就是可以进行通信

1.2 接口介绍

创建匿名管道使用的函数为 int pipe(int pipefd[2])

image-20231211215326099

image-20231211215432856

其中 **int pipefd[2]**为输出型参数 pipefd[0]为读端,pipefd[1]为写端

该接口创建完管道,并为用户分配所需读写端的fd,将其存入该数组后返回给用户.

如果创建成功则返回0,如果创建失败则返回-1,同时设置errno

这是一份简单的管道通代码.创建管道需要在创建子进程前才能被共享到!

#include<iostream>
#include<unistd.h>
#include<cstdio>
#include<sys/types.h>
#include<sys/wait.h>
#include<string>
using namespace std;
#define N 2
#define NUM 1024

void Read(int rfd)
{
    
    while (true) {
        char buffer[1024];
        int n=read(rfd,buffer,sizeof(buffer));
        if(n<=0)
        {
            cout<<"wait write into pipe"<<endl;
        }
        else {
            cout<<buffer;
        }
        
    }
}
void Write(int wfd)
{
    string example="i am a child,hello linux communitate  ";
    pid_t self=getpid();
    example+=to_string(self);
    int flag=example.size();
    int cnt=0;
    while(true)
    {
        example.erase(flag);
        example+=" "+to_string(cnt++)+"\n";
        int n=write(wfd,example.c_str(),example.length());
        if(n<=0)
        {
            cout<<"pipe close"<<endl;
            break;
        }
        sleep(1);
    }
}

int main()
{
    int pipefd[2];
    pipe(pipefd);
    if(pipe(pipefd)<0)
    {
        perror("create pipe failed\n");
    }
    cout<<"0: "<<pipefd[0]<<endl;
    cout<<"1: "<<pipefd[1]<<endl;
    pid_t id = fork();
    if(id==0) //0 read 1 write
    {
        //write
        close(pipefd[0]);
        Write(pipefd[1]);
    }
    else {
        //read
        close(pipefd[1]);
        Read(pipefd[0]); 
    }
    return 0;
}

image-20231211220606452

完成了匿名管道的通信.

管道通信为单向的.读端会将读取的内容从管道中取走.先写入的数据会被先取走(与队列的原理相似)


父进程会随着子进程发送信息的频率而读取信息.(上文写端进行了休眠,而读端并没有)

所以:

读写端正常.当管道中没有内容时,读端会阻塞等待


如果我们重复的写入一段内容而不读取呢?

void Write(int wfd)
{
    string example="i am a child,hello linux communitate  ";
    pid_t self=getpid();
    example+=to_string(self);
    int flag=example.size();
    int cnt=0;
    while(true)
    {
        example.erase(flag);
        example+=" "+to_string(cnt++)+"\n";
        int n=write(wfd,example.c_str(),example.length());
        if(n<=0)
        {
            cout<<"pipe close"<<endl;
            break;
        }
       	cout<<cnt<<endl;;
    }
}

将写端逻辑做出如上更改,当写不进去时,输出 “pipe full”;

void Read(int rfd)
{
    while(true){};
    while (true) {
        char buffer[1024];
        int n=read(rfd,buffer,sizeof(buffer));
        if(n<=0)
        {
            cout<<"wait write into pipe"<<endl;
        }
        else {
            cout<<buffer;
        }
        
    }
}

将读端做出如上更改.手动阻塞进程

image-20231211222740715

观察到写端阻塞,等待读端读取

所以

读写端正常.当管道写满时,写端会阻塞等待读端读取


将写端设置为一段时间后自动关闭.读端不会被阻塞.read返回0,可以根据这个特性做出行为

所以

写端被关闭,读端读到文件结尾,返回0.但此时不会被阻塞


将读端设置为一段时间后自动关闭.为了节省资源.写端将被操作系统关闭

void Read(int rfd)
{
    int cnt=5;
    while (cnt>0) {
        char buffer[1024];
        int n=read(rfd,buffer,sizeof(buffer));
        if(n<=0)
        {
            cout<<"wait write into pipe"<<endl;
        }
        else {
            cout<<buffer;
        }
        cnt--;
    }
    cout<<"read close"<<endl;
}
//main 中修改的部分
 //read
        close(pipefd[1]);
        Read(pipefd[0]);
        close(pipefd[0]);
        int status=0; 
        waitpid(id,&status,0);
        cout<<"receive signal : "<<(status& 0x7f)<<endl;

image-20231212125908227

收到13号信号,进程被终止.13号信号为SIGPIPE

image-20231212125942952

所以

读端被关闭.写端也被关闭


综上,匿名管道通信时有四种情况:

  1. 读写端正常.当管道中没有内容时,读端会阻塞等待
  2. 读写端正常.当管道写满时,写端会阻塞等待读端读取
  3. 写端被关闭,读端读到文件结尾,返回0.但此时不会被阻塞
  4. 读端被关闭.节省资源.写端也被关闭

所以我们可以得到匿名管道有以下特征:

  1. 具有血缘关系的进程才可以进行通信
  2. 管道只能单向通信
  3. 父子进程是会进程协同的,同步与互斥
  4. 管道是面向字节流的
  5. 管道基于内存级文件.其生命周期随进程

2. 命名管道

与匿名管道大同小异.都是基于文件级的通信,但是命名管道在指定路径下创建了一个具有名称的内存级文件.

这使得 没有血缘关系的进程也能够看到同一份资源,所以此时,不同的进程也可以进行通信了

2.1 接口介绍

我们可以使用mkfifo 依照创建文件的方法,在指定目录下创建出内存级文件.

image-20231212131141204

其表示文件属性的权限位,显示其为一个管道文件.

删除这个管道文件我们通常使用unlink

image-20231212131253908

在语言层面上,也为我们封装了该接口

image-20231212131352469

pathname:为指定路径 mode:为权限

#pragma once
#include <sys/stat.h>
#include <unistd.h>
#include"log.hpp"
#define FIFO_PATH "./myfifo"
class InitPipe{
public:
    InitPipe()
    {
        int n=mkfifo(FIFO_PATH,MODE);
        if(n!=0)
        {
            log(FATAL,"create pipe failed");
            exit(0);
        }
    }
    ~InitPipe()
    {
        unlink(FIFO_PATH);
    }
};

接口使用

为了避免每次退出进程时,还要去手动释放该管道文件.所以利用RAII的方式来存储管道文件

之后使用该管道时,根据读写文件那一套来即可.

sever.cpp:

#include"log.hpp"
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "create_pipe.hpp"
int main(int argc,char * argv[])
{
    InitPipe pipe;
    Log log(SCREEN|CLASS_FILE);
    
    int fd=open(FIFO_PATH,O_RDONLY);
    if (fd < 0)
    {
        log(FATAL, "error string: %s, error code: %d", errno, errno);
    }
    while(true)
    {
        char sz[1024]={0};
        int x=read(fd,sz,sizeof(sz));
        if(x > 0)
        {
            cout<<"client say# "<< sz <<endl;
            log(INFO,sz);
        }
        else if(x==0)
        {
            
            break;
        }
        else break;
    }
 
}

client.cpp:

#include <fcntl.h>
#include<unistd.h>
#include<iostream>
#include<string>
using namespace std;
int main()
{
    int fd=open("./myfifo",O_WRONLY);
    if (fd < 0)
    {
        cout<<"failed"<<endl;
        exit(0);

    }
    string s1;
    while(true)
    {
        cout<<"client say @";
        getline(cin,s1);
        write(fd,s1.c_str(),s1.length());
    }
    
}

image-20231212132046069

(上文用到的log可以查看我下一篇博客的日志插件)

3. 共享内存

共享内存相较于前两种通信方式,速度上有明显的优势.

其不会涉及到复制写入/读取的内容.而是直接写入内存.而管道是将内容复制到文件当中,在复制出来

3.1 通信原理

共享内存的本质就是将一段真实的 物理内存,映射到PCB的共享内存当中.当多个进程映射同一个物理内存时,通过对内存数据直接的读写,就可以实现通信.

同样,我们需要使用系统调用接口去申请这段共享内存.使用系统调用接口去释放这段共享内存

共享内存没有像管道一样的同步互斥,需要用户自己去规定.

3.2 接口介绍

申请共享内存:

image-20231212133159479

key:可以理解为申请共享内存的一段密钥,该密钥在系统中是唯一的,就可以申请到唯一的一块共享内存,也是系统内核去校验两个共享是否相同的一个手段

通过ftok去申请:

image-20231212133445721

该函数是一个算法结合两个参数去生成一个唯一的key.所以这两个参数可以根据使用情况去定制.

若申请成功,则返回key,若申请失败则返回-1,并设置errno.

size:为申请的共享内存大小,一般为4096的整数倍.

shmflg具有以下两个值:

  1. IPC_CREAT (申请一段共享内存,若不存在则创建并返回shmid,若存在则返回shmid(用户级的key))
  2. IPC_CREAT | IPC_EXEL| 八进制权限信息 (申请一段共享内存,若不存在则创建并返回shmid,若存在则创建失败) 需要带上权限信息

为什么会有第二个选项呢?用来保证你申请的共享内存是一段新的,唯一被您使用的内存

shmid是什么?与上文的key类似,内核使用key去操作控制共享内存,而用户通过shmid完成如上操作

返回值为 成功返回shmid,失败返回-1,并设置errno

image-20231212134537899

获取共享内存地址.

char * address = (char *)shmat(shmid,nullptr,0);

image-20231212220619538

**取消挂接该地址.**若成功则返回0,失败返回-1

image-20231212220746275

对该共享内存进行控制,一般用来删除共享内存

cmd参数填上IPC_RMID 表示删除当前内存

下面是一个简单的示例demo:

config.hpp

#pragma once
#include <cerrno>
#include <cstring>
#include <sys/ipc.h>
#include<sys/types.h>
#include <sys/shm.h>
#include<stdlib.h>
#include<iostream>
using namespace std;
string PATH= "/tmp";
int MODE= 255;
#define SIZE 4096
class Init{
public:
    Init(){
        _key=ftok(PATH.c_str(), MODE);
        if(_key==-1)
        {
            cout<<"create failed"<<endl;
            strerror(errno);
            exit(0);
        }
    }
    int CreateShm()
    {
        //需要加上权限 否则创建失败
        int shmid=shmget(_key,SIZE,IPC_CREAT|IPC_EXCL|0666);
        if(shmid==-1)
        {
            cout<<"creatshm failed"<<endl;
            strerror(errno);
            exit(0);
        }
        return shmid;
    }
    int GetShm()
    {
        int shmid=shmget(_key,SIZE,IPC_CREAT|0666);
        if(shmid==-1)
        {
            cout<<"getshm failed"<<endl;
            strerror(errno);
            exit(0);
        }
        return shmid;
    }
    void destoryShmid(int shmid)
    {
        if(shmctl(shmid,IPC_RMID, nullptr)!=-1)
            cout<<"destory success";
    }
    
private:
    key_t _key;
};

processaa.cpp 接受方

#include "config.hpp"
#include <cstddef>
#include <sys/shm.h>
#include<iostream>
using namespace std;
int main()
{
    Init it;
    int shmid=it.GetShm();
    cout<<"get success a"<<endl;
    char * address = (char *)shmat(shmid,nullptr,0);
    while(true)
        cout<<address<<endl;
}

processbb.cpp 发送方

#include "config.hpp"
#include<iostream>
#include <cstddef>
using namespace std;
int main()
{
    Init it;
    int shmid=it.CreateShm();
    cout<<"create success b"<<endl;
    char * address = (char *)shmat(shmid,nullptr,0);
    cout<<"address success "<<endl;
    int cnt=5;
    while (cnt-->0) {
        fgets(address,4096,stdin);

    }
    it.destoryShmid(shmid);    
}

image-20230905164632777

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

前端框架学习 Vue(3)vue生命周期,钩子函数,工程化开发脚手架CLI,组件化开发,组件分类

Vue 生命周期 和生命周期的四个阶段 Vue生命周期:一个Vue实例从创建 到 销毁 的整个过程 生命周期四个阶段 :(1)创建 (2)挂载 (3)更新 (4)销毁 Vue生命周期函数(钩子函数) Vue生命周期过程中,会自动运行一些函数,被称为[生命周期钩子] ->让开发者可以在[特定阶段] 运行自…

互联网加竞赛 基于深度学习的水果识别 设计 开题 技术

1 前言 Hi&#xff0c;大家好&#xff0c;这里是丹成学长&#xff0c;今天做一个 基于深度学习的水果识别demo 这是一个较为新颖的竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f9ff; 更多资料, 项目分享&#xff1a; https://gitee.com/dancheng-senior/pos…

利用jmeter完成简单的压力测试

Jmeter是一个非常好用的压力测试工具。Jmeter用来做轻量级的压力测试&#xff0c;非常合适&#xff0c;只需要十几分钟&#xff0c;就能把压力测试需要的脚本写好。 1、什么是压力测试 顾名思义&#xff1a;压力测试&#xff0c;就是 被测试的系统&#xff0c;在一定的访问压…

IDEA新建文件夹后右击不能创建class类排错方法

目录 1 查看自身文件名是否为关键词 2 查看是否被“蓝色文件夹”给包含了 3 检查设置那边的class模板 4 报错解决 1 查看自身文件名是否为关键词 如下使用了 Java中的关键词"class"所以才无法创建包 ---------------------------------------------------------…

用Python Tkinter打造的精彩连连看小游戏【附源码】

文章目录 连连看小游戏&#xff1a;用Python Tkinter打造的精彩游戏体验游戏简介技术背景MainWindow类:职责:方法:Point类: 主执行部分:完整代码&#xff1a;总结&#xff1a; 连连看小游戏&#xff1a;用Python Tkinter打造的精彩游戏体验 在丰富多彩的游戏世界中&#xff0c…

C++学习Day03之构造函数的分类以及调用

目录 一、程序及输出1.1 构造函数的分类1.2 构造函数的调用1.2.1 括号法1.2.2 显式法1.2.3 隐式法 二、分析与总结 一、程序及输出 1.1 构造函数的分类 #include<iostream> using namespace std;//构造函数分类 //按照参数分类&#xff1a; 无参构造(默认构造函数) 和 …

Redis -- set集合

挑战自己&#xff0c;每天进步一点点&#xff0c;成就将属于不停止脚步的你。 目录 Redis集合&#xff1f; 集合基本命令 sadd smembers sismember scard spop srandmember smove srem 集合间操作 sinter sinterstore sunion sdiff sdiifstore Redis集合&#…

RabbitMQ——基于 KeepAlived + HAProxy 搭建 RabbitMQ 高可用负载均衡集群

一、集群简介 1.1 集 群架构 当单台 RabbitMQ 服务器的处理消息的能力达到瓶颈时&#xff0c;此时可以通过 RabbitMQ 集群来进行扩展&#xff0c;从而达到提升吞吐量的目的。 RabbitMQ 集群是一个或多个节点的逻辑分组&#xff0c;集群中的每个节点都是对等的&#xff0c;每…

C2-Search-Netlas:一款基于Netlas API的强大C2服务器识别与检测工具

关于C2-Search-Netlas C2-Search-Netlas是一款功能强大的命令与控制&#xff08;C2&#xff09;服务器检测工具&#xff0c;该工具使用Java语言开发&#xff0c;基于Netlas API实现其功能&#xff0c;可以帮助广大研究人员轻松快速地识别和检测目标C2服务器的相关信息。 C2-S…

python Flask 写一个简易的 web 端程序(附demo)

python Flask 写一个简易的 web 端程序 &#xff08;附demo&#xff09; 介绍简单介绍装饰器 app.route("/") 进阶增加接口设置端口 静态网页核心代码完整代码 介绍 Flask 是一个用于构建 Web 应用程序的轻量级 Python Web 框架。它设计简单、易于学习和使用&#x…

CNN应用Keras Tuner寻找最佳Hidden Layers层数和神经元数量

介绍&#xff1a; Keras Tuner是一种用于优化Keras模型超参数的开源Python库。它允许您通过自动化搜索算法来寻找最佳的超参数组合&#xff0c;以提高模型的性能。Keras Tuner提供了一系列内置的超参数搜索算法&#xff0c;如随机搜索、网格搜索、贝叶斯优化等。它还支持自定义…

力扣刷题之旅:启程篇(四)

力扣&#xff08;LeetCode&#xff09;是一个在线编程平台&#xff0c;主要用于帮助程序员提升算法和数据结构方面的能力。以下是一些力扣上的入门题目&#xff0c;以及它们的解题代码。 --点击进入刷题地址 1.寻找旋转排序数组中的最大值 题目描述&#xff1a; 给定一个旋…

acwing869. 试除法求约数870. 约数个数AcWing871. 约数之和872. 最大公约数

869. 试除法求约数 思路&#xff1a; 约数和质数的求解有着共性&#xff0c; 就是都是使用 for (int i 1; i < n/i; i) 进行计算的。这样的原因是因为约数必然也是两两一组&#xff0c; 那么我们求出小的自然也就知道另一个&#xff0c;只要再判断一下n/i和i是否相同&a…

项目安全问题及解决方法-----xss处理

XSS 问题的根源在于&#xff0c;原本是让用户传入或输入正常数据的地方&#xff0c;被黑客替换为了 JavaScript 脚本&#xff0c;页面没有经过转义直接显示了这个数据&#xff0c;然后脚本就被 执行了。更严重的是&#xff0c;脚本没有经过转义就保存到了数据库中&#xff0c;随…

ReactNative实现文本渐变

我们直接上图&#xff0c;可以看到上面文本的效果&#xff0c;使用SVG实现 1.首先还是要引入react-native-svg库 2.使用该库下面的LinearGradient和Text 好&#xff0c;话不多说&#xff0c;我们看具体代码 <Svg width{422} height{30} viewBox{0 0 422 30}><Defs&…

力扣 第 383 场周赛 解题报告 | 珂学家 | Z函数/StringHash

前言 谁言别后终无悔 寒月清宵绮梦回 深知身在情长在 前尘不共彩云飞 整体评价 T3是道模拟题&#xff0c;但是感觉题意有些晦涩&#xff0c;T4一眼Z函数&#xff0c;当然StringHash更通用些。 新年快乐, _. T1. 将单词恢复初始状态所需的最短时间 I 思路: 模拟 就是前缀和为…

构建高效直播美颜系统:美颜SDK集成与性能优化指南

如今&#xff0c;美颜技术的广泛应用成为各类直播平台的标配之一。今天&#xff0c;小编将与大家进一步讨论如何构建高效的直播美颜系统&#xff0c;重点关注美颜SDK的集成和性能优化方面。 一、美颜SDK的选择与集成 选择合适的美颜SDK是构建高效直播美颜系统的第一步。不同的…

速过计算机二级python——第六讲:文件操作

第六讲:文件操作 文件夹创建文件夹移动文件夹复制文件夹删除文件夹文件操作文件读取文件写入文件文件夹 创建文件夹 定义创建文件夹函数:chmk_path()定义一个函数 chmk_path(),这个函数的功能是创建文件夹。 首先需要导入操作系统接口模块——os 模块,这个模块中包含某些函…

基于单片机控制的智能门锁设计

摘要&#xff1a;阐述基于STC15F2K60S2单片机控制的智能门锁设计&#xff0c;包括CPU控制单元模块、液晶显示LCD、 Wi-Fi模块&#xff0c;实现远程控制开门&#xff0c;密码开门的智能化功能。 关键词&#xff1a;控制技术&#xff0c;单片机&#xff0c;智能门锁&#xff0c;…

cesium-测量高度垂直距离

cesium做垂直测量 完整代码 <template><div id"cesiumContainer" style"height: 100vh;"></div><div id"toolbar" style"position: fixed;top:20px;left:220px;"><el-breadcrumb><el-breadcrumb-i…