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

news2024/11/18 15:25:23

文章目录

  • 📕 进程间通信介绍
  • 📕 匿名管道
    • 原理
    • 使用
    • 读写规则
    • 特点
  • 📕 命名管道
    • 原理
    • 使用
    • 匿名管道和命名管道的区别

📕 进程间通信介绍

进程间通信,顾名思义,就是两个进程之间的 “交流” ,我们知道,进程是相互独立的,也就是说,正常情况下,两个进程之间无法传递消息,但是有时候又需要 进程间通信,如下,这是进程间通信的目的。

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

要达到这些目的,就需要使用一些技术来完成进程间通信。以下是三类技术,管道 、System V、POSIX 。本篇文章主要讲解管道的方法。

  • 管道
    • 匿名管道pipe
    • 命名管道
  • System V IPC
    • System V 消息队列
    • System V 共享内存
    • System V 信号量
  • POSIX IPC
    • 消息队列
    • 共享内存
    • 信号量
    • 互斥量
    • 条件变量
    • 读写锁

那么什么是管道呢?
管道是Unix中最古老的进程间通信的形式。我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。

听起来很抽象,可以了解一下管道的原理,就明白了!

📕 匿名管道

原理

如下图,一个进程可以打开多个文件,其 file_struct 里面的文件描述符 0、1、2 分别指向了内存里的 三个 struct file,在文件描述符那篇文章有提到,每一个 struct file 都有对应的缓冲区,是内存级别的。(要理解这里的内容,首先要了解文件描述符哦,可以看一下这篇文章:【Linux】文件描述符)

所以,我们可以用操作系统创建管道的系统调用,打开一个文件(下图中绿色部分),分别以 读、写 方式打开(具体原因下文会讲),然后这个打开的文件会对应有 struct file 和 缓冲区。这些都是操作系统的行为,所以即使磁盘上没有对应的文件,也可以这样打开。当不需要使用管道的时候,直接将内存里的对应 struct file 和缓冲区释放即可。

所以,管道就是一个妥妥的内存级的文件!!

请添加图片描述

此时,我们再 fork() ,产生一个子进程,子进程会继承父进程的绝大多数内容,包括 file_struct ,但是子进程并不会创建新的文件,因为这是属于文件系统范畴了, fork() 只是创建子进程。此时,子进程同样地也以读写的方式打开了父进程创建的管道文件
请添加图片描述

然后,由于管道是半双工的,所以要在父子进程里面,一个关闭读端,一个关闭写端,就可以完成进程间通信的基本条件。
比如这里,我需要父进程写入信息,子进程读取父进程的信息,那么就把父进程的读端关闭,子进程的写端关闭,这样子就是父进程写、子进程读!

请添加图片描述

使用

创建匿名管道需要用到 pipe() 系统调用。

头文件:#include <unistd.h>
功能:创建一个匿名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码

使用匿名管道需要注意几点,首先是要在创建子进程之前,创建管道。否则子进程无法继承父进程的文件描述符。
其次,不要忘记关闭读端或者写端。

#include <iostream>
#include <unistd.h>
#include <cerrno>
#include <string.h>
#include <stdio.h>

using std::cout;
using std::endl;

int main()
{
    int pipefd[2] = {0};
    // 1.创建管道
    int n = pipe(pipefd); // pipefd[0] 写端   pipefd[1] 读端
    if (n == -1)
    {
        cout << "create pipe error," << errno << strerror(errno) << endl;
        exit(1);
    }
    // cout<<pipefd[0]<<endl<<pipefd[1];

    // 2.创建子进程
    pid_t pid = fork();

    // 子进程
    if (pid == 0)
    {
        // 3.子进程写,那么关闭读端
        close(pipefd[0]);

        // 4.通信
        char buf[128];
        int cnt = 1;
        while (true)
        {
            snprintf(buf, sizeof buf, "我是子进程,cnt:%d ,pid: %d\n", cnt++, getpid());
            write(pipefd[1], buf, strlen(buf));
        }

        close(pipefd[1]); // 管道声明周期随进程,所以不手动关闭,进程结束后也会自动关闭
        exit(1);
    }

    // 父进程

    // 3.父进程读,关闭写端
    close(pipefd[1]);

    // 4.通信
    char buf[128];
    while (true)
    {
        sleep(3);
        int n = read(pipefd[0], buf, sizeof(buf) - 1); // 留出一个位置放\0
        if (n > 0)
        {
            buf[n]='\0';
            cout << "我是父进程,子进程给的信息:" << buf << endl;
        }
    }

    close(pipefd[0]);
    return 0;
}

