一、进程间通信的目的:
进程间通信的本质:是先让不同的进程看到同一份资源
• 数据传输:⼀个进程需要将它的数据发送给另⼀个进程 • 资源共享:多个进程之间共享同样的资源。
• 通知事件:⼀个进程需要向另⼀个或⼀组进程发送消息,通知它(它们)发生了某种事件(如进 程终止时要通知父进程)。
• 进程控制:有些进程希望完全控制另⼀个进程的执行(如Debug进程),此时控制进程希望能够 拦截另⼀个进程的所有陷入和异常,并能够及时知道它的状态改变
二、管道:
1.原理:父进程创建文件描述符表,表里的指针指向结构体struct file,里面的属性指向inode和缓冲区等,创建子进程,子进程同时会拷贝文件描述符表,所以子进程和父进程指向同一个结构体,也就可以指向同一个文件,首先实现了不同进程看到同一份资源。至此,让不同的进程看到同一份资源的struct file和缓冲区这一部分叫做管道。但是这块管道是属于通信的范畴,和磁盘文件没有关系,所以原理上要修改,不能与文件挂钩。
管道两端分别同时进行读和写操作,同时返回两个文件描述符表。可以单项通信。
由于是操作系统自己写的,属于内存级别,因此打开管道的时候不需要路径,也就是它没有名字。匿名管道。子进程继承了父进程的文件描述符表,所以两个进程指向的管道是同一个,在没有名字的情况下可以保证是一条管道。
代码实现:
cpp
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys.wait.h>
int main()
{
//1.创建管道
int fds[2]={0};//创建两个文件描述符,fd[0]通常表示读端 fd[1]通常表示写端
int n=pipe(fds);//创建管道
if(n<0)//成功返回0失败返回1
{
std::cout<<"pipe error"<<std::endl;
return 1;
}
std::cout<<"fds[0]: "<<fds[0]<<std::endl;//这两个文件描述符的值一定是3和4
std::cout<<"fds[1]: "<<fds[1]<<std::endl;
return 0;
}

cpp
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys.wait.h>
void ChildWrite(int wfd)
{
char buffer[1024];
int cnt=0;
while(true)
{
snprintf(buffer,sizeof(buffer),"我是一个子进程,pid:%d,cnt:%d",getpid(),cnt++);
}
}
void FatherRead(int rfd)
{
}
int main()
{
//1.创建管道
int fds[2]={0};//创建两个文件描述符,fd[0]通常表示读端 fd[1]通常表示写端
int n=pipe(fds);//创建管道
if(n<0)//成功返回0失败返回1
{
std::cout<<"pipe error"<<std::endl;
return 1;
}
std::cout<<"fds[0]: "<<fds[0]<<std::endl;//这两个文件描述符的值一定是3和4
std::cout<<"fds[1]: "<<fds[1]<<std::endl;
//2.创建子进程
pid_t id=fork();
if(id==0)
{
//3.关闭不需要的读写段,形成通信通道
//父 -> r 子 -> w
close(fd[0]);//子进程去写要关闭读端
close(fd[1]);
exit(0);
}
//3.关闭不需要的读写段,形成通信通道
//父 -> r 子 -> w
close(fd[1]);//子进程去写要关闭读端
waitpid(id,nullptr,0);
close(fd[0]);
exit(0);
return 0;
}
cpp
void ChildWrite(int wfd)
{
char buffer[1024];
int cnt=0;
while(true)
{
snprintf(buffer,sizeof(buffer),"我是一个子进程,pid:%d,cnt:%d",getpid(),cnt++);
write(wfd,buffer,strlen(buffer));
sleep(1);
}
}
void FatherRead(int rfd)
{
char buffer[1024];
while(true)
{
buffer[0]={0};
ssize_t n=read(rfd,buffer,sizeof(buffer)-1);
if(n>0)
{
buffer[n]=0;
std::cout<<"child say:"<<buffer<<std::endl;
}
}
}
管道也属于文件系统,向文件写字符串不需要写\0。所以读的时候就要自己加\0
3.特性:
(1)匿名管道只能用来进行有血缘关系的进程之间的传输。
(2)管道文件具有同步机制。
(3)管道面向字节流。
(4)管道单向通信。
(5)文件生命周期随进程。
4.管道的4种通信情况
1.写慢读快:读端等待写端读端不工作进程阻塞
2.写快读慢:写满管道时写就要阻塞等到读端的进程
3.写关闭,读继续:read会读到返回值0表示文件结尾
4.读关闭,写继续:没有通信的意义,操作系统发送异常信号直接关闭写进程
三、基于匿名管道---进程池:
在父进程下创建五个管道,有很多写端,要对自己创建的通道管理,再在fork创建五个子进程与管道一一对应,通过不同的管道向不同的子进程写入。如果父进程不想写,所以的进程都会阻塞在read这里等待父进程写消息。写一次消息子进程跑一次,父进程通过管道来暂停或唤醒对应的子进程,规定父进程给子进程写入的数据是整数值int code,每次读四字节。可以将数字规定为任务码,读到哪个数字就去完成某个任务,这种把子进程提前创建一批就叫进程池。

