【Linux】多管道

图示多管道结构:

1.关于父进程内核数据结构的拷贝:这里后面创建子进程,因为会和父进程有一样的文件描述符表,所以也会有文件描述符指向前面管道的写端,但是对于子进程来说我们并不关心子进程的文件描述符表,因为每个子进程都只能读对应的唯一的管道(每次创建新的管道和子进程,子进程就会关闭写端)。

2.父进程可以通过向子进程写入特定的消息,唤醒子进程,甚至让子进程定向的执行某种任务

3.如图,要注意关闭写端,子进程仍然不会退出的问题,因为多个进程指向写端

4.那么如何创建一对一的管道尤为重要!!

代码如下:

我们在头文件里面定义了三个函数,父进程随即调用不同的管道输入不同的command,从而会调用不同的函数,打印出不同的结果。

头文件:

#pragma once

#include<functional>
#include<iostream>
#include<vector>
#include<unistd.h>
#include<sys/types.h>


typedef void (*fun_t)();//函数指针,把一个void类型的无参函数指针重定义为fun_t





void  PrintLog()
{
    std::cout<<"pid:"<<getpid()<<"打印日志任务 ,正在被执行... "<<std::endl;
}

void  InsertMySQL()
{
    std::cout<<"pid:"<<getpid()<<"插入数据库任务,正在被执行 ..."<<std::endl;
}

void  NetRequest()
{
    std::cout<<"pid:"<<getpid()<<"网络请求任务 ,正在被执行 ..."<<std::endl;
}

void Exit()
{
   exit(0);    
}

//约定,每一个command都必须是4字节
#define COMMAND_LOG 0
#define COMMAND_MYSQL 1
#define COMMAND_REQUEST 2

class Task
{
public:
   
   Task()
   {
     funcs.push_back(PrintLog);
     funcs.push_back(InsertMySQL);
     funcs.push_back(NetRequest);
   }

   //对命令做出处理
   void Execute(int command)
   {
    if(command>=0&&command<funcs.size())  funcs[command](); //命令在函数指针数组范围之内,就执行对应的函数;
   }
public: 
   std::vector<fun_t> funcs;//函数指针数组
};

源文件: 

#include <iostream>
#include <string>
#include <vector>
#include<unistd.h>
#include<cassert>
#include<sys/wait.h>


#include"task.hpp"

using namespace std;

const int gnum=5;
Task t;


class endpoint 
{
private:
  static int number;//相当于子进程名
public:
  pid_t _child_pid;//管理的子进程pid
  int _write_fd;//向哪个管道里面去写 
  std::string processname;
public:
    endpoint(pid_t id,int fd)
    :_child_pid(id)
    ,_write_fd(fd)
    {
      char namebuffer[64];
      snprintf(namebuffer,sizeof(namebuffer),"process-%d[%d:%d]",number,_child_pid,_write_fd);
      processname=namebuffer;
    }

    const std::string& name()
    {
      return processname;
    }
};

//类内定义,类外初始化
int endpoint::number=0;


//子进程要执行的方法;
void waitcommand()
{
    while (1)
    {
      int command; // 4个字节的缓冲区
      int n = read(0, &command, sizeof(int));

      if (n == sizeof(int)) // 读入的数据必须是4字节
      {

        t.Execute(command);
      }
      else if (n == 0)
      {
        // 写端关闭
        break;
      }
      else
      {
        break;
      }
    }
}

void create_process(vector<endpoint>& end_points)
{
    //把所有父进程的写端文件描述符保存下来,每创建一个子进程,就关闭该进程继承自父进程的写端描述符
    vector<int> fds;
    for(int i=0;i<gnum;++i)
    {
        //1.1创建管道
        int pipefd[2]={0};
        int n=pipe(pipefd);
        assert(n==0);//表示创建管道成功
        
        //1.2创建子进程
        pid_t id=fork();
        assert(id!=-1);

 //子进程
if(id==0)
        {
       
      
          for(auto e:fds)
          {
            close(e);
          }
        
    
        //1.3关闭不要的fd
          close(pipefd[1]);
      
        //我们期望,子进程读取“指令”的时候都以标准输入读取
        //输入重定向,pipefd[0]这个文件描述符指向特定管道的读端,因为重定向以后,0这个文件描述符也指向管道的读端了,所以就不用pipefd[0]了;
        dup2(pipefd[0],0);
        
        //1.3.2 从子进程被创建开始,子进程就开始等待获取命令,会卡在waitcommand函数的read函数那,因为读不到任何数据;
        waitcommand();



          exit(0);
        }

//父进程(每次创建管道,都关闭管道的读端)
      close(pipefd[0]);
       
     


       //1.4将新的子进程和他的管道写端构建对象
       end_points.push_back(endpoint(id,pipefd[1]));//每次循环就把父进程写端的文件描述符保存下来了
       fds.push_back(pipefd[1]);
    }




}


int showBoard()
{
   std::cout<<"请选择你要执行的任务#"<<std::endl;

   std::cout<<"------------------------------------"<<std::endl;
   std::cout<<"------------------------------------"<<std::endl;
   std::cout<<"------------------------------------"<<std::endl;
   std::cout<<"--0.执行日志任务---1.执行数据库任务---"<<std::endl;
   std::cout<<"--2.执行请求任务---3.退出---"<<std::endl;
   std::cout<<"------------------------------------"<<std::endl;
   std::cout<<"------------------------------------"<<std::endl;
   std::cout<<"------------------------------------"<<std::endl;
   int command=0;
   std::cin>>command;
   return command;
}

void ctrlProcess(vector<endpoint>& end_points)
{
   
    int num=0;
    int count=0;
    while(true)
    {
    //1.选择任务
    int command= showBoard();
    if(command==3) break;
    if(command<0 || command>2) continue;
    
    //2.选择进程---循环选择
    int index=count++;
    if(count==end_points.size()) count=0;
    std::cout<<"您选择了进程:"<<end_points[index].name()<<"处理任务:"<<command<<std::endl;

    //3.下发任务
      write(end_points[index]._write_fd,&command,sizeof(command));

      sleep(1);
    }


}

void waitProcess(vector<endpoint>& end_points)
{
  //1.只需要让父进程关闭所有的写端
  //2.回收子进程的僵尸状态
// for(int end=end_points.size()-1;end<=0;end--)
// {

// //从最后一个子进程开始关闭,这样就可以正常回收了
//  close(end_points[end]._write_fd);
//  waitpid(end_points[end]._child_pid,NULL,0);

// }

//创建一对一的管道,然后就可以按顺序关闭+回收了
 for(auto& e:end_points)
{

 close(e._write_fd);
 waitpid(e._child_pid,NULL,0);

}

  
}

int main()
{
    //1.先行构建控制结构
    vector<endpoint> end_points;
    create_process(end_points);
    
    //2.进行通信
    ctrlProcess(end_points);
    
    //处理所有的退出问题(用父进程控制子进程的退出)
    waitProcess(end_points);
    return 0;
}

结果: 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值