上面的 写入、读取 代码可以看出,写入速度快,读写数据慢。 写入了很多次,才读取一次。但是下图运行结果,并不是写入一次,就读取一次,而是可以写入多次,然后一次性读取。

从这里可以看出,读写不是强相关的。(这里指的是读写次数的多少)

请添加图片描述

读写规则

  • 如果读端读取了管道内的所有数据,而写端不写入,那么只能等待。
  • 如果写端将管道写满了,读端没有读取数据,那么就无法写入。
  • 如果写端关闭,读端依然打开,在读取完管道内剩余的数据之后,再次读取数据,则 read 返回0。
  • 如果读端关闭,而写端还向管道写入数据,毫无疑问这是没有意义的,操作系统不会运行这样的事情发送。所以,write 操作会产生信号SIGPIPE,进而可能导致write进程退出。

特点

  1. 单向通信(半双工)
  2. 管道的本质是文件,因为 fd 的生命周期随进程,所以管道的生命周期也是随进程的
  3. 管道通信,通常用来 对具有“血缘”关系的进程,进行进程间通信。常用于父子通信 – pipe 打开管道,并不清楚管道的名字(匿名管道)。
  4. 在管道通信中,写入的次数 和 读取的次数是不严格匹配的,读写次数的多少没有强相关,读取是按照字节流来读取的。
  5. 根据上面的读写规则,可以知道,管道具有一定的协同能力,能让 reader 和 writer 按照一定的步骤进行通信——自带同步机制。

📕 命名管道

原理

如下,一个进程打开了磁盘上的一个文件。当另一个进程(和之前的进程没有血缘关系),也打开同一个文件的时候,操作系统不会重新创建一个 struct file 对象,而是直接使用之前的。

那么,此时,两个进程就看到了同一个文件,也就可以进行进程间通信。
但是,如果这个文件是普通文件,那么数据会定期刷新的磁盘里。可是如果我们要进行进程间通信,数据就不应该被刷新到磁盘里面,而是在进程之间进行传输。所以,这就要求创建的文件是一个内存级别的文件,由此诞生了命名管道文件!不需要维护其 datablock,只需要告诉操作系统,这个文件存在!以后 两个进程可以打开这个文件,打开文件就会在内核匹配对应的缓冲区,所以两个进程就看到同一份资源!!

这个原理和上面匿名管道的原理其实是一样的!!
在这里插入图片描述

使用

命名管道可以从命令行上创建,命令行方法是使用下面这个命令:

  • $ mkfifo filename

命名管道也可以从程序里创建,相关函数有:

  • int mkfifo(const char *filename,mode_t mode);

如下是在程序里面实现命名管道,以及进程间通信。 server 进程和 client 进程进行交互。

comm.hpp 头文件

#pragma once

#include<iostream>
#include<string>

#define NUM 1024

const std::string filename="./fifo";

mode_t mode=0666;

server.cc 文件


#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include<fcntl.h>
#include <cerrno>
#include <cstring>
#include<unistd.h>

#include"comm.hpp"

using std::cin;
using std::cout;
using std::endl;

int main()
{
    // 1.创建命名管道
    umask(0);
    int n = mkfifo(filename.c_str(),mode);
    if(n < 0)
    {
        cout<<"create fifo error:"<<errno<<":"<<strerror(errno)<<endl;
        return 1;
    }
    cout<<"create fifo success"<<endl;

    // 2.开启管道文件
    int rfd=open(filename.c_str(),O_RDONLY);
    if(rfd < 0)
    {
        cout<<"open fifo file error:"<<errno<<":"<<strerror(errno)<<endl;
        return 1;
    }
    cout<<"open fifo file success"<<endl;

    // 3. 开始通信
    char buf[NUM];

    while(true)
    {
        // 先将缓冲区置0
        buf[0]=0;
        size_t n=read(rfd,buf,sizeof(buf)-1); // 读取是按字节流,所以去掉 \0
        if(n > 0)
        {
            buf[n]='\0';
            cout<<"client#"<<buf<<endl;
            fflush(stdout);
        }
        else if(n==0)
        {
            cout<<"client quit,i will quit either"<<endl;
            break;
        }
        else{
            cout<<errno<<":"<<strerror(errno)<<endl;
        }
    }

    close(rfd);
    unlink(filename.c_str());

     return 0;
}

client.cc 文件


#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include<cassert>

#include "comm.hpp"

using std::cin;
using std::cout;
using std::endl;