代码实现:
cpp
#ifndef __PROCESS_POOL_HPP__
#define __PROCESS_POOL_HPP__
#include <iostream>
#include <vector>
#include <unistd.h>
#include <cstdlib>
#include "Task.hpp"
#include <sys/wait.h>
// 先描述
class Channel
{
public:
Channel(int fd, pid_t id) : _wfd(fd), _subid(id)
{
_name = "channel-" + std::to_string(_wfd) + "-" + std::to_string(_subid); // 将整形转成字符串
}
void Send(int code)
{
int n = write(_wfd, &code, sizeof(code)); // 往管道里写
(void)n; // 强转一下n绕过编译器对未使用变量的违法检查
}
int Fd()
{
return _wfd;
}
pid_t Subid()
{
return _subid;
}
std::string Name()
{
return _name;
}
void Close()
{
close(_wfd);
}
void Wait()
{
pid_t rid = waitpid(_subid, nullptr, 0);
(void)rid;
}
~Channel()
{
}
private:
int _wfd; // 文件描述符
pid_t _subid; // 当前Channel对应的子进程
std::string _name;
};
// 再组织
class ChannelManager
{
public:
ChannelManager() : _next(0)
{
}
void Insert(int wfd, pid_t subid)
{
_channels.emplace_back(wfd, subid);
// Channel c(wfd, subid);
// _channels.push_back(c);
}
Channel &Select()
{
auto &c = _channels[_next];
_next++;
_next %= _channels.size(); // 防止_next越界
return c;
}
void PrintChannel()
{
for (auto &channel : _channels)
{
std::cout << channel.Name() << std::endl;
}
}
void StopSubProcess()
{
for (auto &channel : _channels)
{
channel.Close();
std::cout << "关闭" << channel.Name() << std::endl;
}
}
void WaitSubProcess() // 等待子进程退出
{
for (auto &channel : _channels)
{
channel.Wait();
std::cout << "回收" << channel.Name() << std::endl;
}
}
~ChannelManager()
{
}
private:
std::vector<Channel> _channels; // 用vector管理起来
int _next; // 用于父进程选择子进程
};
int gdefaultnum = 5;
// 进程池
class ProcessPool
{
public:
ProcessPool(int num) : _process_num(num)
{
// 注册任务
_tm.Register(PrintLog);
_tm.Register(DownLoad);
_tm.Register(UpLoad);
}
void Work(int rfd)
{
while (true)
{
// 子进程要等待父进程的投喂
int code = 0;
ssize_t n = read(rfd, &code, sizeof(code));
if (n > 0)
{
if (n != sizeof(code)) // 读取不完整继续读直到读取规范
{
continue;
}
std::cout << "子进程[" << getpid() << "]收到一个任务码" << code << std::endl;
_tm.Execute(code); // 执行任务
}
else if (n == 0)
{
std::cout << "子进程退出" << std::endl;
break;
}
else
{
std::cout << "读取错误" << std::endl;
break;
}
}
}
bool Start()
{
// 维护进程池的结构关系:
for (int i = 0; i < _process_num; i++) // for循环里创建的管道都是临时空间,所以要管理
{
// 创建管道
int pipefd[2] = {0};
int n = pipe(pipefd);
if (n < 0) // 管道创建失败
return false;
// 2.创建子进程
pid_t subid = fork();
if (subid < 0)
return false;
else if (subid == 0)
{
// 子进程
// 关闭不需要的文件描述符
close(pipefd[1]);
Work(pipefd[0]); // 工作
close(pipefd[0]);
exit(0); // 子进程工作做完就退出
}
else
{
// 父进程
// 关闭不需要的文件描述符
close(pipefd[0]);
_cm.Insert(pipefd[1], subid); // 没创建一个管道就在vector里加一个
}
}
return true;
}
void Debug()
{
_cm.PrintChannel();
}
void Run()
{
// 1.选择一个任务
int taskcode = _tm.Code();
// 2.负载均衡的选择一个子进程完成任务
auto &c = _cm.Select();
std::cout << "选择一个子进程" << c.Name() << std::endl;
// 2.给进程发送任务
c.Send(taskcode);
std::cout << "发送一个任务码" << taskcode << std::endl;
}
void Stop()
{
// 关闭父进程所以的wfd
_cm.StopSubProcess();
// 子进程进入僵尸要回收
_cm.WaitSubProcess();
}
~ProcessPool()
{
}
private:
ChannelManager _cm; // 里面包含信道
int _process_num; // 创建进程池的个数
TaskManager _tm; // 管理任务
};
#endif
cpp
#pragma once
#include <iostream>
#include <vector>
#include <time.h>
typedef void (*task_t)();
// 要执行的任务
void PrintLog()
{
std::cout << "我是一个打印日志任务" << std::endl;
}
void DownLoad()
{
std::cout << "我是一个下载任务" << std::endl;
}
void UpLoad()
{
std::cout << "我是一个上传任务" << std::endl;
}
class TaskManager
{
public:
TaskManager()
{
srand(time(nullptr));
}
void Register(task_t t) // 注册任务
{
_tasks.push_back(t);
}
int Code()
{
return rand() % _tasks.size();
}
void Execute(int code)
{
if (code > 0 && code < _tasks.size())
{
_tasks[code]();
}
}
~TaskManager()
{
}
private:
std::vector<task_t> _tasks;
};
cpp
#include "Process.hpp"
int main()
{
// 创建进程池对象
ProcessPool pp(gdefaultnum);
// 启动进程池
pp.Start();
// 派发任务
int cnt = 10;
while (cnt--)
{
// 1.选择一个信道之后发信息给子进程
pp.Run(); // 把任务码交给进程池再由它转发给子进程
sleep(1);
}
// 回收,结束进程池
pp.Stop();
return 0;
}
