匿名管道 Linux

打印 上一主题 下一主题

主题 646|帖子 646|积分 1938

目录
管道
pipe创建一个管道
让子历程写入,父历程读取
如何把消息发送/写入给父历程
父历程该怎么读取呢
管道本质
结论:管道的特征:
测试管道大小
写端退了,测试结果
测试子历程不停写,父历程读一会就退出
管道到的应用场景
写一个历程池(pipe_use)
思绪步调
管道创建
创建父子历程
父历程写,子历程读

子历程pid有了管道也有了,就差在父历程添加字段了
先更改一下,在class里构造一下
添加字段
把初始化改造成函数
debug测试函数,纯输入函数
第二步开始控制历程了(想让子历程做什么)
改变一下,直接从键盘(0号形貌符)里读,不从管道(3)里读了,就没有管道的概念了,slaver就不消传参了,父历程通过管道写,子历程通过标准输入读
如今开始选择任务和历程
也可以轮询选择,界说一个计数器,++弄,再%等
清算收尾
bug的地方:
​编辑
以上是匿名管道 
总文件总代码
makefile中代码
Task.hpp中代码
ProcessPool.cc中代码


管道

起首自己要用用户层缓冲区,还得把用户层缓冲区拷贝到管道里,(从键盘里输入数据到用户层缓冲区里面),然后用户层缓冲区通过体系调用(write)写到管道里,然后再通过read体系调用,被对方(读端)读取,就要从管道拷贝到读端,然后再表现到表现器上。
pipe创建一个管道

pipe的先容

1完成这件事:

看图分析

运行结果

  1. #include<iostream>
  2. #include<unistd.h>
  3. using namespace std;
  4. int main()
  5. {
  6.     //创建管道
  7.     //先创建一个pipefd数组
  8.     int pipefd[2];
  9.     //用n接受一下,判断是否成功
  10.     int n = pipe(pipefd);
  11.     if(n<0) return 1;//创建失败了
  12.     //创建成功
  13.     //测试一下文件描述符是3和4
  14.     cout<<"pipefd[0]:"<<pipefd[0]<<"pipefd[1]:"<<pipefd[1]<<endl;
  15.    
  16.     return 0;
  17. }
复制代码


2完成这件事:

创建一个子历程

  1. pid_t id = fork();
  2.     if(id < 0)return 2;//创建失败
  3.     if(id == 0)//创建成功
  4.     {
  5.         //子进程
  6.     }
  7.     //父进程
复制代码

让子历程写入,父历程读取


要想让子历程历程写,就需要在历程中关闭读端

  1. if(id == 0)//创建成功
  2. {
  3.      //子进程
  4.      close(pipefd[0]);
  5. }
复制代码
同理

  1. //父进程
  2. close(pipefd[1]);
复制代码
都用完结束后,可以都关掉

  1.     if(id == 0)//创建成功
  2.     {
  3.         //子进程
  4.         close(pipefd[0]);
  5.         //.....
  6.         close(pipefd[1]);
  7.     }
  8.     //父进程
  9.     close(pipefd[1]);
  10.     //.....
  11.     close(pipefd[0]);
复制代码
IPC code,写通讯代码
3这件事也完成了:

结构就有了


然后在pipefd[1]这个管道里写,界说一个Writer函数

  1.     if(id == 0)//创建成功
  2.     {
  3.         //子进程
  4.         close(pipefd[0]);
  5.         //.....IPC code,写通信代码
  6.         //在pipefd[1]这个管道里写
  7.         Writer(pipefd[1]);
  8.         close(pipefd[1]);
  9.         exit(0);//正常退出
  10.     }
复制代码
同理父历程的        

  1.     //父进程
  2.     close(pipefd[1]);
  3.     //.....IPC code,写通信代码
  4.     //在pipefd[0]这个管道里写
  5.     Reader(pipefd[0]);
  6.     close(pipefd[0]);
复制代码

  1. //子进程
  2. void Writer(int wfd)
  3. {
  4. }
  5. //父进程
  6. void Reader(int rfd)
  7. {
  8. }
复制代码
Writer

  1. //子进程
  2. void Writer(int wfd)
  3. {
  4.     string s = "hello,I am child";
  5.     pid_t self = getpid();
  6.     int number = 0;
  7.     char buffer[10];
  8.     while(true)
  9.     {
  10.         buffer[0] = 0;//字符串清空,只是为了提醒阅读代码的人,我把这个数组当字符串了
  11.     }
  12. }
复制代码

用到snprintf
先容

将s和self和number放进buffer

  1.   char buffer[100];
  2.     while(true)
  3.     {
  4.         buffer[0] = 0;//字符串清空,只是为了提醒阅读代码的人,我把这个数组当字符串了
  5.         snprintf(buffer,sizeof(buffer),"%s    pid:%d\n",s.c_str(),self);
  6.         cout<< buffer <<endl;
  7.         sleep(1);
  8.     };
复制代码
用cout打印测试一下,打印成功分析写入buffer成功了

等待历程少不了,子历程exit后需要回收

  1. //父进程
  2.     close(pipefd[1]);
  3.     //.....IPC code,写通信代码
  4.     //在pipefd[0]这个管道里写
  5.     Reader(pipefd[0]);
  6.     //等待进程缺少不了
  7.     pid_t rid = waitpid(id,nullptr,0);
  8.     if(rid < 0) return 3;//等待失败了
  9.     close(pipefd[0]);
复制代码

如何把消息发送/写入给父历程

用到了write


用write写入管道(管道也是文件),用strlen,不消+1,不消管\0,由于C语言规定\0末端,和文件没有关系,wfd写入管道

  1. //子进程
  2. void Writer(int wfd)
  3. {
  4.     string s = "hello,I am child";
  5.     pid_t self = getpid();
  6.     int number = 0;
  7.     char buffer[100];
  8.     while(true)
  9.     {
  10.         buffer[0] = 0;//字符串清空,只是为了提醒阅读代码的人,我把这个数组当字符串了
  11.         snprintf(buffer,sizeof(buffer),"%s    pid:%d  %d\n",s.c_str(),self,number++);
  12.         //用write写入管道(管道也是文件),用strlen,不用+1,不用管\0,因为C语言规定\0结尾,和文件没有关系,wfd写入管道
  13.         write(wfd,buffer,strlen(buffer));
  14.         //cout<< buffer <<endl;
  15.         sleep(1);
  16.     };
  17. }
复制代码
父历程该怎么读取呢

用到了read,fd是文件形貌符,从特定的文件形貌符里读取,放在这个buf里,buf的长度是count

这里就需要思量到\0,由于buffer中需要\0

  1. //父进程
  2. void Reader(int rfd)
  3. {
  4.     char buffer[100];
  5.     while(true)
  6.     {
  7.         buffer[0] = 0;
  8.                                     //用sizeof是为了留个空间放\0
  9.         ssize_t n = read(rfd, buffer, sizeof(buffer));//sizeof!=strlen
  10.         if(n > 0)
  11.         {
  12.             //添加\0,因为要放在buffer数组中读取
  13.             buffer[n]=0;
  14.             cout << "father get a message[" << getpid() <<"]"<< buffer <<endl;
  15.         }
  16.     }
  17. }
复制代码
运行结果
也会发现:为什么子历程sleep,父历程不sleep,父历程还是会跟着子历程sleep,由于父子历程是要协同的

管道本质

通讯是为了更好的发送变化的数据,管道本质上是文件
所以必须要用到体系调用接口来访问管道,其是由体系管理,read和write
,操作体系相当于中介 
结论:管道的特征:

1:具有血缘关系的历程进行历程间通讯
2:管道只能单向通讯
3:父子历程是会历程协同的,同步与互斥的--掩护管道文件的数据安全
4:管道是面向字节省的
5:管道是基于文件的,而文件的生命周期是随历程的