int main()
{
    // 打开fifo
    int wfd=open(filename.c_str(),O_WRONLY);
    if(wfd < 0)
    {
        cout<<"open fifo error"<<errno<<":"<<strerror(errno)<<endl;
        return 1;
    }
    cout<<"open fifo success"<<endl;

    // 写入
    char buf[NUM];
    while(true)
    {
        cout<<"请输入你的信息#";
        char* msg=fgets(buf,sizeof(buf),stdin); // C库的函数,会默认加上 \0
        assert(msg);
        (void*)msg;

        size_t n=write(wfd,buf,sizeof(buf)-1);
        if(strcasecmp(buf,"quit") == 0) break;

    }
    close(wfd);
    return 0;
}

如下,server 是读端, client 是写端,只有 client 写入消息, server 才会读取,实现进程间通信。
如果单单在 server 写入消息, client 是不会读取的,因为 server是读端, client 是写端。
在这里插入图片描述

匿名管道和命名管道的区别

  • 匿名管道由pipe函数创建并打开。
  • 命名管道由mkfifo函数创建,打开用open
  • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。

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

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

相关文章

Alibaba开发十年,写出这本“MQ技术手册”,看完我愣住了

前言 消息队列已经逐渐成为企业IT系统内部通信的核心手段。它具有低耦合、可靠投递、广播、流量控制、最终一致性等一系列功能&#xff0c;成为异步RPC的主要手段之一。虽然说&#xff0c;目前状况是Kafka更为火热&#xff0c;但更为广泛的应该还属老牌的RabbtiMQ和Alibaba自主…

Android WebRtc+SRS/ZLM视频通话(2):安装SRS

Android WebRtcSRS/ZLM视频通话&#xff08;2&#xff09;&#xff1a;安装SRS 来自奔三人员的焦虑日志 接着上一章内容&#xff0c;继续来安装开源流媒体系统&#xff08;SRS&#xff09;&#xff0c;可以按官方教程或者直接问ChatGPT安装教程&#xff0c;又或者百度一下照着大…

别开玩笑了!特种兵式旅游,胆小勿进!

【大学生特种兵旅游】是什么梗&#xff1f; 最近在多御浏览器上看新闻的时候看到一个热梗【大学生特种兵旅游】这又是什么梗&#xff01;&#xff01;&#xff01;老阿姨震惊了&#xff01; 我随即搜索了一下&#xff0c;看到了下面这段介绍&#xff1a; 近日&#xff0c;“特种…

【SpringBoot集成Nacos+Dubbo】企业级项目集成微服务组件,实现RPC远程调用

文章目录 一、需求环境/版本 二、须知2.1、什么是RPC&#xff1f;2.2、什么是Dubbo&#xff1f;2.3、什么是Nacos&#xff1f; 三、普通的SpringBoot项目集成微服务组件方案&#xff08;笔者给出两种&#xff09;方案一&#xff08;推荐&#xff09;1、导入maven依赖&#xff0…

rocketmq4.9.4 docker安装

rocketmq4.9.4 给对应的路径赋权 chmod -R 777 文件名 不然启动可能报错后者看不到容器日志 systemctl status firewalld 查看防火墙状态 https://www.jianshu.com/p/0c1c3c679ef8 Docker部署RocketMQ&#xff08;4.9.4&#xff09;官方镜像和控制台windows、mac、linux全平…

20230508在Ubuntu22.04下使用python3批量转换DOCX文档为TXT

20230508在Ubuntu22.04下使用python3批量转换DOCX文档为TXT 2023/5/8 16:27 在WIN10下请参考本文&#xff0c;在Ubuntu22.04下需要不通的插件&#xff01; https://blog.csdn.net/weixin_46255747/article/details/129961988 python实现批量docx转txt docx文档放到input目录中。…

Origin如何绘制二维图形?

文章目录 0.引言1.函数绘图2.线图3.符号图4.点线符号图5.柱状/条形/饼图6.多面板/多轴图7.面积图8.专业图9.主题绘图 0.引言 因科研等多场景需要绘制专业的图表&#xff0c;笔者对Origin进行了学习&#xff0c;本文通过《Origin 2022科学绘图与数据》及其配套素材结合网上相关资…

stable diffusion模型讲解

AI模型最新展现出的图像生成能力远远超出人们的预期&#xff0c;直接根据文字描述就能创造出具有惊人视觉效果的图像&#xff0c;其背后的运行机制显得十分神秘与神奇&#xff0c;但确实影响了人类创造艺术的方式。 AI模型最新展现出的图像生成能力远远超出人们的预期&#xf…

PyCharm使用 Anaconda安装TensorFlow

