Linux之进程间通信之管道

news2024/11/24 2:30:10

进程间通信的目的:

1、数据传输:一个进程需要将它的数据发售那个给另外一个进程。

2、资源共享:多个进程之间需要共享同样的资源。

3、通知事件:一个进程需要向另外一个或者一组进程发送消息,通知它们发生了某种事件(比如:进程终止时要通知父进程)。

4、进程控制:有些进程希望完全控制另一个进程的执行,此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时告知它的状态改变。

进程间通信的本质:

进程间通信的本质其实是:让不同的进程看到了同一份资源。

通过进程的学习我们知道进程之间是具有独立性的,各自进程的数据对方是看不到的,就算是父子进程随意他们的数据是共享的但一旦方式写入。就会发生写时拷贝,数据各自私有一份,所以了进程间进行通信是很困难的。 这也就意味着进程之间要想进行通信,一定要借助第三方资源。这个第三方资源不属于这些进行通信进程中的任何一个。有了这个第三方资源这些进程可以向这个资源里面写入数据或者读取数据,进而实现进程间通信。而这个第三方资源通常是OS提供的内存区域。因此我们可以得出结论:进程间通信的本质是让不同进程看到同一份资源。

一、前提条件(实现进程间通信)

1.进程是具有独立性的--无疑增加了通信的成本。

2.要让两个不同的进程,进行通信,前提条件是要看到同一份“资源。

3.任何进程的通信手段

a.想办法,先让不同的进程,看到同一份资源

b.让一方写入,一方读取,完成通信过程,至于,通信目的与后续工作,要结合具体场景。

二、匿名管道

1、管道的概念

管道是Unix中最古老的进程间通信的形式。我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。 例如:我们使用统计一个文件中代码的行数。

cat proc.cc | wc -l

其中cat指令和wc指令是两个程序当他们运行起来时就变成进程了cat通过标准输出将数据放到管道中wc再从管道中拿数据,至此就完成了进程间通信。

2、匿名管道的概念

匿名管道多用于具有血缘关系的进程进行通信,多用于父子进程。

我们在上面说进程间通信的本质是让不同的进程看到同一份资源,那么使用匿名管道也一定要让父子进程看到同一份资源。匿名管道其实是让父子进程看到了同一份被打开的文件资源,本质其实是一段内核缓冲区。然后了父子进程就可以对这个资源进行读写操作了,进而实现了进程间通信。

3、pipe函数

pipe函数是用于创建匿名管道,pipe函数原型如下:

int pipe(int pipefd[2]);

pipe函数的参数是一个输出型参数,数组pipefd是用来获取读端和写端的文件描述符。

pipe函数调用成功返回0失败返回-1。

4、匿名管道实现通信的原理

管道也是文件,站在管道文件的角度来理解管道实现通信的原理。

管道是一个文件,当一个进程以读和写的方式打开一个管道。再创建一个子进程,子进程会以父进程为模板,拷贝父进程的部分内容。此时file_strcut里的数组(文件描述符与文件的映射关系)会是父进程的拷贝。此时,父子进程都指向了管道文件(同一块空间)并且子进程也是以读写方式打开的该文件(因为子进程会继承父进程代码,父进程再创建子进程之前以读写方式打开的文件),如果将一个进程对文件进行写,一个进程对文件进行读,由于来给你进程指向同一空间,所以读进程拿到的数据就是写进程写进去的数据。此时就完成了对文件的通信。

5、匿名管道的使用代码

实现父进程去读取,子进程去写入的操作。

#include<iostream>
#include<cerrno>
#include<assert.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
​
int main()
{
    int pipefd[2]={0};
    //1.创建管道
    int n=pipe(pipefd);
    if(n<0){
        std::cout<<"pipe error,"<<errno<<": "<<strerror(errno)<<std::endl;
        return 1;
    }
    std::cout<<"pipefd[0]:"<<pipefd[0]<<std::endl; //读端
    std::cout<<"pipefd[1]:"<<pipefd[1]<<std::endl; //写端
    //2.创建子进程
    pid_t id=fork();
    assert(id!=-1);//正常应该用判断,我这里直接断言;意料之外用if,意料之中用assert。
​
    if(id==0){ //子进程
        
        //3.关闭不需要的fd,让父进程去读取,子进程去写入
        close(pipefd[0]); //关闭读
​
        //4.开始通信--结合某种场景
        const std::string namestr="hello,我是子进程";
        int cnt=1;
        char buffer[1024];
        while(true)
        {
            snprintf(buffer,sizeof buffer,"%s,计算器:%d,我的ID:%d\n",namestr.c_str(),cnt++,getpid());
            write(pipefd[1],buffer,strlen(buffer)); //不断写
            sleep(1);
        }
​
        close(pipefd[1]);
        exit(0);
    }
    //父进程
    //3.关闭不需要的fd,让父进程去读取,子进程去写入
    close(pipefd[1]);
    //4.开始通信
    char buffer[1024];
    while(true)
    {
        sleep(10);
        int n=read(pipefd[0],buffer,sizeof(buffer)-1);
        if(n>0)
        {
            buffer[n]='\0';
            std::cout<<"我是父进程,子进程给我信息:"<<buffer<<std::endl;
        }
    }
    close(pipefd[0]);
    return 0;
}