再测试,把子历程sleep去掉,就是让子历程写快一点,父历程sleep几秒,就是让父历程读慢一点,看有什么现象

 管道的四种环境





测试管道大小

把c不停往管道里写,把父历程中休眠50秒


结果差不多64kb



写端退了,测试结果


结果是:
读端正常读,写端关闭,读端就会读到0,表明读到了文件(pipe)末端,不会被阻塞
read读取成功会返回读到的字符个数,读到末端返回0


读到末端父历程也就可以制止读取了,break后去把僵尸的子历程回收

break到这里

最后子历程会被waitpid回收

测试子历程不停写,父历程读一会就退出

界说一个cnt控制退出的时间

这里也要修改一下,加个sleep(5),观察,close提前关闭


结果:通过13号信号杀死 


管道到的应用场景


都会酿成一个历程



写一个历程池(pipe_use)

起首创建好文件


创建5个历程

channel通道的意思
cmdfd文件形貌符
slaverid代表哪个子历程



把它放进vector容器里

思绪步调



管道创建

void(n),假装使用一下,要不然编译不外

创建父子历程


父历程写,子历程读

子历程要读取,就要关闭自己的写端,父历程同理

子历程中的任务



子历程pid有了管道也有了,就差在父历程添加字段了


先更改一下,在class里构造一下



添加字段


测试一下:结果:文件形貌符0,1,2是默认打开,3是从管道里读,4是写入管道


把初始化改造成函数





debug测试函数,纯输入函数




第二步开始控制历程了(想让子历程做什么)


这里打印的rfd都是3,正常吗,文件形貌符是可以被子历程继承的

父历程对应的写端拿到的是4-8,子历程拿到的读端fd是3


改变一下,直接从键盘(0号形貌符)里读,不从管道(3)里读了,就没有管道的概念了,slaver就不消传参了,父历程通过管道写,子历程通过标准输入读

用到了dup2,将从pipefd[0]中读酿成从0开始读


想让父历程固定的向管道里写入指定大小字节的内容,必须读取四个字节,四个字节四个字节的写和读,这里的管道64kb
必须读取四个字节


如果父历程不给子历程发送数据呢?阻塞等待!



开始控制子历程


生成一个随机数种子

可以随机选择任务和选择历程




cmd是任务码,测试一下,父历程控制子历程,父历程发送给子历程(通过cmdcode连续)



在Task.hpp里


要用到函数指针


main中的任务了就属于

再把任务装载进来


输出型参数用*


如今开始选择任务和历程




再把main中的任务弄成全局的


进行判断一下


测试 ,comcode和任创建的任务同等


这里的write是父历程进行写入,向子历程发送,子历程不得闲,先写到管道里,等得闲了再读


也可以轮询选择,界说一个计数器,++弄,再%等



整理一下控制代码,这里是输入型参数,只需要读



这样就可以轮询方式选择历程了,不消随机了

结果



清算收尾

思绪:把所有文件的形貌符都关掉



等待方式设置为0 


read返回0,就是失败了,然后slaver就会调完

结束完就会exit直接退出

打印下更好表现


关闭文件形貌符后sleep(10)秒,


然后这10个子历程一瞬间都应该break,然后最后到exit直接就退了,10秒结束后,父历程再回收他       

测试时不弄死循环,用cnt,5秒后主动结束控制,正常退出流程



测试结果


手动控制一下

界说一个select,输入0就是退出了,判断完后,就走到了选择任务

然后直接把cmdcode改为选择的select,-1是由于是从下标0开始的,输入1就是0下标的

测试


bug的地方:

这样会有一些bug(一个子历程不是只有一个写端(每一次子历程的创建都是有继承))
 这样会有一些bug(一个子历程不是只有一个写端(每一次子历程的创建都是有继承))





按理说这样是对的,可是这样就错了
由于下面两个红线还没有关掉,它们历程了最开始的w



这样倒着回收是可以的



精确改法





修改一下

最后一个push_back的就都是父历程的写入fd,

