提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
每个进程是相互独立的(代码和数据),但是往往许多工作需要俩个进程之间协同工作,所以学会进程通信很重要。今天刚刚学会管道这种模式,所以总结下做个笔记。
一、管道原理
1、进程通信目的
- 数据传输:一个进程需要将它的数据发送给另一个进程
- 资源共享:多个进程之间共享同样的资源
- 通知事件:一个进程需要向另一个进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)
- 进程控制:有些进程希望完全去控制另外一个进程的执行,此时控制进程希望能够拦截另外一个进程的所有陷入和异常,并能够及时知道它的状态改变
2、让不同的进程看到同一份资源成为了进程通信的关键
- 如果能让进程1与进程2都能够操作磁盘中的文件,就能够解决进程之间的通信问题
- 让不同进程看到同一文件
3、管道特点
- 单向的(半双工)
- 传输的是数据
二、匿名管道(pipe)
1、pipe函数
2.pipe应用与父子进程之间通信
#include <iostream>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
int main()
{
// 创建管道
// 定义一个数组来接受管道的读写描述符
int pipefd[2] = {0};
if (pipe(pipefd) != 0)
{
// 创建管道失败
cerr << "pipe erro" << endl;
return 1;
}
// 创建子进程
pid_t id = fork();
if (id < 0)
{
cerr << "fork erro" << endl;
return 2;
}
else if (id == 0)
{
// 子进程
// 子进程完成读,关闭写操作
close(pipefd[1]);
// 读入内容
#define NUM 1024
char buf[NUM];
while(true)
{
//将缓冲区清零
memset(buf,0,sizeof(buf));
size_t s = read(pipefd[0],buf,sizeof(buf)-1);
if(s > 0)
{
//读到了数据
buf[s] = '\0';
cout<<"子进程接受到的数据是:~"<<buf<<endl;
}
else if(s == 0)
{
cout<<"父进程写完了,我也退出了"<<endl;
break;
}
}
// 关闭读操作
close(pipefd[0]);
exit(0);
}
else
{
// 父进程
// 父进程完成写,关闭读操作
close(pipefd[0]);
// 写入内容
const char* msg = "子进程,我是父进程,这次发送的编号是:";
int cnt = 0;
while (cnt<5)
{
char sendbuf[1024];
sprintf(sendbuf,"%s:%d",msg,cnt);
write(pipefd[1],sendbuf,strlen(sendbuf));
cnt++;
sleep(1);
}
// 关闭写操作
close(pipefd[1]);
}
//阻塞等待子进程退出、回收子进程
pid_t res = waitpid(id,nullptr,0);
if(res > 0)
{
cout<<"等待子进程退出成功"<<endl;
}
}
运行结果:
3、pipe应用于父进程控制子进程
#include <iostream>
#include <vector>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cassert>
using namespace std;
typedef void (*functor)();
vector<functor> functors;
void f1()
{
cout << "这是一个处理日志的任务,执行的进程 ID [" << getpid() << "]"
<< "执行时间是 [" << time(nullptr) << "]\n"
<< endl;
}
void f2()
{
cout << "这是一个数据的任务,执行的进程 ID [" << getpid() << "]"
<< "执行时间是 [" << time(nullptr) << "]\n"
<< endl;
}
void f3()
{
cout << "这是一个处理网络的任务,执行的进程 ID [" << getpid() << "]"
<< "执行时间是 [" << time(nullptr) << "]\n"
<< endl;
}
//将任务加载到数组中
void loadFunctor()
{
functors.push_back(f1);
functors.push_back(f2);
functors.push_back(f3);
}
int main()
{
//加载任务
loadFunctor();
// 创建管道
int pipefd[2] = {0};
if (pipe(pipefd) != 0)
{
// 创建管道失败
cerr << "pipe erro" << endl;
return 1;
}
// 创建管道成功、创建父子进程
pid_t id = fork();
if (id < 0)
{
// 创建子进程失败
cerr << "fork erro" << endl;
return 2;
}
else if (id == 0)
{
// 子进程
// 子进程读操作、关闭写操作
close(pipefd[1]);
// 读任务编码
while(true)
{
uint32_t operatorType = 0;
size_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<<"读取错误"<<endl;
}
}
close(pipefd[0]);
exit(0);
}
else if (id > 0)
{
//种子
srand((long long)time(nullptr));
// 父进程
// 父进程进行写操作、关闭读操作
close(pipefd[0]);
// 发送任务编号
int num = functors.size();
int cnt = 10;
while (cnt--)
{
//形成任务码
uint32_t commandCode = rand() % num;
//发送任务码
write(pipefd[1],&commandCode,sizeof(uint32_t));
sleep(1);
}
close(pipefd[1]);
// 当父进程处理完之后,等待子进程结束进行回收
pid_t res = waitpid(id, nullptr, 0);
if (res)
{
cout << "等待子进程退出" << endl;
}
}
}
运行结果:
4、pipe父进程控制一批子进程
#include <iostream>
#include <vector>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cassert>
using namespace std;
typedef void (*functor)();
// 装入函数
vector<functor> functors;
// 定义一个pair,存放子进程的ID与文件描述符
typedef pair<int32_t, int32_t> elem;
// 定义子进程数量
int processNum = 5;
void f1()
{
cout << "这是一个处理日志的任务"
<< "执行时间是 [" << time(nullptr) << "]\n"
<< endl;
}
void f2()
{
cout << "这是一个数据的任务"
<< "执行时间是 [" << time(nullptr) << "]\n"
<< endl;
}
void f3()
{
cout << "这是一个处理网络的任务"
<< "执行时间是 [" << time(nullptr) << "]\n"
<< endl;
}
// 将任务加载到数组中
void loadFunctor()
{
functors.push_back(f1);
functors.push_back(f2);
functors.push_back(f3);
}
void work(int blockFd)
{
while (true)
{
uint32_t operatorData = 0;
size_t s = read(blockFd, &operatorData, sizeof(uint32_t));
if (s == 0)
{
cout << "父进程不写了,我要退出了" << endl;
}
assert(s == sizeof(uint32_t));
(void)s;
if (operatorData < functors.size())
functors[operatorData]();
}
}
void blanceSendTask(const vector<elem> processFds)
{
// 种子
srand((long long)time(nullptr));
while (true)
{
sleep(1);
// 随机选择一个进程
uint32_t pick = rand() % processFds.size();
// 随机产生一个任务
uint32_t task = rand() % functors.size();
// 将任务分发给进程
write(pick, &task, sizeof(uint32_t));
}
}
int main()
{
// 加载任务
loadFunctor();
vector<elem> assignMap;
for (int i = 0; i < processNum; i++)
{
// 创建管道
int pipefd[2] = {0};
if (pipe(pipefd) != 0)
{
cerr << "pipe erro" << endl;
return 2;
}
// 创建进程
pid_t id = fork();
if (id < 0)
{
cerr << "fork erro" << endl;
return 1;
}
else if (id == 0)
{
// 子进程
// 子进程需要关闭写操作,并读取父进程发来的命令
close(pipefd[1]);
work(pipefd[0]);
// 执行完关闭操作
close(pipefd[0]);
exit(0);
}
else if (id > 0)
{
// 父进程
// 父进程需要关闭读操作,并收集子进程的id,与写文件描述符
close(pipefd[0]);
elem e(id, pipefd[1]);
assignMap.push_back(e);
}
}
cout << "创建所有子进程成功" << endl;
blanceSendTask(assignMap);
// 等待子进程结束,回收子进程
for (int i = 0; i < processNum; i++)
{
if (waitpid(assignMap[i].first, nullptr, 0) > 0)
{
cout << "等待成功,回收子进程,子进程是:" << assignMap[i].first << endl;
// 关闭写操作
close(assignMap[i].second);
}
}
}
运行结果:
二、命名管道(mkfifo)
1、命名管道
如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
2、命令行创建命名管道
3、代码创建命令行
客户端:
#include "comm.h"
using namespace std;
int main()
{
int pipeFd = open(IPC_PATH, O_WRONLY);
if(pipeFd < 0)
{
cerr << "open error " << endl;
return 1;
}
#define NUM 1024
char line[NUM];
while(true)
{
printf("请输入你的消息# ");
fflush(stdout);
memset(line, 0, sizeof(line));
// fgets -> C -> line结尾自动添加\0
if(fgets(line, sizeof(line), stdin) != nullptr)
{
//abcd\n\0
line[strlen(line) - 1] = '\0';
write(pipeFd, line, strlen(line));
}
else
{
break;
}
}
close(pipeFd);
return 0;
}
服务端:
#include "comm.h"
using namespace std;
int main()
{
umask(0);
//创建命名管道
if(mkfifo(IPC_PATH,0600) != 0)
{
cerr<<"mkfifo error"<<endl;
return 1;
}
//读管道
int pipefd = open(IPC_PATH,O_RDONLY);
if(pipefd < 0)
{
cerr<<"open fifo error"<<endl;
return 2;
}
//打开管道成功
#define NUM 1024
char buffer[NUM];
while(true)
{
size_t s = read(pipefd,buffer,sizeof(buffer)-1);
if(s == 0)
{
cout<<"客户推出了,我也退出"<<endl;
break;
}
else if(s > 0)
{
buffer[s] = '\0';
cout<<"客户端->服务端#"<<buffer<<endl;
}
}
close(pipefd);
unlink(IPC_PATH);
return 0;
}
运行结果: