进程间通信--管道

通信

通信的本质:1、数据传输,将自己的数据发送给另一个进程。2、资源共享,多个进程之间共享同样的数据。3、通知事件:一个进程向另一个或一组进程发送消息,通知他发生了某种事件,如进程终止通知父进程。4、进程控制:有些进程希望他们完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常。

进程需要通信,本质上是数据的相互交换,需要内存存储数据?那在哪里存储数据呢?因为各自进程是具有独立性的,所以不可能在本进程进行存储,所以需要操作系统直接或间接给通信双方的进程提供内存空间(存放交换数据)。

目前进程间通信有两套标准:POSIX--让通信过程可以跨主机;System V --聚焦在本地通信。还有共享内存(消息队列,信号量)等。

不同的通信种类本质就是:由操作系统提供的资源是由哪一个模块儿提供的:

如果是文件系统提供的:管道通信,由System V提供的:System V,提供一大块内存:共享内存,如果是计数器:信号量,如果是队列:消息队列。

匿名管道

对于父子进程而言:子进程会复制父进程的PCB相关内容,其中file_struct也会复制一份,而对于文件系统相关的文件,则是存储在内存里,不会复制。对于父子进程的file_struct看到的是同一份的内核资源(文件系统)。就通过这个内核资源进行通信,但是该文件不会存储在磁盘上,也不会在磁盘上IO刷新,有属于自己的内核缓冲区,是一个内存级文件,这个文件就叫做匿名管道。父子进程分别以读和写方式打开匿名管道,一个进程负责写,一个进程负责读,管道是无法完成单向通信的需求。

什么叫管道:由父进程通过调用特定的管道系统调用,以读的方式和写的方式打开一个内存级文件(这个内存级文件是有大小的,也就是说写得过快,写操作会被堵塞,当然,读过快,也会被堵塞到read命令),并通过fork()建立子进程继承下去,分别关闭父子进程的读写端,从而形成一条文件级别的通信信道。一般而言,管道只能用来进行单向数据通信!!!!

#include <iostream>
#include <cstring>
#include <cstdio>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
#include <unistd.h>
using namespace std;

//父进程进行读取,子进程进行写入
int main()
{
    int fds[2];  //用来接受创建管道中的输出型参数
    int d = pipe(fds);
    assert(d==0);
    int id=fork();
    assert(id>=0);

    if(id==0)
    {
        //子进程进行写入,关闭读取
        close(fds[0]);
        //子进程
        int cnt=0;
        const char* s= "这是子进程给父进程发送消息";
        while(true)
        {
            cnt++;
            char buffer[1024];
            //c语言的格式化字符串的输出
            snprintf(buffer,sizeof buffer,"child-->parent: %s[%d][%d]",s,cnt,getpid());
            //将buffer的数据通过系统调用write写到管道中
            write(fds[1],buffer,strlen(buffer));
            if(cnt==5)
                break;
            //sleep(1);
        }
        close(fds[1]);
        exit(0);
    }

    //父进程进行读取,关闭写入
    close(fds[1]);
    while(true)
    {
        char buffer[1024];
        //调用系统调用read从管道文件中读取字符
        ssize_t n= read(fds[0],buffer,sizeof(buffer)-1);
        if(n>0)
        {
            buffer[n]=0;
            cout<<"#"<<buffer<<"| parent pid"<<getpid()<<endl;
        }
        else if(n==0) //当写入操作结束的时候,读取到最后一个字符的时候,可以退出
        {
            cout<<"管道内的内容已经被读取完成"<<endl;
            break;
        }
        
    }
    //父进程,需要等待子进程
    int status=0;
    int n=waitpid(id,&status,0);
    //status的低7位是进程退出码
    cout<<"pid-->"<<(status&0x7F)<<endl;
    assert(n==id);

    // cout<<"fds[0]:"<<fds[0]<<endl;
    // cout<<"fds[1]:"<<fds[1]<<endl;
    //std::cout<<"hello c++"<<std::endl;

    return 0;
}

读写特征:

1、读快,写慢:会等待管道部分内容被读取之后,在写入。

2、读快,写慢:读会在read处阻塞。

3、写关闭,一直读:会读到管道的末尾处。

4、读关闭,写:OS会终止写端,给写端发送信号,13,终止写端。

管道特征:

