使用匿名管道

一、什么是管道
如果你使用过Linux的命令,那么对于管道这个名词你一定不会感觉到陌生,因为我们通常通过符号“|"来使用管道,但是管理的真正定义是什么呢?管道是一个进程连接数据流到另一个进程的通道,它通常是用作把一个进程的输出通过管道连接到另一个进程的输入。
举个例子,在shell中输入命令:ls -l | grep string,我们知道ls命令(其实也是一个进程)会把当前目录中的文件都列出来,但是它不会直接输出,而是把本来要输出到屏幕上的数据通过管道输出到grep这个进程中,作为grep这个进程的输入,然后这个进程对输入的信息进行筛选,把存在string的信息的字符串(以行为单位)打印在屏幕上。
二、使用popen函数
1、popen函数和pclose函数介绍
有静就有动,有开就有关,与此相同,与popen函数相对应的函数是pclose函数,它们的原型如下:
  1. #include <stdio.h>
  2. FILE* popen (const char *command, const char *open_mode);
  3. int pclose(FILE *stream_to_close);
poen函数允许一个程序将另一个程序作为新进程来启动,并可以传递数据给它或者通过它接收数据。command是要运行的程序名和相应的参数。open_mode只能是"r(只读)"和"w(只写)"的其中之一。注意,popen函数的返回值是一个FILE类型的指针,而Linux把一切都视为文件,也就是说我们可以使用stdio I/O库中的文件处理函数来对其进行操作。
如果open_mode是"r",主调用程序就可以使用被调用程序的输出,通过函数返回的FILE指针,就可以能过stdio函数(如fread)来读取程序的输出;如果open_mode是"w",主调用程序就可以向被调用程序发送数据,即通过stdio函数(如fwrite)向被调用程序写数据,而被调用程序就可以在自己的标准输入中读取这些数据。
pclose函数用于关闭由popen创建出的关联文件流。pclose只在popen启动的进程结束后才返回,如果调用pclose时被调用进程仍在运行,pclose调用将等待该进程结束。它返回关闭的文件流所在进程的退出码。
2、例子
很多时候,我们根本就不知道输出数据的长度,为了避免定义一个非常大的数组作为缓冲区,我们可以以块的方式来发送数据,一次读取一个块的数据并发送一个块的数据,直到把所有的数据都发送完。下面的例子就是采用这种方式的数据读取和发送方式。源文件名为popen.c,代码如下:
  1. #include <unistd.h>
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include <string.h>
  5. int main()
  6. {
  7. FILE *read_fp = NULL;
  8. FILE *write_fp = NULL;
  9. char buffer[BUFSIZ + 1];
  10. int chars_read = 0;
  11. //初始化缓冲区
  12. memset(buffer, '\0', sizeof(buffer));
  13. //打开ls和grep进程
  14. read_fp = popen("ls -l", "r");
  15. write_fp = popen("grep rwxrwxr-x", "w");
  16. //两个进程都打开成功
  17. if(read_fp && write_fp)
  18. {
  19. //读取一个数据块
  20. chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
  21. while(chars_read > 0)
  22. {
  23. buffer[chars_read] = '\0';
  24. //把数据写入grep进程
  25. fwrite(buffer, sizeof(char), chars_read, write_fp);
  26. //还有数据可读,循环读取数据,直到读完所有数据
  27. chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
  28. }
  29. //关闭文件流
  30. pclose(read_fp);
  31. pclose(write_fp);
  32. exit(EXIT_SUCCESS);
  33. }
  34. exit(EXIT_FAILURE);
  35. }

3、popen的实现方式及优缺点

当请求popen调用运行一个程序时,它首先启动shell,即系统中的sh命令,然后将command字符串作为一个参数传递给它。
这样就带来了一个优点和一个缺点。优点是:在Linux中所有的参数扩展都是由shell来完成的。所以在启动程序(command中的命令程序)之前先启动shell来分析命令字符串,也就可以使各种shell扩展(如通配符)在程序启动之前就全部完成,这样我们就可以通过popen启动非常复杂的shell命令。
而它的缺点就是:对于每个popen调用,不仅要启动一个被请求的程序,还要启动一个shell,即每一个popen调用将启动两个进程,从效率和资源的角度看,popen函数的调用比正常方式要慢一些。
三、pipe调用
如果说popen是一个高级的函数,pipe则是一个底层的调用。与popen函数不同的是,它在两个进程之间传递数据不需要启动一个shell来解释请求命令,同时它还提供对读写数据的更多的控制。
pipe函数的原型如下:
  1. #include <unistd.h>
  2. int pipe(int file_descriptor[2]);
