图示多管道结构:
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;
}