初识Linux · 匿名管道

打印 上一主题 下一主题

主题 1043|帖子 1043|积分 3129

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
目次
媒介:
匿名管道
明白为什么?
明白是什么?
明白怎么做?

媒介:

引入管道之前,我们引入几个题目,进程通信的相关题目。
第一个是进程之间为什么要通信,对于进程间通信来说,进程是具有独立性的,而进程 = 内核数据结构 + 代码数据,进程通信就是由于需要协同,协同的本质是通过数据的的流动来协同的。以是第二个题目,进程如何通信?
进程间通信是通过数据举行通信的,那么也就是说A进程给某些数据,B进程需要接受到这个数据,可是以什么作为数据流通的平台呢?此时管道就进场了,管道可以说是作为信息的载体保证两个进程之间可以通信。对于进程间的通信常见的方式有消息队列,共享内存,信号量,背面先容。
使用管道通信是直接复用的内核代码,这样不但可以简单一点,还可以低沉成本。
可是说了这么多,管道毕竟是什么呢?
两个进程之间想要通信一定要看到同一份资源,或者是同一份内存空间,以是管道现实上就是OS开辟的堆区和栈区之间的那一块共享区的资源。
管道分为匿名管道和著名管道,我们从匿名管道开始先容,到下篇文章先容的进程池的小项目,到末了的命名管道,这是管道的先容顺序,那么直接进入主题吧!

匿名管道

明白为什么?


我们通过这个图简单明白一下为什么?为什么要存在管道?
假设如今有两个进程,A进程将文件输入到了内核级文件缓冲区,然后数据通过OS到了磁盘,B如今通过read方法,读取到了A进程write的数据,这个过程看起来好像没有什么槽点?
现实上,为什么我们不能直接让A进程输入的数据直接给B呢?
那么这个过程是不需要重新计划一个通信端口的,太贫苦了,我们需要一个fork函数 close函数什么的我们就可以实现这样一个功能:
  1. int main()
  2. {
  3.     pid_t id = fork();
  4.     if(id == 0)
  5.     {
  6.         //子进程准备work...
  7.     }
  8.     //父进程准备work...
  9.    
  10.    
  11.     return 0;
  12. }
复制代码
实现这个功能之前,我们需要相识到管道通信的文件形貌符是如何的?

先看一段代码:
  1. #include <iostream>
  2. #include <sys/types.h>
  3. #include <unistd.h>
  4. using namespace std;
  5. int main()
  6. {
  7.     pid_t id = fork();
  8.     if (id == 0)
  9.     {
  10.         std::cout << "I am a child process!" << std::endl;
  11.     }
  12.     if(id > 0)
  13.     {
  14.         std::cout << "I am a father process!" << std::endl;
  15.     }
  16.     return 0;
  17. }
复制代码
我们思索一个现象,为什么父子进程默认的都是打印在了1上?
进程打开的时候我们知道是默认打开了三个流,但是我们是否思索过为什么默认打开了吗?前文提及到了历史原因是存在的。以是当我们启动了Linux机器的时候,bash进程已经启动了,此时bash进程的三个流已经打开了,我们背面启动的所有进程都是bash进程的子进程,子进程的三个流也默认打开了,那么假如我们子进程close到0 1 2呢?
  1. #include <iostream>
  2. #include <sys/types.h>
  3. #include <unistd.h>
  4. using namespace std;
  5. int main()
  6. {
  7.     pid_t id = fork();
  8.     if (id == 0)
  9.     {
  10.         close(0);
  11.         close(1);
  12.         close(2);
  13.         std::cout << "I am a child process!" << std::endl;
  14.     }
  15.     if(id > 0)
  16.     {
  17.         std::cout << "I am a father process!" << std::endl;
  18.     }
  19.     return 0;
  20. }
复制代码