1、管道的生命周期进程;

2、管道可以用来进行具有血缘关系的进程之间进行通信,常用于父子通信。

3、管道是面向字节流的

4、半双工,单向通信

5、互斥与同步机制-对共享资源进行保护的方案。

匿名管道的管道池

        创建n个子进程和n个任务,随机将任务分配给各个子进程去执行

#include <iostream>
#include <unistd.h>
#include <string>
#include <assert.h>
#include <vector>
#include <time.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/types.h>
using namespace std;
#define NUM_Process 5
//生成随机数
#define MakeSeed() srand((unsigned long)time(nullptr) ^ getpid() ^ 0x171237 ^ rand() % 1234)

//函数指针
typedef void(*func)();
void Func1()
{
    cout<<getpid()<<" :call Func1"<<endl;
    sleep(1);
}

void Func2()
{
    cout<<getpid()<<" :call Func2"<<endl;
    sleep(1);
    
} 

void Func3()
{
    cout<<getpid()<<" : call Func3"<<endl;
    sleep(1);
    
} 
void  LoadFunc(vector<func> *subFunc)  //输出型参数
{
    assert(subFunc);
    subFunc->push_back(&Func1);
    subFunc->push_back(&Func2);
    subFunc->push_back(&Func3);
}

//键值对{namex : func1}
class info_Process{
public:
     info_Process(pid_t pid,int id)   //子进程ID和管道id(也就是文件描述符)
     :pid_(pid),id_(id)
     {
        char namebuffer[1024];
        snprintf(namebuffer,sizeof namebuffer,"Process%d[%d][%d]",cnt,pid_,id);
        name_ = namebuffer;
     }
public:
    string name_; //每个子进程的名字
    int id_;      //子进程读取管道的id
    pid_t pid_;   //子进程的PID
    static int cnt;
};
int info_Process::cnt=0;



int recvTask(int fds)
{
    int code=0;
    ssize_t n = read(fds,&code,sizeof code);
    if(n==4) return code;
    else if(n<=0) return -1;
    else return 0;
}

void creatSubPro(vector<info_Process> *subPro,vector<func> &subFunc)   //做输出型参数
{
  for(int i=0 ; i<NUM_Process ; ++i)
    {
        int fds[2];
        ssize_t n= pipe(fds);  //成功了返回0
        assert(n == 0);
        (void)n;
        pid_t id=fork();
        if(id==0)
        {
            //子进程关闭write功能,只留下了读的功能
            close(fds[1]);
            while(true)
            {
                //1.获取命令码,如果没有发送,需要阻塞
                int commandCode = recvTask(fds[0]);
                //2.完成命令
                if(commandCode >=0 && commandCode < subFunc.size())
                {
                    subFunc[commandCode]();       
                }
                else if(commandCode == -1) break;
            }
            //子进程退出
            exit(0);
        }
        //将子进程的id信息,名字,文件口的id存储进去
        //创建一个临时的info_Process变量,存储每个进程的参数
        close(fds[0]);
        info_Process temp_info(id,fds[1]);
        subPro->push_back(move(temp_info));  //move这里把temp_info变成左值,直接记录进subPro
    }
}

void SendTask(const info_Process& subProcess,const int index)
{
    cout<<"send task num: "<<index<<"send to ->"<<subProcess.name_<<endl;
    int n=write(subProcess.id_,&index,sizeof(index));
    assert(n==sizeof(int));
    (void)n; //防止出现warning警告

}

void LoadBalanceFactor(const vector<info_Process> &subPro,const vector<func> &subFunc,int num)
{
    //获取到子进程的总量以及任务的总量
    int processnum=subPro.size();
    int tasknum = subFunc.size();
    bool forever = (num==0?true:false);
    while(true)
    {
        //1.那个子进程去执行任务
        int IndexProcess = rand()%processnum;
        //2.规定什么任务
        int IndexTask = rand()%tasknum;
        //3.分发任务
        SendTask(subPro[IndexProcess],IndexTask);
        sleep(1);
        if(!forever)
        {
            num--;
            if(num==0) break;   //如果任务数量为0的话退出进程
        }
    }
    //当退出while循环的时候,这里需要对子进程进行结束和等待
    //防止出现僵尸进程
    //这里关闭的时候应该注意:在创建的时候是循环创建的,所以第2个子进程会继承父进程的
    //所有的文件描述符,也就是说 假设文件描述符3与进程1相连接   子进程2的文件描述符中有一个文件描述符3也指向进程1
    //也就是越到后面的进程,就会继承父进程的所有的文件描述符去指向上面的子进程
    //所以在关闭子进程的时候,应该注意如果是waitpid的方式,需要从末尾开始关闭,如果从头开始关闭的话,是不能完全关闭的
    //
    //这里的关闭方法是close,在关掉最后一个子进程的时候,文件描述符被关闭,所以也能正常关闭
    // write 被关闭的时候,read方也会被关闭
    for(int i=0;i<subPro.size();++i)
    {
        close(subPro[i].id_);
    }


}
void waitProcess(vector<info_Process> processes)
{
    int processnum = processes.size();
    for(int i = 0; i < processnum; i++)
    {
        waitpid(processes[i].pid_, nullptr, 0);
        std::cout << "wait sub process success ...: " << processes[i].pid_ << std::endl;
    }
}
int main()
{
    MakeSeed();
    //1.首先创建子进程的Vector 以及 任务的vector
    vector<info_Process> subPro;
    vector<func> subFunc;
    //将所有的函数任务放入到subFunc中去
    LoadFunc(&subFunc);
    creatSubPro(&subPro,subFunc);

   //父进程
    int task_num=10;
    //父进程负责分发命令给子进程
    LoadBalanceFactor(subPro,subFunc,task_num);

    waitProcess(subPro);
    return 0;
}

命名管道

匿名管道针对的是父子进程或者亲戚进程的相互通信,对于不同进程之间的通信,可以使用命名管道,命名管道的文件属性是p,当生成命名管道的时候,一端向文件写内容,一端向文件读内容。同时通信的时候,命名管道是内存级文件,也就是向文件写内容的时候,不会刷新到磁盘中

命名管道的函数时mkfifo(pathname,mode)。

不同进程之间的通信如下代码所示:

首先需要一份公共资源需要两端都能看见

#include <iostream>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <assert.h>
#include <cerrno>
#include <fcntl.h>
#include <unistd.h>
using namespace std;
#define NAMED_PIPE "/tmp/Namedpipe"


bool creatFifo(const string &path)
{
    umask(0);  //首先设置umask的值为0
    int n=mkfifo(path.c_str(),0666);  //创建的文件的权限是 rw
    if(n==0)
        return true;
    else
    {
        std::cout<<"errno: "<<errno <<"err string :" <<strerror(errno)<<endl;
        return false;
    }
}


void removeFifo(const string &path)
{
    int n = unlink(path.c_str());
    assert(n==0); //debug中的assert才有效,release模式下不执行assert
    (void)n;  
}

然后需要对接收端也就是服务端的代码

#include "Comm.hpp"

//这边是接受信息,文件的创建和删除都在这边
int main()
{
    
    bool n = creatFifo(NAMED_PIPE);
    assert(n);
    (void)n;
    //开始从命名管道读数据
    int rfd = open(NAMED_PIPE,O_RDONLY);
    if(rfd<0) exit(-1);
    char buffer[1024];
    while(true)
    {
        ssize_t s = read(rfd,buffer,sizeof(buffer)-1);
        if(s>0)
        {
            buffer[s]=0;
            cout<<"client->server# "<<buffer<<endl;
        }
        else if(s==0)
        {
            cout<<"client quit,me too!"<<endl;
            break;
        }
        else{
            cout<<"err string:"<<strerror(errno)<<endl;
            break;
        }

    }
    remove(NAMED_PIPE);
    return 0;
}

然后是发送端也就是客户端的内容

#include "Comm.hpp"

//这边是发送信息
int main()
{
    int wrd = open(NAMED_PIPE,O_WRONLY);
    if(wrd<0) exit(-1);
    char buffer[1024];
    while(true)
    {
        cout<<"Please say# ";
        fgets(buffer,sizeof(buffer),stdin);  //先往字符串写入字符
        if(strlen(buffer)>0) buffer[strlen(buffer)-1]=0;   //把最后一个字符改成/0
        ssize_t n =write(wrd,buffer,strlen(buffer));
        assert(n==strlen(buffer));
        (void)n;
    }
    close(wrd);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值