进程间通信

使用vscode(微软开发)+ubuntu20.04/+c++11 c++14配置;实际上vscode是一个编辑器,但是可以添加插件就可以打造成本地的IDE;

当我们使用vscode与云服务器进行远程连接时会消耗很大的服务器资源,因为vscode的插件也在服务器上起作用耗费资源,有时可能会卡(插件过多);

进程为什么要通信

进程之间也是需要进行某种协同的。(就比如在学校里有负责上课的,由负责排课的,有负责安保的,有负责食堂的等等,他们之间协同共同构成学校系统)。

所以如何协同的前提条件是----通信;(比如排课的人通知上课的人什么时候有课,才能进行上课)

在我们多进程运行的时候,我们知道进程之间是相互独立的,而通信的话一定会传递数据而数据本身是由类别的----通知就绪的(排课催老师上课),单纯的要传递给我的数据(班长把作业考勤信息给老师),控制相关的信息(拖堂了那么排课老师催下课)......

进程是具有独立性的。(比如在电脑上启动CSGO/永劫无间/微信/QQ等多个进程,任意一个进程崩溃了不会影响其他进程)

我们又知道进程=内核数据结构+代码和数据。进程具有独立性的本质就是你进程的内核数据结构比如PCB/页表/虚拟地址空间/映射关系等都是不一样的独立的,而代码和数据不同进程之间也是不同的。那么我们就知道了内核数据结构是独立的,代码和数据是独立的,映射关系是只有自己的映射关系,所以一个进程在运行时崩掉了无非就是代码和数据崩掉,把内核数据结构释放掉。可是对于进程来讲你只是删掉了你的代码和数据以及内核结构并没有影响其他的进程。

进程如何通信

a.  进程间通信,它的成本可能会微高一些!因为进程的独立性所以无法进行多个进程共享;

一个进程开辟的资源另一个进程是看不到的;但是我们之前知道父子进程时知道父进程的数据可以通过fork继承下去但是它不属于通信;我们得知道能传递信息和一直能传递信息是不一样的,通过fork的方式继承是只读的,而且这种方案无法做到一直通信因为通信就是一直随时传递信息。虽然父子进程可以共享资源能通过fork继承但不是一直继承因为有写实拷贝;我们要的是一直通信;

b.  进程间通信的前提是:先让不同的进程看到同一份资源(这个资源一般只能由操作系统提供) 就相当于两个死对手之间进行通信不会自己通信而是由第三方来联系;

进程通信的常见方式

system V标准,定标准的一定是本专业或者领域的领头羊,背后有大量的专利支撑的;所以有些东西贵是因为要付专利钱的缘故。所以大量公司争取专利加标准因为来钱快,因为你每卖出一个带我专利的东西就要多付一份专利钱;

方式:消息队列;共享内存;信号量;

也可以直接复用内核代码直接通信:管道:命名,匿名;

管道

匿名管道

用途:主要用于在同一计算机上运行的进程之间传输数据。
特性:匿名管道是单向的,即数据只能从管道的一端流向另一端。它们通常用于父子进程之间的数据通信。

在C语言中,可以使用pipe()系统调用创建一个匿名管道。然后使用fork()创建子进程,并通过管道在父子进程之间传输数据。

 进程间通信的本质是先让两个不同的进程看到同一份公共的资源,这份公共的资源是由os统一分配的。上述图中父子进程有看到公共的资源,这份公共的资源就是你俩看到的同一份文件,最重要的是父子进程看到同一份内存级文件缓冲区;我们把整个的基于文件的让多个进程看到同一份资源我们把它叫做----管道文件!!!

但是呢管道文件只允许单向通信(要么父给子发信息,要么子给父发,不能父与子同时发信息)因为它简单所以父进程先读后写,未来子进程继承后,我们想让父进程读取,让子进程写入,那么就关掉不需要的文件描述符即可即父进程关闭4号fd,子进程关掉3号fd;那么未来父进程就可以通过3号fd通过文件对象访问文件缓冲区,子进程就可以通过文件缓冲区再通过struct_file通过4号fd来进行写;

如果想双向通信的话--两个管道;

为什么要单向通道呢?最开始的时候就是复用代码原因是简单又单独设计接口---只进行单向通信---所以才命名管道;

使用

下面代码只是让不同进程看到了同一份资源,并没有进行通信;

匿名管道测试代码

#include<iostream>
#include<unistd.h>  
#include<cerrno>  //errno.h
#include<cstring> //string.h
#include<sys/wait.h> //防止僵尸进程

const int size=1024;

//消息id
std::string getothermessage(){
    static int cnt=0;//静态变量,每次调用getothermessage时,消息ID会增加1
    std::string messageid=std::to_string(cnt);//整数转成字符串,递增消息 ID
    cnt++;
    pid_t self_id=getpid();//拿到自己的pid
    std::string stringpid=std::to_string(self_id);//将pid变为字符串
    std::string message="messageid: ";

    message+=messageid;
    message+=" my pid is : ";
    message+=stringpid;

    return message;//输出
}
//子进程进行写入
void sonwrite(int wfd){
    std::string message="fareher, i am your son\n";
    while(true){
        std::string info=message+getothermessage();//这条消息就是子进程发给父进程的消息
        write(wfd,info.c_str(),info.size());//调用系统接口写入
        //写入的时候没有写入\0,因为这是文件不是语言,没有必要写\0
        sleep(1);
    }
}
//父进程读
void fartherread(int rfd){
    char buffer[size]; 
    while(true){
    size_t n=read(rfd,buffer,sizeof(buffer)-1);//调用系统接口读
    //读的只是字符串没有\0
    if(n>0){
        buffer[n]=0;//0=='\0';曾经写的时候不带\0,那么我来未来读要加\0
        std::cout<<"farther get message: "<<buffer<<std::endl;//父进程读取
    }
    }
}
int main(){
    //1.创建管道
    int pipefd[2];//调用管道接口
    int n=pipe(pipefd);//输出型参数,wfd和rfd将会被放到pipefd数组当中
    if(n!=0){//返回非零则出错
        std::cerr<<"erron: "<<errno<<": "<<"errstring: "<<strerror(errno)<<std::endl;
        //使用了std::cerr来打印错误信息,errno来获取错误代码,和strerror(errno)来获取错误代码对应的描述字符串。
        return 1;
    }
    //管道建立成功
    //pipe[0]--0号下标--r;pipe[1]--1号下标--w;
    std::cout<<"pipefd[0]: "<<pipefd[0]<<"pipefd[1]: "<<pipefd[1]<<std::endl;//输出分配规则
    sleep(1);

    //2.创建子进程
    pid_t id=fork();
    if(id==0){
        //子进程----------write
        std::cout<<"子进程关闭不需要的fd了,准备发消息"<<std::endl;
        sleep(1);
        //3. 关闭不需要的fd
        close(pipefd[0]);//关闭读
        sonwrite(pipefd[1]);//子进程写
        close(pipefd[1]);//子进程结束关闭
        exit(0);
    }

    //父进程--------read
    std::cout<<"父进程关闭不需要的fd了,准备收消息"<<std::endl;
    sleep(1);
    close(pipefd[1]);//关闭写
    fartherread(pipefd[0]);//父进程读
    close(pipefd[0]);//父进程结束关闭

    pid_t rid=waitpid(id,nullptr,0);//等待子进程
    if(rid>0){
        std::cout<<"wait child process done "<<std::endl;
    }
    return 0;
}

我们发现子进程写的慢了那么父进程读的也慢了 ;

管道的四种情况

1.  如果管道内部是空的&&write fd没有关闭,读取条件不具备,读进程会被阻塞,等待读取条件就绪

 2.  如果管道被写满了&&read fd不读且没有关闭,那么就代表管道被写满了,那么写进程就会被阻塞,再写就覆盖历史数据了所以阻塞wait,直到管道数据被读取就可以再次写入

3.  管道一直在被读&&写端关闭了fd,读端read返回值会读到零,表示读到了文件结尾

4.  让rfd直接关闭,写端wfd一直在写入;那么此时来说就是在做无用功因为没人读,而os不做无意义的事就会杀掉进程属于异常情况因为不满足写的条件还要写;发送13号信号
写端进程直接被os使用13信号直接关掉,相当于异常

#include<iostream>
#include<unistd.h>  
#include<cerrno>  //errno.h
#include<cstring> //string.h
#include<sys/wait.h> //防止僵尸进程

const int size=1024;