6、匿名管道的四种情况

6.1写进程比较慢时,读进程会进入阻塞状态,读进程只能等待
  • 当读进程进行读操作时,当读条件不满足,读进程进入阻塞状态。

读条件不满足:管道里没有数据或者说写端没有往管道写数据。

读进程进入阻塞状态:PCB的状态设置为S,该进程从运行队列进入等待队列,等待管道中有数据。

由于写进程比读进程慢,读进程在读时,大部分时间,管道是空的,此时读进程会进入阻塞状态,等待管道中有数据。

6.2当写进程进行写操作时,管道慢了,写进程就不能再写了

当写进程进行读操作时,当写条件不满足,写进程进入阻塞状态。写条件不满足:管道满了的时候

    写进程进入阻塞状态:PCB的状态设置为S,该进程从运行队列进入等待队列,等待管道中可以写入数据。

改变上面代码:子进程写数据没有时间限制,父进程读数据延时5s。

进程间同步的概念:一个进程快导致另一个进程也快,一个进程慢导致另一个进程也慢。一个进程受到控制,另外一个进程也受到控制,着就叫进程间同步。

6.3如果关闭写端文件描述符,读进程会一直读到文件结尾

这里说明,如果将写文件描述符关闭,读进程最终一定会读到管道的结尾。因为已经没有进程往文件中写入数据了。

6.4如果关闭读进程文件,写端一直写,写进程可能会被进程直接杀死,进程异常退出

读进程退出读文件描述符,系统传13号信号杀死写进程。并不是将写进程变成僵尸状态,上面是因为写进程是子进程,但是父进程没有退出。

所以一般先将写进程关闭写文件描述符,再关闭读进程的读文件描述符。

7、匿名管道的特征

三、命名管道

其实原理和匿名管道差不多,只是需要先创建一个命名管道再一个进程以读或者写的方式来打开该管道文件,再另外一个进程不需要创建管道,只需要以写或者读的方式来打开管道文件。再调用读写系统调用来往文件写或者读,来进行进程间通信。

两进程分别对同一管道文件分别用读或写的方式打开,两进程看到同一文件(资源)。不需要创建子进程,可以是两个不相关的进程。

1、创建命名管道

1.1、使用命令行mkfifo创建管道

1.2、使用函数创建管道

//头文件
#include <sys/types.h>
#include <sys/stat.h>
​
int mkfifo(const char *pathname,mode_t mode)
    
//返回值:成功返回0,失败返回-1
//参数:filename 路径+文件名
//      mode:权限

创建命名管道代码:

 
 #include<stdio.h>
 #include<stdlib.h>
 #include<sys/types.h>
 #include<sys/stat.h>
   
int main(){
     umask(0);//设置掩码为0
     int res=mkfifo("./fifo",0644);                          if(res==-1){
        perror("mkfifo error");
       exit(2);
      }
    return 0;
  }

2、使用命名管道实现两个进程间的通信代码实例