然后加一句这个红线的,每创建子历程后都先把上一次父历程的读端fd关掉就可以了,这里很妙,由于vector一开始是空的

方便看


这里这样就可以了        



管道已经完成


以上是匿名管道 


总文件总代码


makefile中代码

  1. ProcessPool:ProcessPool.cc
  2.         g++ -o $@ $^ -std=c++11
  3. .PHNOY:clean
  4. clean:
  5.         rm -f ProcessPool
复制代码
Task.hpp中代码

  1. #pragma once
  2. #include<iostream>
  3. #include<vector>
  4. using namespace std;
  5. typedef void (*task_t)();
  6. void task1()
  7. {
  8.     cout<< "lol 刷新日志" <<endl;
  9. }
  10. void task2()
  11. {
  12.     cout<< "lol 更新野区" <<endl;
  13. }
  14. void task3()
  15. {
  16.     cout<< "lol 检测软件更新" <<endl;
  17. }
  18. void task4()
  19. {
  20.     cout<< "lol 释放技能" <<endl;
  21. }
复制代码
ProcessPool.cc中代码

留意:

这里为啥是取地址一个comcode,不是一个0吗,要发送的数据就是这个,所以要把地址传给函数,可以是个数组

换成数组的话,read这也接收数据的时候,就得用数组去担当,要是写入凌驾int大小的话,就可能会出错,这个就是通讯的双方要服从的约定,这个判断一下,就是派发的这个任务是不是合法的,假设你的tasks任务中,只有4个任务,所以任务编号就是0 ~ 3,如果你担当到的任务编号是10或者-20,那么这些就是非法的,你执行的话,程序就会崩溃,所以要做一个简单的判断。
write以后,cmdcode的值也会跟着传到read对吧,write就是为了把cmdcode的值转达给给另外一个历程,以前见到的都是用的char buffer[];,这样&cmdcode能更方便的传值已往是不,看要传的是什么数据,只是转达一个int数据的话,就这样转达,如果是文本数据,或者是其他的话,可能就需要数组了,具体问题,具体讨论
  1. #include "Task.hpp"
  2. #include<iostream>
  3. #include<string>
  4. #include<vector>
  5. #include<unistd.h>
  6. #include<assert.h>
  7. #include <sys/types.h>
  8. #include <sys/wait.h>
  9. using namespace std;
  10. //打算创建5个进程
  11. const int processnum = 5;
  12. //全局任务
  13. vector<task_t> tasks;
  14. //先描述
  15. class channel//管道
  16. {
  17. public:
  18.     channel(int cmdfd,pid_t slaverid,string& processname)
  19.     :_cmdfd(cmdfd)
  20.     ,_slaverid(slaverid)
  21.     ,_processname(processname)
  22.     {}
  23. public:
  24.     int _cmdfd;//文件描述符
  25.     pid_t _slaverid;//代表哪个子进程
  26.     string _processname;//子进程的名字,方便打印日志
  27. };
  28. //子进程中读的任务
  29. // void slaver(int rfd)
  30. // {
  31. //     while(true)
  32. //     {
  33. //         cout<< getpid() <<" - "<< "read fd is->"<<rfd<<endl;
  34. //         sleep(1000);
  35. //     }
  36. // }
  37. //改变一下从fd为0的地方开始读
  38. void slaver()
  39. {
  40.     //read(0);
  41.     while(true)
  42.     {
  43.         int cmdcode = 0;
  44.         int n = read(0, &cmdcode, sizeof(int));
  45.         if(n == sizeof(int))
  46.         {
  47.             //执行cmdcode对应的任务列表
  48.             cout<< "slaver say@ get a command:" << getpid() << ":cmdcode:" << cmdcode <<endl;
  49.             //判断一下并执行
  50.             if(cmdcode >= 0 && cmdcode < tasks.size())  tasks[cmdcode]();
  51.         }
  52.         if(n == 0) break;
  53.     }
  54. }
  55. //初始化
  56. void Init(vector<channel>& channels)
  57. {
  58.     for(int i =0;i < processnum;i++)
  59.     {
  60.         int pipefd[2];
  61.         int n = pipe(pipefd);//创建管道
  62.         //返回值小于0就创建失败了
  63.         assert(!n);
  64.         (void)n;
  65.         pid_t id = fork();
  66.         if(id == 0)
  67.         {
  68.             //子进程:读
  69.             close(pipefd[1]);
  70.             //改变一下从fd为0的地方读
  71.             dup2(pipefd[0],0);
  72.             close(pipefd[0]);
  73.             //任务
  74.             slaver();
  75.             cout<< "process: " << getpid() << "quit" <<endl;
  76.             //slaver(pipefd[0]);
  77.             exit(0);
  78.         }
  79.         //父进程:写
  80.         close(pipefd[0]);
  81.         //channel添加字段
  82.         string name = "processs-" + to_string(i);
  83.         //插入的是自定义类型,要构造一下,第一个传的是文件描述符,要写入的fd
  84.         channels.push_back(channel(pipefd[1], id, name));
  85.     }
  86. }
  87. //测试函数,纯输入函数
  88. //输入:const &
  89. //输出:*
  90. //输入输出:&
  91. void debug(const vector<channel>& channels)
  92. {
  93.     for(auto&e : channels)
  94.     {
  95.         cout<< e._cmdfd <<"  "<<e._slaverid<<"   "<<e._processname<<endl;
  96.     }
  97. }
  98. void Loadtask(vector<task_t> *tasks)
  99. {   
  100.     tasks->push_back(task1);
  101.     tasks->push_back(task2);
  102.     tasks->push_back(task3);
  103.     tasks->push_back(task4);
  104. }
  105. void memu()
  106. {
  107.     cout<< "########################" <<endl;
  108.     cout<< "1:lol 刷新日志     2:lol 更新野区" <<endl;
  109.     cout<< "1:lol 检测软件更新   4:lol 释放技能" <<endl;
  110.     cout<< "           0:退出             " <<endl;
  111.     cout<< "########################" <<endl;
  112. }
  113. //2:开始控制子进程
  114. void ctrlSlaver(vector<channel> &channels)
  115. {
  116.     int which = 0;
  117.     int cnt = 5;
  118.     while(true)
  119.     {
  120.         int select = 0;
  121.         memu();
  122.         cout<< "Please Enter@:";
  123.         cin>> select;
  124.         if(select == 0) break;
  125.         //1:选择任务
  126.         //int cmdcode = rand()%tasks.size();
  127.         int cmdcode = select - 1;
  128.         //2:随机选择进程
  129.         //int processpos = rand()%channels.size();
  130.         
  131.         //2:轮询选择进程
  132.         cout<< "father say:"<< "cmdcode:" << cmdcode << "   already sendto  " <<channels[which]._slaverid << "process name   "
  133.             <<channels[which]._processname << endl;
  134.         //3:发送任务
  135.         write(channels[which]._cmdfd, &cmdcode, sizeof(cmdcode));
  136.         which++;
  137.         which%=channels.size();//保证不大于其长度
  138.         cnt--;
  139.         if(cnt == 0) break;
  140.         sleep(1);
  141.     }
  142. }
  143. void QuitProcess(const vector<channel> &channels)
  144. {
  145.     for(const auto& e : channels) close(e._cmdfd);
  146.     sleep(10);
  147.     for(const auto& e : channels) waitpid(e._slaverid, nullptr, 0);//进程的pid=_slaverid,关上了以后记得回收
  148. }
  149. int main()
  150. {
  151.     Loadtask(&tasks);
  152.     //srand(time(nullptr)^getpid()^1023);//种一个随机数种子
  153.     //在组织
  154.     vector<channel> channels;
  155.     //1:初始化
  156.     Init(channels);
  157.     debug(channels);
  158.     //2:开始控制子进程
  159.     ctrlSlaver(channels);
  160.     //3:清理收尾
  161.     QuitProcess(channels);
  162.     return 0;
  163. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表