//消息id
std::string getothermessage(){
    static int cnt=0;//静态变量,每次调用getothermessage时,消息ID会增加1
    std::string messageid=std::to_string(cnt);//整数转成字符串,递增消息 ID
    cnt++;
    pid_t self_id=getpid();//拿到自己的pid
    std::string stringpid=std::to_string(self_id);//将pid变为字符串
    std::string message="messageid: ";

    message+=messageid;
    message+=" my pid is : ";
    message+=stringpid;

    return message;//输出
}
//子进程进行写入
void sonwrite(int wfd){
    int pipesize=0;
    std::string message="fareher, i am your son\n";
    char c='a'; 
    while(true){
        std::string info=message+getothermessage();//这条消息就是子进程发给父进程的消息
        write(wfd,info.c_str(),info.size());//调用系统接口写入
        //写入的时候没有写入\0,因为这是文件不是语言,没有必要写\0
        sleep(1);
         
       //write(wfd,&c,1);
        //std::cout<<"pipesize: "<<++pipesize<<" write world is: "<<c<<std::endl;
        //c++;//多写几次
        //if(c=='g')break;
       //sleep(1);//休眠以防止太快
    }
    std::cout<<"child quit .."<<std::endl;//写入一条消息就退出
}
//父进程读
void fartherread(int rfd){
    char buffer[size]; 
    while(true){
    //sleep(500);//休眠
    size_t n=read(rfd,buffer,sizeof(buffer)-1);//调用系统接口读
    //读的只是字符串没有\0

    if(n>0){
        buffer[n]=0;//0=='\0';曾经写的时候不带\0,那么我来未来读要加\0
        std::cout<<"farther get message: "<<buffer<<std::endl;//父进程读取
    }
    else if(n==0){
        //如果read的返回值为0,表示写端直接关闭了,我们都到了文件的结尾所以是0
        std::cout<<"client quit,farther get return val: "<<n<<"farther quit too!"<<std::endl;
        break;
    }
    else if(n<0){
        //读失败了
        std::cout<<"read error"<<std::endl;
        break;
    }
    break;
    }
}
int main(){
    //1.创建管道
    int pipefd[2];//调用管道接口
    int n=pipe(pipefd);//输出型参数,wfd和rfd将会被放到pipefd数组当中
    if(n!=0){//返回非零则出错
        std::cerr<<"erron: "<<errno<<": "<<"errstring: "<<strerror(errno)<<std::endl;
        //使用了std::cerr来打印错误信息,errno来获取错误代码,和strerror(errno)来获取错误代码对应的描述字符串。
        return 1;
    }
    //管道建立成功
    //pipe[0]--0号下标--r;pipe[1]--1号下标--w;
    std::cout<<"pipefd[0]: "<<pipefd[0]<<"pipefd[1]: "<<pipefd[1]<<std::endl;//输出分配规则
    sleep(1);

    //2.创建子进程
    pid_t id=fork();
    if(id==0){
        //子进程----------write
        std::cout<<"子进程关闭不需要的fd了,准备发消息"<<std::endl;
        sleep(1);
        //3. 关闭不需要的fd
        close(pipefd[0]);//关闭读
        sonwrite(pipefd[1]);//子进程写
        close(pipefd[1]);//子进程结束关闭
        exit(0);
    } 

    //父进程--------read
    std::cout<<"父进程关闭不需要的fd了,准备收消息"<<std::endl;
    sleep(1);
    close(pipefd[1]);//关闭写
    fartherread(pipefd[0]);//父进程读
    std::cout<<"5s close rfd"<<std::endl;
    sleep(5);
    close(pipefd[0]);//父进程结束关闭
    int status=0;
    pid_t rid=waitpid(id,&status,0);//等待子进程
    if(rid>0){
        std::cout<<"wait child process done,code(i) "<<WEXITSTATUS(status)<<std::endl;//退出码
        std::cout<<"wait child process done,sig "<<(status&0x7f)<<std::endl;//推出信号
    }
    return 0;
}

 管道的5种特性

1. 匿名管道:只能用来(具有血缘关系即继承关系,父子,爷孙等)父子进程之间的通信;如果俩个毫不相关的进程是不能用匿名管道的因为无法做到让两个毫不相关的进程看到同一个文件,父子本身能看到相同文件的原因是继承关系;

2.  管道内部,自带进程之间的同步机制 

3.  管道文件的生命周期是随进程的 

4.  管道文件在通信的时候,是面向字节流的(按字节(8位)连续读写数据。字节流通常用于处理二进制数据,)(后续讲解) ,write的次数和读取的次数不是一一匹配的

5.  管道的通信模式,是一种特殊的半双工模式。

全双工--:你给我发消息的时候,我也能给你发消息 (类似吵架)

半双工--:   我给你发消息的时候,你得等我说完,你才能给我发消息(类似跟人交流)

电话微信和短信本身是全双工的但是我们在操作使用的时候可能是半双工;对讲机就是经典的半双工

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wangsir.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值