管道通信详解

news2024/11/24 20:55:29

目录

 

一、进程通信原理

二、什么是管道

三、创建一个匿名管道

四、fork共享管道的原理

五、管道的特点

六、4中场景

 七、命名管道

八、命名管道通信的原理

九、创建一个命名管道

十、上实例


 

一、进程通信原理

我们知道进程间相互独立,具有独立性。那么我们要实现两个进程之间的通信就需要,让这两个进程看到同一个文件。然后一个进程对文件写入,一个进程对文件内容进行读取,这就是现实了进程间的通信。

二、什么是管道

管道是进程间通信的一种方式。

进程间通信的方式:

管道:

  • 匿名管道
  • 命名管道

System V

  • System V 消息队列
  • System V 贡献内存
  • System V 信号量

POSIX IPC

  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

我们把一个进程连接到另外一个进程的数据流成为一个“管道”

Linux中的|就是管道。

三、创建一个匿名管道

#include<unistd.h>

int pipe(int fd[2]);
//fd:文件描述符数组
//返回值:成功返回0,失败返回错误代码。

 此时就创建除了一个有读写权限的数据流。这里只有一个进程,管道还无法实现通信,下文会自习讲解。

四、fork共享管道的原理

这就是实现一个管道通信的原理:

1.父进程先通过pipe创造一个匿名管道

2.父进程再通过fork创建一个子进程,(重点:子进程继承了父进程的fd文件描述符)

3. 父进程保留读端,子进程保留写端

4.这样就实现了:子进程写入,父进程读出的进程通信。

通过文件描述符,深度理解管道:

 以下是进程间通信实例:

#include<unistd.h>
#include<stdlib,h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<assert.h>

#define ERR_EXIT(m) do{
    perror(m);
    exit(EXIT_FAILURE);
}while(0);

int main(int argc,char *argv[])
{
    int piprfd[2];//输出型参数 ,会返回两个文件描述符
    if(pipe(pipefd)==-1)
    ERR_EXIT("pipe error");//也可以这样写:cout<<pipe(pipefd);return 1;
    pid_t pid;
    pid=fork();
    assert(pid!=-1);//意料之外用if意料之内用assert
    if(pid==0)//子进程
    {
        close(pipefd[0]);
        write(pipefd[1],"hello",5);
        close(pipefd[1]);
        exit(EXIT_SUCCESS);
    }

    close(pipefd[1]);
    char buf[10]={0};
    read(pipefd[0],buf,10);
    printf("%s",buf);
    return 0;


}

上面的pipefd数组为输出型参数,pipe函数会将被使用的文件标识符写入这个数组。

记住一般来说:

pipefd[0]为写端。 

pipefd[1]为读端。

五、管道的特点

  1. 管道是单向通信的
  2. 管道的本质是文件,符合Linux下一切皆文件的设计理念。因为fd的生命周期是随进程的,所以管道的生命周期是随进程的。
  3. 通常需要有血缘关系的进程进行管道通信,比如:父子、兄弟、爷孙。因为这样才能继承指向同一个匿名管道的指针。与之相对的有命名管道,后续会详细介绍。
  4. 在管道通信中,写入的次数不一定与读取的次数,不是强相关的。

以下面这个为例子:

        while(true)
        {
            char a= 'x';
            
            write(pipefd[1],&a,1);
        }

这个是写端的代码。一次写入一个x。

    while(true)
    {
        int n=read(pipefd[0],buffer,sizeof buffer-1);
        if(n>0)
        {
            buffer[n]='\0';
            std::cout<<"child give me message:"<<buffer<<std::endl;
        }
    }

这个是读端的代码。

这样的运行结果是:

如图所示:可以看出,有时候写端写了很多次才会读,有时候写端写了三次就读取数据了。

这说明,一个进程重载读取管道中数据的时候是一次将管道中数据的数据全部读取出来的。而且我们也看得出读端的速度比写端的慢很多。 

5.具有一定的协同能力,让reader和writer能够按照一定的步骤通信——自带的同步机制。

六、4中场景

  1. 如果我们read了所有的管道数据,如果对方不发,我就只能等待。
  2. 如果write把管道写满了,就不能继续写了。
  3. 如果我们关闭了写端,读取完管道数据,然后再读就会返回0,表明读到了文件结尾。
  4. 写端一直写,但是把读端关闭,os就会把进行写的程序(通过信号)直接杀死,因为os不会维护一个没有意义的进程。(没有读端,写端也就没有了意义了)。

场景三实例讲解:

            char a[]="abcdefg";
            write(pipefd[1],a,sizeof(a));
            sleep(3);
            close(pipefd[1]);

这里是负责写入的进程,对管道进行了写入,并在写入后3s关闭写端。

    while(true)
    {
        int n=read(pipefd[0],buffer,sizeof buffer-1);
        if(n>0)
        {
            buffer[n]='\0';
            std::cout<<"child give me message:"<<buffer<<std::endl;
        }
        else if(n==0)
        {
            std::cout<<"我读到了文件结尾"<<std::endl;
            break;
        }

    }

这里是负责读取的父进程,当读取到文件结尾时就爱给你退出进程。

一下是运行结果:

 七、命名管道

1、命名管道与匿名管道一个很显著的区别是:匿名管道只能在有血缘关系的进程间进行通信,但命名管道可以让两个毫无关系的进程进行通信。

2、如果我们想在不相关的进程间交换数据,我们可以用到FIFO文件来进行通信,这个文件也被称之为命名管道。

3、命名管道其实就是一种特殊的文件类型。

八、命名管道通信的原理

进程间通信的原理就是让两个独立的进程看到同一份文件,通过在这个文件里进行读写,就实现了两个进程间的通信了。

匿名管道是通过子进程会继承父进程的PCD的特性,实现两个进程看到同一份文件来实现的。

而命名管道是直接创建了一个FIFO文件来实现,两个不相关的进程看到同一个文件的。

这是一个进程,打开了一个磁盘上的文件,如果两个进程打开了同一个文件,他们会使用同一个struct_file:

 

如果这两个进程同时写入这个文件的话,数据会进入到缓冲区中,然后再定期刷新到磁盘上。这样也可以实现两个进程建的通信,但是这样子太慢了,我们还需要刷新到磁盘上。如果我们有一个内存级文件的话,我们就不需要通过磁盘这样减速的外设进行通信了。这也是我们为什么要将fifo文件的定为命名管道文件的原因。

这个过程是如何保证两个进程看到的文件就是同一个呢?文件的唯一性是由绝对路径决定的,一个路径只会对应一个文件。

九、创建一个命名管道

方法一——命令行的方式:

mkfifo filename

这里的fifoname需要加上路径,不加的话默认就是本目录下

例子:

$ mkfifo ./fifo
$ echo "hello">fifo
$ cat <fifo

解释:第一行在本目录下创建了一个fifo文件,第二行使用echo指令将“hello”重定向进了fifo,第三行,将fifo文件中的内容重定向进了stdout。

echo和cat也是两个进程,在这个例子中我们也用fifo文件让这两个进程进行了通信。

方法二——通过系统调用:

1、创建一个fifo文件

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

filename与上面同理,mode就是文件创建的权限。但它会受到umask的影响:mode&~umask

因此我们一般会调用umask()函数将umask置零——umask(0);

2、两个进程通过open函数将fifo文件打开,一个以O_RDONLY,一个以O_WRONLY将文件打开。

    int wfd=open(fifoname.c_str(),O_WRONLY);
    if(wfd<0)
    {
        std::cout<<errno<<":"<<strerror(errno)<<std::endl;
        return 3;
    }
int rfd=open(fifoname.c_str(),O_RDONLY);
    if(rfd<0)
    {
        std::cout<<errno<<":"<<strerror(errno)<<std::endl;
        return 2;
    }
    std::cout<<"fifo open sucess,begin ipc"<<std::endl;