com.hpp: 相当于头文件
#pragma once
#include<iostream>
#include<string>
​
#define NUM 1024
using namespace std;
​
const string fifoname="./fifo";
uint32_t mode=0666;
server.cc: 服务端
#include<iostream>
#include<cerrno>
#include<cstring>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
​
#include "com.hpp"
​
using namespace std;
int main(){
    //创建管道文件,我们只需要创建一次
    umask(0); //这个设置并不影响系统的默认位置,只影响当前进程
    int n=mkfifo(fifoname.c_str(),mode);
    if(n!=0)
    {
        cout<<errno<<":"<<strerror(errno)<<endl;
        return 1;
    }
    cout<<"创建管道文件成功"<<endl;
    //让服务端直接开启管道文件
    int rfd = open(fifoname.c_str(),O_RDONLY);
    if(rfd<0)
    {
        cout<<errno<<":"<<strerror(errno)<<endl;
        return 2;
    }
    
    cout<<"成功打开管道文件,正常通信"<<endl;
    //3.正常通信
    char buffer[NUM];
    while(true)
    {
    buffer[0]=0;
    ssize_t n=read(rfd,buffer,sizeof(buffer)-1);
    if(n>0){
         buffer[n]=0;
        //cout<<"客户端的信息# "<<endl;
        cout<<"客户端的信息# "<<buffer<<endl;
        //printf("%c",buffer[0]);
        fflush(stdout);
    }
    else if(n==0)
    {
        cout<<"客户端退出,服务端也应该退出"<<endl;
        break;
    }
   //关闭不要的fd
   close(rfd);
   unlink(fifoname.c_str());
 }
client.cc: 用户端
#include<iostream>
#include<cerrno>
#include<cstring>
#include<cassert>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
​
#include "com.hpp"
int main(){
    int wfd=open(fifoname.c_str(),O_WRONLY);
    if(wfd<0){
        cerr<<errno<<":"<<strerror(errno)<<endl;
        return 2;
    }
  //可以进行常规通信了
  char buffer[NUM];
  while(true){
    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;
​
  }
  close(wfd);
  return 0;
}

即可以实现在用户端输入数据,在服务端接收。

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

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

相关文章

java中避免使用“isSuccess“作为变量名的原因和解决方法

阿里巴巴Java开发手册的说法 在阿里巴巴Java开发手册中关于这一点&#xff0c;有过一个『强制性』规定&#xff1a; 其他原因 另外根据Java命名约定&#xff0c;方法名应该以动词开头&#xff0c;而变量名应该以名词或形容词开头。使用"isSuccess"作为变量名可能…

AAAI 2023 | 语言模型如何增强视觉模型的零样本能力 ?

文章链接&#xff1a;https://arxiv.org/abs/2207.01328 项目地址&#xff1a;https://github.com/zjukg/DUET 该论文设计了一种新的零样本学习范式&#xff0c;通过迁移语言模型中的先验语义知识&#xff0c;与视觉模型的特征感知能力进行对齐&#xff0c;以增强后者对于未见过…

nacos集成springcloud实现配置中心功能

1. 在nacos控制台创建namespace 2. 创建SpringCloud项目 大家注意springboot和springcloud的版本对应&#xff0c;可以去官网查看。 https://spring.io/projects/spring-cloud#learn <properties><java.version>11</java.version><poi.version>5.2.…

VSCode使用CodeWhisperer(AI编程)

安装AWS Toolkit插件&#xff0c;点击侧边插件搜索并安装 2.点击aws ->CodeWhisperer ->Start 3.在下拉菜单中点击Use a personal email to sign up and sign in with AWS Builder ID 4.点Copy Code and Proceed&#xff0c;这会自动复制一些东西 5. 收到提示打开外部网…

高德API JS 地图获取多个坐标点的中心点

高德API JS 地图获取多个坐标点的中心点 一、需求 我需要&#xff1a; 在地图上展示多个地点地图缩放到合适的大小&#xff0c;要求刚好能显示全部点位边缘留有一部分间隔。 做成如图所示这样。 二、需要用到的 AMap 类库 经过一下午的研究&#xff0c;弄出来了。 需要以…

【计算机网络自顶向下】简答题习题总结(一)

计算机网络是什么 两个及以上具有独立操作系统的计算机通过某些介质连接成的相互共享软硬件资源的集合体。 计算机网络两大功能 连通性和共享 如何描述Internet 分为两方面 具体构成 计算机互连设备、通信链路、分组交换设备 提供服务&#xff1a; 提供网络应用基础架构为分布式…

计网第二章实验2:DNS

DNS实验pdf下载地址(科学上网)&#xff1a;Wireshark_DNS_v7.0 (umass.edu) 实验前置知识 nslookup命令 nslookup –option1 –option2 host-to-find dns-server 如果dns-server不指定的话那么就是默认的&#xff1a;本地DNS服务器 选项其一 -typeNS or -typeA 所代表的意思不…

文心一言 VS 讯飞星火 VS chatgpt (43)-- 算法导论5.4 7题

七、为使特征序列长度的下界变得更精确&#xff0c;请说明在 n 次硬币的公平抛掷中&#xff0c;不出现比Ign - 2lglgn 更长的连续正面特征序列的概率小于 1/n。 文心一言&#xff1a; 在 n 次硬币的公平抛掷中&#xff0c;每个硬币正、反面朝上的概率均为 1/2。让我们考虑一个…

【人工智能技术专题】「入门到精通系列教程」零基础带你进军人工智能领域的全流程技术体系和实战指南(LLM、AGI和AIGC都是什么)

零基础带你掌握人工智能全流程技术体系和实战指南&#xff08;LLM、AGI和AIGC都是什么&#xff09; 前言专栏介绍专栏说明学习大纲前提条件面向读者学习目标核心重点三连问核心学习目标和方向 什么是LLM大语言模型概念定义训练方式机器学习的类型LLM与无监督学习什么是无监督学…

视频剪辑培训班要学多久 视频剪辑的培训班有用吗

视频剪辑培训班要学多久&#xff1f;视学员的基础而定&#xff0c;零基础的学员可能需要花费较多的时间&#xff0c;而有基础的学员则更快上手。另外&#xff0c;学习的内容也会影响到学习周期。视频剪辑的培训班有用吗&#xff1f;靠谱的培训班会比自学更有用&#xff0c;效率…

为什么老板宁愿招年轻测试员?

测试员&#xff0c;30岁是一个分水岭&#xff0c;年龄越大越难找工作&#xff0c;为何&#xff1f;下面通过几方面来谈谈&#xff0c;为什么老板宁愿招年轻测试员。 可塑性强 年老的测试员可塑性不强了&#xff0c;通俗来讲&#xff0c;不会被老板画的大饼忽悠了。 而年轻人&…

canvas绘制s形曲线

<!DOCTYPE html> <html> <head><title>S形曲线示例</title> </head> <body><canvas id"canvas" width"400" height"400"></canvas><script>var canvas document.getElementById(c…

VUE-3组合API

1、为什么学vue3&#xff1f; 2020年09月18日&#xff0c;正式发布vue3.0版本。但是由于刚发布周边生态不支持&#xff0c;大多数开发者处于观望。 现在主流组件库都已经发布了支持vue3.0的版本&#xff0c;其他生态也在不断地完善中&#xff0c;这是趋势。 element-plus A Vue…

spring.freemarker 2306

Springboot Properties 2306 >spring.freemarker 模板属性 NameDescriptionDefault Valuespring.freemarker.allow-request-overrideWhether HttpServletRequest attributes are allowed to override (hide) controller generated model attributes of the same name.falses…

Cisco MPLS VPN Option A

一、拓扑 二、思路 1、AS 100内运行OSPF&#xff0c; AS 200运行OSPF打通底层网络 2、AS 100和200运行LDP协议&#xff0c;分发标签 3、PE1和ASBR-PE1建立VPNV4邻居关系&#xff08;可以看成是两个单域的PE建立VPNV4邻居关系&#xff09;&#xff0c;PE2和ASBR-PE2建立VPNV4…

Github拉取老版本或releases稳定版本的仓库

Github拉取老版本或releases稳定版本的仓库 文章目录 Github拉取老版本或releases稳定版本的仓库拉取老版本方法一&#xff1a;clone方法二&#xff1a;checkout 下载 releases 版本 拉取老版本 方法一&#xff1a;clone 随便进入一个仓库&#xff0c;查看分支信息 针对要拉取…

spring-security -oauth2 整合 JWT

前言 在这个基础上&#xff0c;进行整合。 spring security oauth2学习 -- 快速入门_本郡主是喵的博客-CSDN博客 1.jwt的一般使用 先把 reids,common-pools 等依赖删掉。 删掉redis的下相关配置 1.1 导入依赖 <!--jjwt--><dependency><groupId>io.json…

2023年测试工程师的职业规划?从入行到“顶薪“卷起来...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 初级测试工程师&a…

Python实现性能自动化测试竟然如此简单

一、思考 1.什么是性能自动化测试? 性能系统负载能力超负荷运行下的稳定性系统瓶颈自动化测试使用程序代替手工提升测试效率性能自动化使用代码模拟大批量用户让用户并发请求多页面多用户并发请求采集参数&#xff0c;统计系统负载能力生成报告 2.Python中的性能自动化测试…

Netty 进阶

粘包与半包 粘包和半包问题的出现原因主要是因为 TCP 协议是面向流的&#xff0c;而不是面向报文的。即发送方给接收方传输的是一整个数据流&#xff0c;但是接收方并不知道数据流中的哪一部分才是一个完整的数据报&#xff0c;需要自行判断。 如果是在发送方解决&#xff0c;通…