我们可以看到pipe函数的定义非常特别,该函数在数组中墙上两个新的文件描述符后返回0,如果返回返回-1,并设置errno来说明失败原因。
数组中的两个文件描述符以一种特殊的方式连接起来,数据基于先进先出的原则,写到file_descriptor[1]的所有数据都可以从file_descriptor[0]读回来。由于数据基于先进先出的原则,所以读取的数据和写入的数据是一致的。
特别提醒:
1、从函数的原型我们可以看到,它跟popen函数的一个重大区别是,popen函数是基于文件流(FILE)工作的,而pipe是基于文件描述符工作的,所以在使用pipe后,数据必须要用底层的read和write调用来读取和发送。
2、不要用file_descriptor[0]写数据,也不要用file_descriptor[1]读数据,其行为未定义的,但在有些系统上可能会返回-1表示调用失败。数据只能从file_descriptor[0]中读取,数据也只能写入到file_descriptor[1],不能倒过来。
例子:
首先,我们在原先的进程中创建一个管道,然后再调用fork创建一个新的进程,最后通过管道在两个进程之间传递数据。源文件名为pipe.c,代码如下:
  1. #include <unistd.h>
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include <string.h>
  5. int main()
  6. {
  7. int data_processed = 0;
  8. int filedes[2];
  9. const char data[] = "Hello pipe!";
  10. char buffer[BUFSIZ + 1];
  11. pid_t pid;
  12. //清空缓冲区
  13. memset(buffer, '\0', sizeof(buffer));
  14. if(pipe(filedes) == 0)
  15. {
  16. //创建管道成功
  17. //通过调用fork创建子进程
  18. pid = fork();
  19. if(pid == -1)
  20. {
  21. fprintf(stderr, "Fork failure");
  22. exit(EXIT_FAILURE);
  23. }
  24. if(pid == 0)
  25. {
  26. //子进程中
  27. //读取数据
  28. data_processed = read(filedes[0], buffer, BUFSIZ);
  29. printf("Read %d bytes: %s\n", data_processed, buffer);
  30. exit(EXIT_SUCCESS);
  31. }
  32. else
  33. {
  34. //父进程中
  35. //写数据
  36. data_processed = write(filedes[1], data, strlen(data));
  37. printf("Wrote %d bytes: %s\n", data_processed, data);
  38. //休眠2秒,主要是为了等子进程先结束,这样做也只是纯粹为了输出好看而已
  39. //父进程其实没有必要等等子进程结束
  40. sleep(2);
  41. exit(EXIT_SUCCESS);
  42. }
  43. }
  44. exit(EXIT_FAILURE);
  45. }
使用匿名管道,则通信的进程之间需要一个父子关系,通信的两个进程一定是由一个共同的祖先进程启动。但是匿名管道没有上面说到的数据交叉的问题。 
与使用匿名管道相比,我们可以看到fifowrite.exe和fiforead.exe这两个进程是没有什么必然的联系的

使用命名管道

一、什么是命名管道
命名管道也被称为FIFO文件,它是一种特殊类型的文件,它在文件系统中以文件名的形式存在,但是它的行为却和之前所讲的没有名字的管道(匿名管道)类似。
由于Linux中所有的事物都可被视为文件,所以对命名管道的使用也就变得与文件操作非常的统一,也使它的使用非常方便,同时我们也可以像平常的文件名一样在命令中使用。
二、创建命名管道
我们可以使用两下函数之一来创建一个命名管道,他们的原型如下:
  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. int mkfifo(const char *filename, mode_t mode);
  4. int mknod(const char *filename, mode_t mode | S_IFIFO, (dev_t)0);
