当前位置:   article > 正文

linux系统编程之管道(一)

linux系统编程之管道(一)

今天继续研究linux系统编程,继上次对信号进行研究之后,这次开始一个新的章节-----管道,在正式开始之前,先把今天让自己很激动的事说一下,小小的兴奋,经过下周后自己的创业产品用户量就有一个质的飞越了,百万级的,虽说离最终的目标还有很远,但是,这说明自己团队最近几个月的辛苦付出是值得的,也让自己对这次的创业更加有期待了,小小骄傲一下,我的学习任务还得继续坚持,谁叫咱是搞技术的呢,言归正传,正入正题:

什么是管道:

实际上,我们在linux中经常会用到带"管道"的命令,如:

那管道的本质是什么呢?既然它是一个数据流,那就一定得要有一个缓冲区来保存数据, 所以说,我们可以把管道看成是具有固定大小的一个内核缓冲区。

管道限制:

关于上面提到的第二点,为啥只能用于具有共同祖先的进程呢?需要先理解下面的函数才能明白,所以先学习下面的用法,回过头来再来理解这句话。

匿名管道pipe:

创建管道后示意图:

回到之前提出的问题来,为啥管道只能用于具有共同祖先的进程呢?原因在于:管道的文件描述符其它进程是没有办法获取,只能通过子进程继承父进程得来了。

通过管道的这些文件描述符,我们就可以实现进程间的通信了,比如:子进程往管道的写入端点中写入数据,父进程可以从管道的读端点获取数据,下面就以实际代码来说明一下父子进程的数据传递:

  1. #include <unistd.h>
  2. #include <sys/stat.h>
  3. #include <sys/wait.h>
  4. #include <sys/types.h>
  5. #include <fcntl.h>
  6. #include <stdlib.h>
  7. #include <stdio.h>
  8. #include <errno.h>
  9. #include <string.h>
  10. #include <signal.h>
  11. #include <sys/time.h>
  12. #define ERR_EXIT(m) \
  13. do \
  14. { \
  15. perror(m); \
  16. exit(EXIT_FAILURE); \
  17. } while(0)
  18. int main(int argc, char *argv[])
  19. {
  20. int pipefd[2];
  21. if (pipe(pipefd) == -1)//创建一个管道之后,就会得到两个文件描述符
  22. ERR_EXIT("pipe error");
  23. pid_t pid;
  24. pid = fork();//创建父子进程来演示数据通讯的目的
  25. if (pid == -1)
  26. ERR_EXIT("fork error");
  27. if (pid == 0)
  28. {//子进程发送数据
  29. close(pipefd[0]);//关闭子进程管道的读端,因为没有用
  30. write(pipefd[1], "hello", 5);//向子进程管道的写端传入数据
  31. close(pipefd[1]);//传递完之后将其关掉
  32. exit(EXIT_SUCCESS);
  33. }
  34. //父进程读取数据
  35. close(pipefd[1]);//关闭父进程的写端,因为没有用
  36. char buf[10] = {0};
  37. read(pipefd[0], buf, 10);//从管道的读端读入数据
  38. close(pipefd[0]);//关闭管道的读端
  39. printf("buf=%s\n", buf);
  40. return 0;
  41. }

编译运行:

这个通过管道达到进程间传递数据的例子比较简单,下面用程序来模拟下面的这个shell命令的效果:

我们可以用子进程来运行ls,父进程来运行wc -w命令,具体代码如下:

第一步:

  1. #include <unistd.h>
  2. #include <sys/stat.h>
  3. #include <sys/wait.h>
  4. #include <sys/types.h>
  5. #include <fcntl.h>
  6. #include <stdlib.h>
  7. #include <stdio.h>
  8. #include <errno.h>
  9. #include <string.h>
  10. #include <signal.h>
  11. #include <sys/time.h>
  12. #define ERR_EXIT(m) \
  13. do \
  14. { \
  15. perror(m); \
  16. exit(EXIT_FAILURE); \
  17. } while(0)
  18. int main(int argc, char *argv[])
  19. {
  20. int pipefd[2];
  21. if (pipe(pipefd) == -1)
  22. ERR_EXIT("pipe error");
  23. pid_t pid;
  24. pid = fork();
  25. if (pid == -1)
  26. ERR_EXIT("fork error");
  27. if (pid == 0)
  28. {//子进程运行ls命令,
  29. execlp("ls", "ls", NULL);
  30. fprintf(stderr, "error execute ls\n");
  31. exit(EXIT_FAILURE);
  32. }
  33. //父进程运行wc -w命令
  34. execlp("wc", "wc", "-w", NULL);
  35. fprintf(stderr, "error execute wc\n");//如果执行execlp运行失败了,才会执行到这
  36. exit(EXIT_FAILURE);
  37. }