3、负责写的进程开始向fifo文件里写入,而负责读取的进程就开始读取。

char buffer[NUM];
    while(true)
    {
        std::cout<<"请输入你的信息#";
        char *msg=fgets(buffer,sizeof(buffer),stdin);
        assert(msg);
        (void)msg;
        buffer[strlen(buffer)-1]=0;
        ssize_t n=write(wfd,buffer,strlen(buffer));
        assert(n>0);
        (void)n;
        if(strcasecmp(buffer,"quit")==0) break;
    }
    char buffer[NUM];
    while(true)
    {
        buffer[0]=0;
        int n =read(rfd,buffer,sizeof(buffer)-1);
        if(n>0)
        {
            buffer[n]=0;
            std::cout<<buffer<<std::endl;
            fflush(stdout);
        }
        else if(n==0)
        {
            std::cout<<"client out,me too"<<std::endl;
            break;
        }
        else{
            std::cout<<n<<std::endl;
            std::cout<<errno<<":"<<strerror(errno)<<std::endl;
            break;
        }

    }

十、上实例

makefile

.PHONY:all
all:clientPipe serverPipe
serverPipe.c
clientPipe:clientPipe.c
 g++ -o $@ $^ std=c++11
serverPipe:serverPipe.c
 gcc -o $@ $^ std=c++11
.PHONY:clean
clean:
 rm -f clientPipe serverPipe

server.cc//这就是创建fifo文件的进程

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


int main()
{
    //1、创建管道文件
    umask(0);//这个不影响系统配置,只改变当前进程。
    int n=mkfifo(fifoname.c_str(),mode);
    if(n!=0)
    {
        std::cout<<errno<<":"<<strerror(errno)<<std::endl;
        return 1;
    }
    std::cout<<"create fifo success"<<std::endl;

    //2、服务端直接开启管道文件

    int rfd=open(fifoname.c_str(),O_RDONLY);
    if(rfd<0)
    {
        std::cout<<errno<<":"<<strerror(errno)<<std::endl;
        return 2;
    }
    std::cout<<"fifo open sucess,begin ipc"<<std::endl;
    
    //开始通信
    char buffer[NUM];
    while(true)
    {
        buffer[0]=0;
        int n =read(rfd,buffer,sizeof(buffer)-1);
        if(n>0)
        {
            buffer[n]=0;
            std::cout<<buffer<<std::endl;
            fflush(stdout);
        }
        else if(n==0)
        {
            std::cout<<"client out,me too"<<std::endl;
            break;
        }
        else{
            std::cout<<n<<std::endl;
            std::cout<<errno<<":"<<strerror(errno)<<std::endl;
            break;
        }

    }
    close(rfd);


    unlink(fifoname.c_str());



    return 0;
}

client.cc//负责写入的文件

#include <iostream>
#include <cstdio>
#include <cerrno>
#include <cstring>
#include <cassert>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
// #include <ncurses.h>
#include "comm.hpp"

int main()
{
    //1、不需要创建管道文件,我只需要打开对应的文件即可

    int wfd=open(fifoname.c_str(),O_WRONLY);
    if(wfd<0)
    {
        std::cout<<errno<<":"<<strerror(errno)<<std::endl;
        return 3;
    }

    //进行常规通信
    char buffer[NUM];
    while(true)
    {
        std::cout<<"请输入你的信息#";
        char *msg=fgets(buffer,sizeof(buffer),stdin);
        assert(msg);
        (void)msg;
        buffer[strlen(buffer)-1]=0;
        ssize_t n=write(wfd,buffer,strlen(buffer));
        assert(n>0);
        (void)n;
        if(strcasecmp(buffer,"quit")==0) break;
    }

    close(wfd);

    return 0;
}

运行结果:

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

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

相关文章

编译原理 SLR(1) 语法分析器的构建

编译原理 SLR(1) 语法分析器的构建 在我的博客查看&#xff1a;https://chenhaotian.top/study/compilation-principle-slr1/ 实验三 自底向上语法分析器的构建 项目代码&#xff1a;https://github.com/chen2438/zstu-study/tree/main/%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%8…