这两个函数都能创建一个FIFO文件,注意是创建一个真实存在于文件系统中的文件,filename指定了文件名,而mode则指定了文件的读写权限。
mknod是比较老的函数,而使用mkfifo函数更加简单和规范,所以建议在可能的情况下,尽量使用mkfifo而不是mknod。
三、访问命名管道
1、打开FIFO文件
与打开其他文件一样,FIFO文件也可以使用open调用来打开。注意,mkfifo函数只是创建一个FIFO文件,要使用命名管道还是将其打开。
但是有两点要注意,1、就是程序不能以O_RDWR模式打开FIFO文件进行读写操作,而其行为也未明确定义,因为如一个管道以读/写方式打开,进程就会读回自己的输出,同时我们通常使用FIFO只是为了单向的数据传递。2、就是传递给open调用的是FIFO的路径名,而不是正常的文件。
打开FIFO文件通常有四种方式,
  1. open(const char *path, O_RDONLY);//1
  2. open(const char *path, O_RDONLY | O_NONBLOCK);//2
  3. open(const char *path, O_WRONLY);//3
  4. open(const char *path, O_WRONLY | O_NONBLOCK);//4
在open函数的调用的第二个参数中,你看到一个陌生的选项O_NONBLOCK,选项O_NONBLOCK表示非阻塞,加上这个选项后,表示open调用是非阻塞的,如果没有这个选项,则表示open调用是阻塞的。
open调用的阻塞是什么一回事呢?很简单,对于以只读方式(O_RDONLY)打开的FIFO文件,如果open调用是阻塞的(即第二个参数为O_RDONLY),除非有一个进程以写方式打开同一个FIFO,否则它不会返回;如果open调用是非阻塞的的(即第二个参数为O_RDONLY | O_NONBLOCK),则即使没有其他进程以写方式打开同一个FIFO文件,open调用将成功并立即返回。
对于以只写方式(O_WRONLY)打开的FIFO文件,如果open调用是阻塞的(即第二个参数为O_WRONLY),open调用将被阻塞,直到有一个进程以只读方式打开同一个FIFO文件为止;如果open调用是非阻塞的(即第二个参数为O_WRONLY | O_NONBLOCK),open总会立即返回,但如果没有其他进程以只读方式打开同一个FIFO文件,open调用将返回-1,并且FIFO也不会被打开。
四、使用FIFO实现进程间的通信
说了这么多,下面就用一个例子程序来说明一下,两个进程如何通过FIFO实现通信吧。这里有两个源文件,一个fifowrite.c,它在需要时创建管道,然后向管道写入数据,数据由文件Data.txt提供,大小为10M,内容全是字符‘0’。另一个源文件为fiforead.c,它从FIFO中读取数据,并把读到的数据保存到另一个文件DataFormFIFO.txt中。为了让程序更加简洁,忽略了有些函数调用是否成功的检查。
fifowrite.c的源代码如下:
  1. #include <unistd.h>
  2. #include <stdlib.h>
  3. #include <fcntl.h>
  4. #include <limits.h>
  5. #include <sys/types.h>
  6. #include <sys/stat.h>
  7. #include <stdio.h>
  8. #include <string.h>
  9. int main()
  10. {
  11. const char *fifo_name = "/tmp/my_fifo";
  12. int pipe_fd = -1;
  13. int data_fd = -1;
  14. int res = 0;
  15. const int open_mode = O_WRONLY;
  16. int bytes_sent = 0;
  17. char buffer[PIPE_BUF + 1];
  18. if(access(fifo_name, F_OK) == -1)
  19. {
  20. //管道文件不存在
  21. //创建命名管道
  22. res = mkfifo(fifo_name, 0777);
  23. if(res != 0)
  24. {
  25. fprintf(stderr, "Could not create fifo %s\n", fifo_name);
  26. exit(EXIT_FAILURE);
  27. }
  28. }
  29. printf("Process %d opening FIFO O_WRONLY\n", getpid());
  30. //以只写阻塞方式打开FIFO文件,以只读方式打开数据文件
  31. pipe_fd = open(fifo_name, open_mode);
  32. data_fd = open("Data.txt", O_RDONLY);
  33. printf("Process %d result %d\n", getpid(), pipe_fd);
  34. if(pipe_fd != -1)
  35. {
  36. int bytes_read = 0;
  37. //向数据文件读取数据
  38. bytes_read = read(data_fd, buffer, PIPE_BUF);
  39. buffer[bytes_read] = '\0';
  40. while(bytes_read > 0)
  41. {
  42. //向FIFO文件写数据
  43. res = write(pipe_fd, buffer, bytes_read);
  44. if(res == -1)
  45. {
  46. fprintf(stderr, "Write error on pipe\n");
  47. exit(EXIT_FAILURE);
  48. }
  49. //累加写的字节数,并继续读取数据
  50. bytes_sent += res;
  51. bytes_read = read(data_fd, buffer, PIPE_BUF);
  52. buffer[bytes_read] = '\0';
  53. }
  54. close(pipe_fd);
  55. close(data_fd);
  56. }
  57. else
  58. exit(EXIT_FAILURE);
  59. printf("Process %d finished\n", getpid());
  60. exit(EXIT_SUCCESS);
  61. }