【说明】:关于execlp函数的使用,可以参考博文:linux系统编程之进程(三) - cexo - 博客园

第二步,重定向文件描述符,这是实现的关键:

因为ls命令标准是输出到标准输出设备(屏幕)当中,wc命令是从标准输入设备获取数据,而现在,我们希望ls命令输出到管道的写端,而wc命令是从管道的读端获取数据,那该怎么办呢?文件描述符的复制既可达到这个目的,具体代码如下:

  1. #include <unistd.h>
  2. #include <sys/stat.h>
  3. #include <sys/wait.h>
  4. #include <sys/types.h>
  5. #include <fcntl.h>
  6. #include <stdlib.h>
  7. #include <stdio.h>
  8. #include <errno.h>
  9. #include <string.h>
  10. #include <signal.h>
  11. #include <sys/time.h>
  12. #define ERR_EXIT(m) \
  13. do \
  14. { \
  15. perror(m); \
  16. exit(EXIT_FAILURE); \
  17. } while(0)
  18. int main(int argc, char *argv[])
  19. {
  20. int pipefd[2];
  21. if (pipe(pipefd) == -1)
  22. ERR_EXIT("pipe error");
  23. pid_t pid;
  24. pid = fork();
  25. if (pid == -1)
  26. ERR_EXIT("fork error");
  27. if (pid == 0)
  28. {//子进程运行ls命令,
  29. dup2(pipefd[1], STDOUT_FILENO);//将标准输出复制到管道的写端,也就是说标准输出定位到了管道的写端
  30. close(pipefd[1]);//这时管道的读写端都没用了,将其关闭
  31. close(pipefd[0]);
  32. execlp("ls", "ls", NULL);//这时ls输出则为管道的写端了,由于文件描述符重定向了
  33. fprintf(stderr, "error execute ls\n");
  34. exit(EXIT_FAILURE);
  35. }
  36. //父进程运行wc -w命令
  37. dup2(pipefd[0], STDIN_FILENO);//将标准输入重定向管道的读端,所以wc命令这时就会从管道的读端来获取数据喽
  38. close(pipefd[0]);
  39. close(pipefd[1]);
  40. execlp("wc", "wc", "-w", NULL);
  41. fprintf(stderr, "error execute wc\n");//如果执行execlp运行失败了,才会执行到这
  42. exit(EXIT_FAILURE);
  43. }

编译运行:

下面再来看一个有关文件描述符复制的程序,先看效果,再来分析其原理:

  1. #include <unistd.h>
  2. #include <sys/stat.h>
  3. #include <sys/wait.h>
  4. #include <sys/types.h>
  5. #include <fcntl.h>
  6. #include <stdlib.h>
  7. #include <stdio.h>
  8. #include <errno.h>
  9. #include <string.h>
  10. #include <signal.h>
  11. #include <sys/time.h>
  12. #define ERR_EXIT(m) \
  13. do \
  14. { \
  15. perror(m); \
  16. exit(EXIT_FAILURE); \
  17. } while(0)
  18. int main(int argc, char *argv[])
  19. {
  20. close(0);
  21. open("Makefile", O_RDONLY);
  22. close(1);
  23. open("Makefile2", O_WRONLY | O_CREAT | O_TRUNC, 0644);
  24. execlp("cat", "cat", NULL);
  25. return 0;
  26. }

编译运行:

为啥能实现文件的拷贝效果呢?咱们来分析一下程序:

默认情况下:

而下面这句代码过后:

  1. close(0);
  2. open("Makefile", O_RDONLY);
  3. close(1);
  4. open("Makefile2", O_WRONLY | O_CREAT | O_TRUNC, 0644);

而最后一句关键代码如下:

execlp("cat", "cat", NULL);

我们通常用cat可以查看一个文件内容:

但是,如果cat不带参数,那是什么意思呢?

如效果所示,不带参数的cat命令实际上是从标准输入获取数据,写入到标准输出当中,所以也就是从Makefile文件获取数据,写入到Makefile2文件当中,如果Makefile2文件不存在则会主动创建一个,所以就实现了一个cp命令喽,是不是很有技巧。

【说明】:关于文件描述符的复制,可以参考博文:linux系统编程之文件与io(四) - cexo - 博客园

好了,今天的内容学到这,虽说内容不多,但是得好好理解,下回见!!

关注个人公众号,获得实时推送

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/462663
推荐阅读
相关标签
  

闽ICP备14008679号