1.安装python全家桶Anaconda 1.1 官网 https://www.anaconda.com/ 进入官网后如下图所示&#xff0c;点击Download即可开始下载&#xff08;若无法下载&#xff0c;请转至清华源下载&#xff09; 1.2 清华 https://repo.anaconda.com/archive/ 2.Anaconda安装 点击Next -…

libssh2交叉编译和测试

目录 官方地址&#xff1a;https://www.libssh2.org/ 1.源码下载 2.交叉编译 3.测试代码 官方地址&#xff1a;https://www.libssh2.org/ 正常来说&#xff0c;看官网说明和例子都能正常编译和使用&#xff0c;想偷个懒的就参考以下步骤。 1.源码下载 我当前看到的版本是li…

【二分查找】求解单调方程的解 C++实现

目录 1 问题2 想法3 二分查找4 实现4-1 伪代码说明4-2 C11 1 问题 有函数 f ( x ) a x ( a > 1 ) f(x)a^x(a>1) f(x)ax(a>1) ,单调递增&#xff0c;现在给一个正整数 N N N&#xff0c;求使得 f ( x ) N f(x)N f(x)N的正整数解 x x x。    2 想法 x l o g a N xl…

Spring Boot集成ShardingSphere分片利器 AutoTable (二)—— 自动分片算法示例 | Spring Cloud 46

一、前言 在前面我们通过以下章节对ShardingSphere的AutoTable 有了基础的了解&#xff1a; Spring Boot集成ShardingSphere分片利器 AutoTable &#xff08;一&#xff09;—— 简单体验 | Spring Cloud 45 书接上回&#xff0c;本章进行对AutoTable 支持的自动分片算法进行…

【JAVAEE】使用wait()方法和notify()方法解决线程不安全中的有序性问题

目录 1.wait()方法 2.notify()方法 3.notifyAll()方法 4.wait()和sleep()方法的区别 由于线程之间是抢占式执行的&#xff0c;因此线程之间执行的先后顺序难以预知。但是在实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序。 完成这个协调工作&#xff0c;主…

QML之HTML5画布移植(Porting from HTML5 Canvas)

移植一个HTML5画布图像到QML画布非常简单。在成百上千的例子中&#xff0c;我们选择了一个来移植。 螺旋图形&#xff08;Spiro Graph&#xff09; 我们使用一个来自Mozila项目的螺旋图形例子来作为我们的基础示例。原始的HTML5代码被作为画布教程发布。 下面是我们需要修改…

OpenGL(十)——基础光照

目录 一、前言 二、环境光照 三、漫反射光照 3.1 法向量 3.2顶点着色器 3.3 VAO属性解释 3.4 片段着色器 四、镜面光照 4.1 片段着色器 一、前言 现实世界光照十分复杂&#xff0c;冯氏光照模型是对现实世界光照的抽象&#xff0c;主要由3部分组成&#xff0c;环境amb…

【JAVAEE】使用synchronized关键字和volatile关键字解决线程安全问题中的原子性,内存可见性和有序性问题

目录 1.synchronized关键字---监视器锁monitor lock 1.1synchronized的特性 互斥 刷新内存 可重入 1.3synchronized使用注意事项 2.volatile关键字 2.1volatile保证内存可见性问题 MESI缓存一致性协议 内存屏障 2.2volatile解决有序性问题 3.总结synchronized和vola…

ELK -- kibana 用nginx代理后无法访问

背景&#xff1a; 本地搭建好elk后&#xff0c;一切正常&#xff0c;后面改成用nginx代理kibana的5601端口&#xff0c;发现代理后无法正常访问&#xff08;未代理的地址可正常访问&#xff09;&#xff0c;花了很多时间去查问题&#xff0c;报错基本都是http://ip:port/spaces…

Leetcode刷题之复制带随机指针的链表

生命不是安排&#xff0c;而是追求&#xff0c;人生的意义也许永远没有答案&#xff0c;但也要尽情感受这种没有答案的人生。 --弗吉尼亚. 伍尔芙 目录 前言&#xff1a; &#x1f338;一.复制带随机指针的链表 &#x1f305;1.复制结点链接到原本链表每一个结点的…

24个强大的HTML属性,每个资深Web工程师都应该掌握!

HTML 属性非常多&#xff0c;除了基本的一些属性外&#xff0c;还有很多很有用的功能性特别强大的属性&#xff1b; 本文将介绍24个强大的HTML属性&#xff0c;这些属性可以让你的网站更加动态和交互&#xff0c;让用户感到更加舒适和愉悦。 让我们一起来探索这24个强大的HTML…