冈萨雷斯DIP第10章知识点

文章目录 10.2 点、线和边缘检测10.2.2 孤立点的检测10.2.3 线检测10.2.4 边缘模型 10.3 阈值处理10.3.4 使用图像平滑改进全局阈值处理10.3.5 使用边缘改进全局阈值处理10.4 使用区域生长、区域分离与聚合进行分割 分割依据的灰度值基本性质是&#xff1a;不连续性和相似性。本…

计算机网络第二章——物理层(下)

提示&#xff1a;君子可内敛不可懦弱&#xff0c;面不公可起而论之 文章目录 2.1.7 数据交换方式为什么要进行数据交换数据交换的方式电路交换电路交换的优缺点报文交换报文交换的优缺分组交换分组交换的优缺点数据交换方式的选择数据报方式虚电路方式虚电路方式的特点数据报VS…

HJ29 字符串加解密

描述 对输入的字符串进行加解密&#xff0c;并输出。 加密方法为&#xff1a; 当内容是英文字母时则用该英文字母的后一个字母替换&#xff0c;同时字母变换大小写,如字母a时则替换为B&#xff1b;字母Z时则替换为a&#xff1b; 当内容是数字时则把该数字加1&#xff0c…

深入理解设计原则之依赖反转原则(DIP)【软件架构设计】

系列文章目录 C高性能优化编程系列 深入理解软件架构设计系列 深入理解设计模式系列 高级C并发线程编程 DIP&#xff1a;依赖反转原则 系列文章目录1、依赖反转原则的定义和解读2、稳定的抽象层3、依赖倒置原则和控制反转、依赖注入的联系小结 1、依赖反转原则的定义和解读 …

多线程事务回滚方法

多线程事务回滚方法 介绍案例演示线程池配置异常类实体类控制层业务层mapper工具类验证 解决方案使用sqlSession控制手动提交事务SqlSessionTemplate注入容器中改造业务层验证成功操作示例业务层改造 介绍 1.最近有一个大数据量插入的操作入库的业务场景&#xff0c;需要先做一…

Matcher: Segment Anything with One Shot Using All-Purpose Feature Matching 论文精读

Matcher: Segment Anything with One Shot Using All-Purpose Feature Matching 论文链接&#xff1a;[2305.13310] Matcher: Segment Anything with One Shot Using All-Purpose Feature Matching (arxiv.org) 代码链接&#xff1a;aim-uofa/Matcher: Matcher: Segment Anyt…

STM32 HAL库开发——基础篇

目录 一、基础知识 1.1 Cortex--M系列介绍 1.2 什么是stm32 1.3 数据手册查看 1.4 最小系统和 IO 分配 1.4.1 电源电路 1.4.2 复位电路 1.4.3 BOOT 启动电路 1.4.4 晶振电路 1.4.5 下载调试电路 1.4.6 串口一键下载电路 1.4.7 IO 分配 1.4.8 总结 1.5 开发工…

Spring:Spring框架中的核心类 ③

一、解读思想 1、用轮廓解读体系。 2、关注细节&#xff0c;不执着细节。 二、核心类设计 1、 容器接口和实现类 ApplicationContext 接口&#xff08;容器&#xff09; ①.读取配置文件 ②.注解形成bean 哪种形式的bean统一核心管理使用中心类。 2、 ApplicationCont…

MySQL 子查询

文章目录 子查询单行子查询多行子查询相关子查询 exists 子查询 所谓子查询就是 select 查询语句中还有 select 查询语句&#xff0c;里面的称为子查询或内查询&#xff0c;外面的称为主查询或外查询。 根据查询结果记录数量&#xff0c;子查询可以分为两类&#xff1a; 单行…

机器学习 | 分类问题

目录 一、K近邻算法 二、决策树 1.一些原理介绍 2.决策树案例与实践 三、距离 一、K近邻算法 我们引入accuracy_score&#xff0c;利用score()的方法评估准确性。k近邻算法中的k是一个超参数&#xff0c;需要事先进行定义。 k值得选取经验做法是一般低于训练样本得平方根…

排书 dfs 迭代加深 IDA* 剪枝 java

&#x1f351; 算法题解专栏 &#x1f351; 排书 给定 n n n 本书&#xff0c;编号为 1 ∼ n 1 \sim n 1∼n。 在初始状态下&#xff0c;书是任意排列的。 在每一次操作中&#xff0c;可以抽取其中连续的一段&#xff0c;再把这段插入到其他某个位置。 我们的目标状态是把…

【云原生-K8s】k8s可视化管理界面安装配置及比较【Kuboard篇】

总览 安装了k8s控制面板&#xff0c;方便日常的问题处理&#xff0c;查看资源状态信息&#xff0c;也可以增加子账号进行开放给其他人员使用&#xff0c;减少命令操作&#xff0c;提升工作效率 前置条件 须有一个正常使用的k8s集群附k8s v1.23版本搭建&#xff1a;https://…

amis框架实现sdk中使用tsx

1.开发过程中&#xff0c;由于自己和同事用的不同方式使用&#xff0c;本人使用react搭建的amis框架&#xff0c;同事用sdk使用方式搭建 2.开发过程中遇到问题&#xff0c;如果需求中出现amis无法满足的组件&#xff0c;需要自己进行自定义组件&#xff0c;而不同使用方式的am…

JVM内存变化分析实战

最近在一次项目压力测试时&#xff0c;监测到JVM内存明显的变化&#xff0c;由于之前开发工作中没有涉及到JVM相关的问题分析&#xff0c;所以特此借这个机会学习和记录。项目使用的JDK版本为 OpenJdk 1.8&#xff0c;虚拟机为 HotSpot。 1. 内存变化情况 在压力测试进行2H48…

Java008——Java关键字和标识符的简单认识

一、Java关键字 围绕以下3点介绍&#xff1a; 1、什么是Java关键字&#xff1f; 2、Java有哪些关键字&#xff1f; 3、Java关键字的作用&#xff1f; 4、Java关键字的使用&#xff1f;后面文章再做介绍 1.1、什么是Java关键字&#xff1f; 定义&#xff1a;被Java语言赋予了…

github开源化课程体系推荐 浙江大学 计算机考研必备408资料汇总 北京大学计算机系资料整理

github漫游指南 github漫游指南 *所有开源课程资料网站整理在文末 什么是GitHub Wiki 百科上是这么说的 GitHub 是一个共享虚拟主机服务&#xff0c;用于存放使用Git版本控制的软件代码和内容项目。它由GitHub公司&#xff08;曾称Logical Awesome&#xff09;的开发者Chr…

【手撕Spring源码】深度理解SpringMVC【下】

文章目录 控制器方法执行流程ControllerAdvice 之 ModelAttribute返回值处理器MessageConverterControllerAdvice 之 ResponseBodyAdviceBeanNameUrlHandlerMapping 与 SimpleControllerHandlerAdapterRouterFunctionMapping 与 HandlerFunctionAdapterSimpleUrlHandlerMapping…

Elasticsearch:节点角色 - node roles

你可能已经知道 Elasticsearch 集群由一个或多个节点组成。 每个节点将数据存储在分片上&#xff0c;每个分片存储在一个节点上。 到目前为止&#xff0c;你看到的每个节点都至少存储了一个分片&#xff0c;但值得注意的是&#xff0c;节点并不总是必须存储分片。 这是因为每个…

【Unity3D】运动模糊特效

1 运动模糊原理 开启混合&#xff08;Blend&#xff09;后&#xff0c;通过 Alpha 通道控制当前屏幕纹理与历史屏幕纹理进行混合&#xff0c;当有物体运动时&#xff0c;就会将当前位置的物体影像与历史位置的物体影像进行混合&#xff0c;从而实现运动模糊效果&#xff0c;即模…