linux——进程间通信及管道的应用场景
linux进程的控制-CSDN博客
liunx——进程间通信(管道通信)-CSDN博客
文章目录
文章目录
前言
二、管道的应用
1.创建子进程
1、描述:
2.创建进程及管理
3、子进程接受任务
4、控制子进程
总结
前言
上篇博客我们学习了进程间通信,并且我们还写了一个简单的进程间的通信,实现了子进程写数据父进程读数据。
管道的特征:
1、具有血缘关系的进程进行进程间通信;
2、管道只能单向通信;
3、父子进程是会进程协同的,同步与互斥的——保护管道文件的数据安全
4、管道是面向字节流的;
5、管道是基于文件的,而文件的生命周期吃随进程的!
一、进程间通信的四种情况
a:读写端正常,管道如果为空,读端就堵塞;
模拟:
void Write(int rfd)
{
char buff[1024];
//string s="hello word!!";
int count=0;
while(true)
{
//snprintf(buff,sizeof(buff),"%s - %d - %d ",s.c_str(),getpid(),count);
//write(rfd,buff,sizeof(buff));
count++;
sleep(1);
if(count==5)
{
break;
}
}
}
void Read(int rfd)
{
int count=0;
char buff[1024]={0};
while(true)
{
int n = read(rfd,buff,sizeof(buff));
count++;
sleep(1);
if(count==10)
{
printf("father read file done!\n");
break;
}
cout << "father get a message[" << getpid() << "]# " << buff << endl;
}
}
读数据10秒,写数据为空,不想管道写入数据那么管道就是空的,此时读端阻塞,等写端关闭,父进程继续;
b:读写端正常,管道如果被写满了,写端就会堵塞;
void Write(int rfd)
{
char buff[1024];
char bu[1]={'c'};
string s="hello word!!";
int count=0;
int size=0;
int t=0;
while(true)
{
cout<<size<<endl;
//snprintf(buff,sizeof(buff),"%s - %d - %d ",s.c_str(),getpid(),count);
//int n =write(rfd,buff,sizeof(buff));
int n =write(rfd,bu,sizeof(bu));
size+=n;
//count++;
//sleep(1);
if(n==0)
{
sleep(5);
cout<<"写文件最大内容size "<<size<<endl;
t=size;
break;//写满了不退
}
}
}
void Read(int rfd)
{
int count=0;
char buff[1024000]={0};
while(true)
{
//int n = read(rfd,buff,sizeof(buff));
count++;
sleep(1);
if(count==5)
{
int n = read(rfd,buff,sizeof(buff));
cout << "father get a message[" << getpid() << "]# " << buff << endl;
cout<<n<<endl;
printf("father read file done!\n");
break;
}
// cout << "father get a message[" << getpid() << "]# " << buff << endl;
}
}
因为读端一直不读,写端写满了就阻塞在了write那里,所以在写端打印的计数只能到65536处就阻塞了,打印不出来65536,而读端一次可以把管道的全读完。
而65569是读端将管道读完以后,管道空了,写端又开始写了。
c:读端正常读,写端关闭,读端就会读到0,表面读到了文件结尾,不会被阻塞;
void Write(int rfd)
{
char buff[1024];
string s="hello word!!";
int count=0;
int size=0;
int t=0;
while(true)
{
snprintf(buff,sizeof(buff),"%s - %d - %d ",s.c_str(),getpid(),count);
int n =write(rfd,buff,sizeof(buff));
count++;
sleep(1);
if(count==5)
{
break;//写满了不退
}
}
}
void Read(int rfd)
{
int count=0;
char buff[1024];
while(true)
{
buff[0]=0;
int n = read(rfd,buff,sizeof(buff));
if(n>0)buff[n]='\0';
count++;
sleep(1);
if(count==10)
{
printf("father read file done!\n");
break;
}
cout << "father get a message[" << getpid() << "]# " << buff << endl;
}
}
d:写端正常写,读端关闭;操作系统就要就要杀掉正在写入的进程。
void Write(int rfd)
{
char buff[1024];
string s="hello word!!";
int count=0;
int size=0;
int t=0;
while(true)
{
snprintf(buff,sizeof(buff),"%s - %d - %d ",s.c_str(),getpid(),count);
int n =write(rfd,buff,sizeof(buff));
count++;
sleep(1);
if(n==0)
{
break;//写满了不退
}
}
}
void Read(int rfd)
{
int count=0;
char buff[1024];
while(true)
{
buff[0]=0;
int n = read(rfd,buff,sizeof(buff));
if(n>0)buff[n]='\0';
count++;
sleep(1);
if(count==5)
{
printf("father read file done!\n");
break;
}
cout << "father get a message[" << getpid() << "]# " << buff << endl;
}
}
二、管道的应用
实现一个向多个进程发送指令;
1.创建子进程
我们在前面的学习过程中,我们知道了操作系统在管理进程的时候是对我们的进程,还有文件等等都是先描述在组织;
那么我们就创建一批进程将他们管理起来。
1、描述:
class channel
{
public:
channel(int cmdfd, int slaverid, const std::string &processname)
:_cmdfd(cmdfd), _slaverid(slaverid), _processname(processname)
{}
public:
int _cmdfd; // 发送任务的文件描述符
pid_t _slaverid; // 子进程的PID
std::string _processname; // 子进程的名字 -- 方便我们打印日志
// int _cmdcnt;
};
2.创建进程及管理
void InitProcessPool(std::vector<channel> *channels)
{
for(int i=0;i<processnum;i++)
{
int pipefd[2]={0};
int n=pipe(pipefd);
assert(!n);
(void)n;
pid_t id=fork();
if(id<0)
{
perror("fork\0");
return ;
}
if(id==0)
{
//子进程读
close(pipefd[1]);
dup2(0,pipefd[0]);//将写文件重载到键盘位
slaver();//子进程接受指令
close(0);
}
//父进程将文件组织起来
close(pipefd[0]);
//给子进程命名
string name="process-" + std::to_string(i);
channels->push_back(channel(pipefd[1],id,name));
sleep(1);
}
}
这段初始化就是创建一批进程将这批进程管理起来,而这些进程也都是一些管道和父进程连接在一起的;
3、子进程接受任务
void slaver()
{
// read(0)
while(true)
{
int cmdcode = 0;
int n = read(0, &cmdcode, sizeof(int)); // 如果父进程不给子进程发送数据呢??阻塞等待!
if(n == sizeof(int))
{
//执行cmdcode对应的任务列表
std::cout <<"slaver say@ get a command: "<< getpid() << " : cmdcode: " << cmdcode << std::endl;
if(cmdcode >= 0 && cmdcode < tasks.size()) tasks[cmdcode]();
}
if(n == 0) break;
}
}
我们知道我们管道传输的数据越大,那么效率越低,那么我就传输一个整数,然后通过这个整数去调取函数数组的函数;
我们进程子进程通过管道读取到父进程发送的指令,然后将我们的整数当下标去调用函数。
4、控制子进程
给出目录
void Menu()
{
std::cout << "################################################" << std::endl;
std::cout << "# 1. 刷新日志 2. 刷新出来野怪 #" << std::endl;
std::cout << "# 3. 检测软件是否更新 4. 更新用的血量和蓝量 #" << std::endl;
std::cout << "# 0. 退出 #" << std::endl;
std::cout << "#################################################" << std::endl;
}
void ctrlSlaver(const std::vector<channel> &channels)
{
int which = 0;//用来选择进程
while(true)
{
int select = 0;//用来选择执行什么指令
Menu();
std::cout << "Please Enter@ ";
std::cin >> select;
if(select <= 0 || select >= 5) break;//不在指令内就退出
// select > 0&& select < 5
// 1. 选择任务
int cmdcode = select - 1;//我们的进程的cmdfd从channels的0号下标开始存储,选项是1
// 2. 选择进程
std::cout << "father say: " << " cmdcode: " <<
cmdcode << " already sendto " << channels[which]._slaverid << " process name: "
<< channels[which]._processname << std::endl;
// 3. 发送任务
write(channels[which]._cmdfd, &cmdcode, sizeof(cmdcode));
which++;
which %= channels.size();
}
}
我们只需要向子进程通过管道传输我们目录中给出的选项就可以选择让子进程执行什么了。
5、清理收尾
void QuitProcess(const std::vector<channel> &channels)
{
for(const auto &c : channels){
close(c._cmdfd);
waitpid(c._slaverid, nullptr, 0);
}
}
将我们的连接管道端关闭,然后让父进程将自己一个一个回收掉;
总结
管道的特征:
1、具有血缘关系的进程进行进程间通信;
2、管道只能单向通信;
3、父子进程是会进程协同的,同步与互斥的——保护管道文件的数据安全
4、管道是面向字节流的;
5、管道是基于文件的,而文件的生命周期吃随进程的!