目录
前言
在学习了匿名管道的基本内容之后,你有没有想过自己使用一下匿名管道,本文就详细介绍一个匿名管道的实例–进程控制。
匿名管道详解:进程间通信–匿名管道【Linux】
一、实例分析
本实例想通过管道实现一个功能 – 父进程控制子进程完成对应的任务。
简而言之,就是父进程通过管道给子进程发送任务代码,然后子进程完成对应的任务,可以分为以下几个步骤。
①创建管道
②创建子进程
③实现管道通信
④父进程发送任务代码,子进程完成任务
二、管道
什么是管道
- 管道是Unix中最古老的进程间通信的形式。
- 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
原理
父进程指向一个管道文件,子进程继承父进程的指向关系,从而子进程也指向管道,进行通信。
三、实例代码
0. 加载任务列表
由于我们需要指派子进程完成任务,首先需要一些任务列表,本实例不详细实现任务,只是打印输出执行任务成功,来模拟实现完成任务。
typedef void (*functor)();
一个数组指针,用来保存任务执行的函数。
vector<functor> functors;
一个方法集合,用vector
来存放一系列的任务执行的数组指针。
unordered_map<uint32_t, string> info;
存放uint32_t
类型的key值以及string
字符串,存放任务名称。
其中uint32_t
是unsighed int
的别名,为无符号整形。
生成任务
typedef void (*functor)();
vector<functor> functors; // 方法集合
// for debug
unordered_map<uint32_t, string> info;
void f1()
{
cout << "这是一个处理日志的任务,执行的进程 ID [" << getpid() << "]"
<< ", 执行时间是 [" << time(nullptr) << "]" << endl;
}
void f2()
{
cout << "这是一个备份数据的任务,执行的进程 ID [" << getpid() << "]"
<< ", 执行时间是 [" << time(nullptr) << "]" << endl;
}
void f3()
{
cout << "这是一个处理网络链接的任务,执行的进程 ID [" << getpid() << "]"
<< ", 执行时间是 [" << time(nullptr) << "]" << endl;
}
void loadFunctor()
{
info.insert({functors.size(), "处理日志的任务"});
functors.push_back(f1);
info.insert({functors.size(), "备份数据的任务"});
functors.push_back(f2);
info.insert({functors.size(), "处理网络链接的任务"});
functors.push_back(f3);
}
生成任务之后,在main
函数中使用loadFunctor
函数来加载任务列表。
1. 创建管道
生成管道的读端和写端
int pipefd[2] = {0};
if (pipe(pipefd) != 0)
{
cerr << "pipe error" << endl;
return 1;
}
2. 创建子进程
pid_t id = fork();
if (id < 0)
{
cerr << "fork error" << endl;
return 2;
}
3. 子进程
3.1 关闭文件fd
close(pipefd[1]);
3.2 业务处理
子进程先读取父进程发出的指令,再执行相关任务。
assert(s == sizeof(uint32_t));
(void)s;
assert
断言,是编译有效 (debug 模式),在release 模式下,断言就失效了。一旦断言失效,s
变量就是只被定义,没有被使用,release模式中,可能会有warning。
while (true)
{
uint32_t operatorType = 0;
// 如果有数据,就读取,如果没有数据,就阻塞等待,等待任务的到来
ssize_t s = read(pipefd[0], &operatorType, sizeof(uint32_t));
if (s == 0)
{
cout << "我要退出啦,我是给人打工的,老板都走了..." << endl;
break;
}
assert(s == sizeof(uint32_t));
(void)s;
if (operatorType < functors.size())
{
functors[operatorType]();
}
else
{
cerr << "bug? operatorType = " << operatorType << endl;
}
}
执行完毕之后关闭读端并退出
close(pipefd[0]);
exit(0);
4. 父进程
4.1 关闭文件fd
设置随机数种子并关闭不需要的文件fd
srand((long long)time(nullptr));
close(pipefd[0]);
4.2 指派任务
父进程形成任务码并向指定的进程下达执行任务的操作。执行完毕后,关闭写端,等待子进程退出。
int num = functors.size();
int cnt = 10;
while (cnt--)
{
// 形成任务码
uint32_t commandCode = rand() % num;
cout << "父进程指派任务完成,任务是:" << info[commandCode]
<< ", 任务的编号是:" << cnt + 1 << endl;
// 向指定的进程下达执行任务的操作
write(pipefd[1], &commandCode, sizeof(uint32_t));
sleep(1);
}
close(pipefd[1]);
pid_t res = waitpid(id, nullptr, 0);
if (res)
cout << "wait success" << endl;
输出样例
源代码
#include <iostream>
#include <unistd.h>
#include <vector>
#include <unordered_map>
#include <string>
#include <cstring>
#include <ctime>
#include <cassert>
#include <cstdlib>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
typedef void (*functor)();
vector<functor> functors; // 方法集合
// for debug
unordered_map<uint32_t, string> info;
void f1()
{
cout << "这是一个处理日志的任务,执行的进程 ID [" << getpid() << "]"
<< ", 执行时间是 [" << time(nullptr) << "]" << endl;
}
void f2()
{
cout << "这是一个备份数据的任务,执行的进程 ID [" << getpid() << "]"
<< ", 执行时间是 [" << time(nullptr) << "]" << endl;
}
void f3()
{
cout << "这是一个处理网络链接的任务,执行的进程 ID [" << getpid() << "]"
<< ", 执行时间是 [" << time(nullptr) << "]" << endl;
}
void loadFunctor()
{
info.insert({functors.size(), "处理日志的任务"});
functors.push_back(f1);
info.insert({functors.size(), "备份数据的任务"});
functors.push_back(f2);
info.insert({functors.size(), "处理网络链接的任务"});
functors.push_back(f3);
}
int main()
{
// 0. 加载任务列表
loadFunctor();
// 1. 创建管道
int pipefd[2] = {0};
if (pipe(pipefd) != 0)
{
cerr << "pipe error" << endl;
return 1;
}
// 2. 创建子进程
pid_t id = fork();
if (id < 0)
{
cerr << "fork error" << endl;
return 2;
}
else if (id == 0)
{
// 3. 关闭不需要的文件fd
// child, read
close(pipefd[1]);
// 4. 业务处理
while (true)
{
uint32_t operatorType = 0;
// 如果有数据,就读取,如果没有数据,就阻塞等待,等待任务的到来
ssize_t s = read(pipefd[0], &operatorType, sizeof(uint32_t));
if (s == 0)
{
cout << "我要退出啦,我是给人打工的,老板都走了..." << endl;
break;
}
assert(s == sizeof(uint32_t));
// assert断言,是编译有效 debug 模式
// release 模式,断言就没有了
// 一旦断言没有了,s变量就是只被定义了,没有被使用,release模式中,可能会有warning
(void)s;
if (operatorType < functors.size())
{
functors[operatorType]();
}
else
{
cerr << "bug? operatorType = " << operatorType << endl;
}
}
close(pipefd[0]);
exit(0);
}
else
{
srand((long long)time(nullptr));
// 3. 关闭不需要的文件fd
// parent, write
close(pipefd[0]);
// 4. 指派任务
int num = functors.size();
int cnt = 10;
while (cnt--)
{
// 5. 形成任务码
uint32_t commandCode = rand() % num;
cout << "父进程指派任务完成,任务是:" << info[commandCode]
<< ", 任务的编号是:" << cnt + 1 << endl;
// 向指定的进程下达执行任务的操作
write(pipefd[1], &commandCode, sizeof(uint32_t));
sleep(1);
}
close(pipefd[1]);
pid_t res = waitpid(id, nullptr, 0);
if (res)
cout << "wait success" << endl;
}
return 0;
}
总结
本文从代码角度详细介绍了一个匿名管道的实例–进程控制,功能是父进程控制子进程完成对应的任务。大家有兴趣的话可以尝试用自己的方式实现这个功能。喜欢的话,欢迎点赞支持和关注~