现象就是父进程能正常打印,以是关闭了fd现实上不会影响自己的父进程,以是我们使用这个点
可以实现管道单向通信的功能,可是为什么实现的是单向的呢?由于假如是双向的,也就是父进程子进程的数据全部都在管道,读取的时候不经过一些操纵肯定是要出错的,以是我们先简单就看看单向的。
为什么这里我们能得出的结论是子进程能继承父进程的文件形貌符表,为了实现单向的管道通信我们需要关闭文件形貌符。

明白是什么?

我们实现管道的时候,需要用到的函数是pipe:

对于该函数来说,我们使用的时候不使用那个结构体,使用的int pipe(int pipefd[2])即可,结构体临时先不管,而对于pipefd[2]这是个输出型参数,管道开辟乐成之后,fd[1]是管道的写入文件形貌符,fd[0]是文件形貌符的读端。
而为什么管道叫做匿名管道是由于我们得到该文件形貌符甚至不需要文件名,不需要文件路径,以是叫做匿名管道。

这是创建管道最开始的模样,末了需要我们手动的关闭几个文件形貌符,至于为什么单向,为什么要关,是否可以不关等题目这里不做讨论,由于上文已经先容了。
我们本日的重点是放在怎么做上。

明白怎么做?

由前文的是什么为什么,我们知道了根本操纵是需要我们创建管道,使用pipe函数,开辟好管道之后,我们需要手动将两个文件形貌符关闭,由于子进程会继承父进程的文件形貌符表,以是对于父进程来说我们同样需要关闭对应的文件形貌符表。
对于0 1是读还是写来说,我们联合形状吧,0是张开了嘴巴,以是是读取,1就是另一个了。
怎么做我们从三个部门开始,第一个是创建管道,第二个是子进程写入数据,第三个是父进程读取数据。

假如乐成创建了管道,返回的就是0,假如不即是0我们就可以cerr了。
  1.     int pipefd[2];
  2.     int n = pipe(pipefd);
  3.     if(n)
  4.     {
  5.         std::cerr << "errno:" << errno << ":"\
  6.         << "errstring is :" << strerror(errno) << std::endl;
  7.     }
  8.     std::cout << "pipefd[0]:" << pipefd[0] << " pipefd[1]:" << pipefd[1] << std::endl;
  9.     sleep(1);
复制代码
创建管道部门,假如返回值不是0的话也就是创建失败了,以是我们打印出来详细的错误信息,使用到的是前面学习到的errno和strerror,一个是错误码,一个是错误码对应的字符串,然后打印出来0 1对应的文件形貌符,就算是管道创建乐成了。
如今就是子进程的写入数据部门,我们写对应的代码之前,简单思索一下大要的写入思绪是什么样的?
首先是创建子进程,创建之后,关闭不需要的fd,然后子进程开始work,对应的工作做完之后,关闭掉对应的文件形貌符,然后子进程退出,父进程接纳即可,这个过程文件形貌符肯定都是要关闭的,由于管道这个内存是一个引用计数的空间,以是假如不关闭,导致的结果就是内存泄漏,毕竟是空间都没有释放。
整体代码为:
  1.     //2.创建子进程
  2.     pid_t id = fork();
  3.     if(id == 0)
  4.     {
  5.         //子进程开始准备工作
  6.         std::cout << "子进程准备开始写入数据了..." << std::endl;
  7.         sleep(1);
  8.         close(pipefd[0]);
  9.         
  10.         SubProcessWrite(pipefd[1]);
  11.         close(pipefd[1]);
  12.         exit(0);
  13.     }
复制代码
然后就是子进程的subProcessWrite函数了:
  1. std::string getOtherMessage()
  2. {
  3.     //消息次数
  4.     static int cnt = 0;
  5.     std::string message = std::to_string(cnt);
  6.     cnt++;
  7.     //子进程的pid
  8.     pid_t self_id  = getpid();
  9.     std::string stringpid = std::to_string(self_id);
  10.     std::string info = "messageid: ";
  11.     message += message;
  12.     message += " My pid is :";
  13.     message += stringpid;
  14.     return message;
  15. }
  16. void SubProcessWrite(int wfd)
  17. {
  18.     int pipesize = 0;
  19.     std::string message = "Father,I am your son process! ";
  20.     char charactor = 'A';
  21.     while(true)
  22.     {
  23.         std::cout << "+++++++++++++++++++++++++++++++++++++++++++++++++" << std::endl;
  24.         //得到数据
  25.         std::string info = message + getOtherMessage();
  26.         //开始写入数据
  27.         write(wfd,info.c_str(),info.size());
  28.         std::cerr << info << std::endl;
  29.    
  30.     }
  31.     std::cout  << "child quit……" << std::endl;
  32. }