源文件fiforead.c的代码如下:
  1. #include <unistd.h>
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include <fcntl.h>
  5. #include <sys/types.h>
  6. #include <sys/stat.h>
  7. #include <limits.h>
  8. #include <string.h>
  9. int main()
  10. {
  11. const char *fifo_name = "/tmp/my_fifo";
  12. int pipe_fd = -1;
  13. int data_fd = -1;
  14. int res = 0;
  15. int open_mode = O_RDONLY;
  16. char buffer[PIPE_BUF + 1];
  17. int bytes_read = 0;
  18. int bytes_write = 0;
  19. //清空缓冲数组
  20. memset(buffer, '\0', sizeof(buffer));
  21. printf("Process %d opening FIFO O_RDONLY\n", getpid());
  22. //以只读阻塞方式打开管道文件,注意与fifowrite.c文件中的FIFO同名
  23. pipe_fd = open(fifo_name, open_mode);
  24. //以只写方式创建保存数据的文件
  25. data_fd = open("DataFormFIFO.txt", O_WRONLY|O_CREAT, 0644);
  26. printf("Process %d result %d\n",getpid(), pipe_fd);
  27. if(pipe_fd != -1)
  28. {
  29. do
  30. {
  31. //读取FIFO中的数据,并把它保存在文件DataFormFIFO.txt文件中
  32. res = read(pipe_fd, buffer, PIPE_BUF);
  33. bytes_write = write(data_fd, buffer, res);
  34. bytes_read += res;
  35. }while(res > 0);
  36. close(pipe_fd);
  37. close(data_fd);
  38. }
  39. else
  40. exit(EXIT_FAILURE);
  41. printf("Process %d finished, %d bytes read\n", getpid(), bytes_read);
  42. exit(EXIT_SUCCESS);
  43. }

但是为了数据的安全,我们很多时候要采用阻塞的FIFO,让写操作变成原子操作。

转载于:https://www.cnblogs.com/dk666/p/7412527.html