复制代码
写入数据的同时通过cerr打印到表现器上,而且写入的时候我们通过函数GetOtherMessage获取到子进程的Pid和写入了多少次的字符串。
这是子进程的写入函数部门。
子进程写入完毕之后是父进程开始读取数据:
  1. void ProcessFatherRead(int rfd)
  2. {
  3.     char inbuffer[SIZE];
  4.     while(true)
  5.     {
  6.         //休眠一会儿开始读取
  7.         sleep(2);
  8.         std::cout << "---------------------------------------------------" << std::endl;
  9.         sleep(500);
  10.         ssize_t n = read(rfd,inbuffer,sizeof(inbuffer) - 1);
  11.         if(n > 0)
  12.         {
  13.             inbuffer[n] = 0;// == '\0'
  14.             std::cout << inbuffer << std::endl;
  15.         }
  16.         else if(n == 0) //如果n == 0代表读到了文件结尾
  17.         {
  18.             std::cout << "client quit, father get return val: " << n << " father quit tool" << std::endl;
  19.             break;
  20.         }
  21.         else if(n > 0)
  22.         {
  23.             std::cout << "Read error!" << std::endl;
  24.             break;
  25.         }
  26.     }
  27. }
复制代码
父进程使用函数read,这里不妨温习一下read函数:

返回值是ssize_t ,读取count个字符,读取到buf数组内里。

假如返回值是0,代表读取到了文件的末尾,假如返回的是-1代表read出错了,> 0的代表的是success。
然后是主函数的父进程开始读取数据部门函数,大要思绪仍然先关闭掉不需要的文件形貌符,读取完之后,需要等候子进程退出,为了收集子进程的退出信息,而且我们可以打印出来:
  1.     //3.父进程开始读取
  2.     std::cout << "父进程关闭不需要的fd, 准备接收消息了..." << std::endl;
  3.     sleep(1);
  4.     close(pipefd[1]);
  5.     ProcessFatherRead(pipefd[0]);
  6.     std::cout << "5s,father close fd" << std::endl;
  7.     sleep(5);
  8.     close(pipefd[0]);
  9.     //4.父进程开始等待子进程
  10.     int status = 0;
  11.     pid_t rid = waitpid(id,&status,0);
  12.     if(rid > 0)
  13.     {
  14.         std::cout << "wait child process done, exit sig: " << (status&0x7f) << std::endl;
  15.         std::cout << "wait child process done, exit code(ign): " << ((status>>8)&0xff) << std::endl;
  16.     }
复制代码

如今看来是正常写入,但是父进程是否读取到了我们并不知道,以是我们打算让子进程write到一定水平的时候break:
  1.     while(true)
  2.     {
  3.         std::cout << "+++++++++++++++++++++++++++++++++++++++++++++++++" << std::endl;
  4.         //得到数据
  5.         std::string info = message + getOtherMessage();
  6.         //开始写入数据
  7.         write(wfd,info.c_str(),info.size());
  8.         std::cerr << info << std::endl;
  9.         
  10.         sleep(1);
  11.         write(wfd,&charactor,1);
  12.         std::cout << "pipesize: " << ++pipesize << " write charactor is: " << charactor++ << std::endl;
  13.         if(charactor == 'H') break;
  14.     }
复制代码

此时,子进程退出之后,子进程的状态乐成变成了僵尸状态,我们将父进程的sleep时间收缩,准备让父进程举行接纳子进程。
匿名管道粗略的到这里吧,,背面等着二刷。

感谢阅读!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

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

本版积分规则

北冰洋以北

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表