C/C++ 进程间通信 管道相关推荐

  1. Linux下进程间通信——管道

    进程间通信 一.进程间通信 1.进程间通信--管道 (1)有名管道的创建 (2)有名管道的操作 写操作 读操作 发生阻塞和关闭程序的几种情况 2.无名管道的创建 (1)无名管道的操作 3.管道读取数据 ...

  2. 操作系统实验报告9:进程间通信—管道和 socket 通信

    操作系统实验报告9 实验内容 实验内容:进程间通信-管道和 socket 通信. 编译运行课件 Lecture11 例程代码: alg.11-3-socket-input-2.c alg.11-4-s ...

  3. #Linux#进程间通信# 管道(pipe)-标准流管道pipe

    在#Linux#进程间通信# 管道(pipe)-普通管道pipe中,我们很容易可以看出普通管道一是单工,即只能单向传输,而标准流管道针对匿名管道PIPE一系列封装.返回文件流.只不过返回的文件流无法使 ...

  4. linux学习:进程间通信—管道

    1.进程间通信当中一种比較简单的方法是管道操作 /*========================================================================== ...

  5. Unix网络编程--进程间通信--管道通信

          所有式样的Unix都提供管道,它由 pipe 函数创建,提供一个单路(单向)数据流.       原型如下: #include <unistd.h> int pipe(int ...

  6. [OS] 进程间通信--管道

    管道是单向的.先进先出的.无结构的.固定大小的字节流,它把一个进程的标准输出和另一个进程的标准输入连接在一起.写进程在管道的尾端写入数据,读进程在管道的首端读出数据.数据读出后将从管道中移走,其它读进 ...

  7. Linux进程间通信——管道

    转自:http://www.cnblogs.com/feisky/archive/2010/03/24/1693484.html Linux进程间通信机制: 1.同一主机进程间通信机制: Unix方式 ...

  8. Linux进程间通信(管道、消息队列、共享内存、信号、信号量)

    目录 Linux进程间通信概述 1.管道 无名管道(pipe) 有名管道(fifo) 2.消息队列(msg) 消息队列的通信原理 消息队列相关api 消息队列收发数据 键值生成 消息队列移除 3.共享 ...

  9. Linux系统【四】进程间通信-管道

    进程间通信(IPC Interprocess Communication) 进程和进程之间的通信只能通过内核,在内核中提供一块缓冲区进行通信.内核提供的这种机制叫做IPC 在进程间完成数据传输需要借助 ...

  10. 进程间通信管道进阶篇:linux下dup/dup2函数的用法

    由于利用管道实现进程间通信,是通过创建两个文件描述符,但是描述符的初始化是通过随机的,就是从可用的文件描述符中取出,并将可用的文件描述符与file对象相关联,如果我们需要将管道的两头与其他的流相关时, ...

最新文章

  1. vant组件实现上传图片裁剪_如何用 120 行代码,实现交互完整的拖拽上传组件?...
  2. CUDA学习3-GridBlock
  3. Bootstrap table表格
  4. 通过脚本案例学习shell(五) 通过创建DNS脚本一步一步教你将一个普通脚本规范到一个生产环境脚本...
  5. 用XFTP向Linux上传文件时出现Permission is not allowed
  6. 去除字符串中的html标记及标记中的内容
  7. Knative Serving 健康检查机制分析
  8. ios 设备获取idfa_超4成用户选择升级iOS 14,35%苹果设备已无法获取IDFA
  9. php中update语句修改多个字段,Myabtis中批量更新update多字段
  10. 扎哈遗作:北京大兴机场,耗资800亿,被英国《卫报》评为新世界七大奇迹!...
  11. 【CodeForces - 1102C 】Doors Breaking and Repairing (思维,简单博弈)
  12. 面向中文自然语言处理的60余类系统开源实践项目与工业探索索引
  13. php 调用日历控制,基于ThinkPHP实现的日历功能实例详解
  14. SQL SERVER 2000数据库置疑处理
  15. Java对接网易云信IM 即时通讯
  16. 【Python】一篇文章学习Pandas包 Pandas Series、DataFrame 对比学习
  17. 基于selenium的码市外包信息爬虫
  18. ios xmpp 发送语音图片解决方案
  19. android 高德地图sha1,Android调试高德SDK,如何获取SHA1?
  20. 删除Github中已有仓库或文件

热门文章

  1. 自然语言处理理论与实战
  2. Dynamics CRM2013 6.1.1.1143版本号插件注冊器的一个bug
  3. hadoop安装报错
  4. Struts2 工作流程
  5. (转)Android 如何建立你的菜单
  6. matlab指定的数据范围无效,c – 如何检查迭代器指定的范围是否有效?
  7. 大学计算机应用基础教程第三章,计算机应用基础教程 李健苹 计算机应用基础教程 第3章...
  8. json html双引号,当gethtml方法返回json时,json中的字符串如果存在双引号,会破坏json的格式, 如:quot;...
  9. Linux网络编程——tcp、udp迭代服务器
  10. ribbon基于接口配置超时_Spring Cloud第二篇:服务消费者RestTemplate+Ribbon