热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

Linux通过匿名管道进行进程间通信

这篇文章主要介绍了Linux通过匿名管道进行进程间通信,介绍了什么是管道,popen函数,pipe调用等相关内容,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下

本文研究的主要是Linux通过匿名管道进行进程间通信的相关内容,具体介绍如下。

在前面,介绍了一种进程间的通信方式:使用信号,我们创建通知事件,并通过它引起响应,但传递的信息只是一个信号值。这里将介绍另一种进程间通信的方式——匿名管道,通过它进程间可以交换更多有用的数据。

一、什么是管道

如果你使用过Linux的命令,那么对于管道这个名词你一定不会感觉到陌生,因为我们通常通过符号“|"来使用管道,但是管理的真正定义是什么呢?管道是一个进程连接数据流到另一个进程的通道,它通常是用作把一个进程的输出通过管道连接到另一个进程的输入。

举个例子,在shell中输入命令:ls -l | grep string,我们知道ls命令(其实也是一个进程)会把当前目录中的文件都列出来,但是它不会直接输出,而是把本来要输出到屏幕上的数据通过管道输出到grep这个进程中,作为grep这个进程的输入,然后这个进程对输入的信息进行筛选,把存在string的信息的字符串(以行为单位)打印在屏幕上。

二、使用popen函数

1、popen函数和pclose函数介绍

有静就有动,有开就有关,与此相同,与popen函数相对应的函数是pclose函数,它们的原型如下:

#include  
FILE* popen (const char *command, const char *open_mode); 
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,代码如下:

#include  
#include  
#include  
#include  
 
int main() 
{ 
  FILE *read_fp = NULL; 
  FILE *write_fp = NULL; 
  char buffer[BUFSIZ + 1]; 
  int chars_read = 0; 
   
  //初始化缓冲区 
  memset(buffer, '\0', sizeof(buffer)); 
  //打开ls和grep进程 
  read_fp = popen("ls -l", "r"); 
  write_fp = popen("grep rwxrwxr-x", "w"); 
  //两个进程都打开成功 
  if(read_fp && write_fp) 
  { 
    //读取一个数据块 
    chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp); 
    while(chars_read > 0) 
    { 
      buffer[chars_read] = '\0'; 
      //把数据写入grep进程 
      fwrite(buffer, sizeof(char), chars_read, write_fp); 
      //还有数据可读,循环读取数据,直到读完所有数据 
      chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp); 
    } 
    //关闭文件流 
    pclose(read_fp); 
    pclose(write_fp); 
    exit(EXIT_SUCCESS); 
  } 
  exit(EXIT_FAILURE); 
} 

运行结果如下:

从运行结果来看,达到了信息筛选的目的。程序在进程ls中读取数据,再把数据发送到进程grep中进行筛选处理,相当于在shell中直接输入命令:ls -l | grep rwxrwxr-x。

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

当请求popen调用运行一个程序时,它首先启动shell,即系统中的sh命令,然后将command字符串作为一个参数传递给它。

这样就带来了一个优点和一个缺点。优点是:在Linux中所有的参数扩展都是由shell来完成的。所以在启动程序(command中的命令程序)之前先启动shell来分析命令字符串,也就可以使各种shell扩展(如通配符)在程序启动之前就全部完成,这样我们就可以通过popen启动非常复杂的shell命令。

而它的缺点就是:对于每个popen调用,不仅要启动一个被请求的程序,还要启动一个shell,即每一个popen调用将启动两个进程,从效率和资源的角度看,popen函数的调用比正常方式要慢一些。

三、pipe调用

如果说popen是一个高级的函数,pipe则是一个底层的调用。与popen函数不同的是,它在两个进程之间传递数据不需要启动一个shell来解释请求命令,同时它还提供对读写数据的更多的控制。

pipe函数的原型如下:

#include  
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,代码如下:

#include  
#include  
#include  
#include  
 
int main() 
{ 
  int data_processed = 0; 
  int filedes[2]; 
  const char data[] = "Hello pipe!"; 
  char buffer[BUFSIZ + 1]; 
  pid_t pid; 
  //清空缓冲区 
  memset(buffer, '\0', sizeof(buffer)); 
 
  if(pipe(filedes) == 0) 
  { 
    //创建管道成功 
    //通过调用fork创建子进程 
    pid = fork(); 
    if(pid == -1) 
    { 
      fprintf(stderr, "Fork failure"); 
      exit(EXIT_FAILURE); 
    } 
    if(pid == 0) 
    { 
      //子进程中 
      //读取数据 
      data_processed = read(filedes[0], buffer, BUFSIZ); 
      printf("Read %d bytes: %s\n", data_processed, buffer); 
      exit(EXIT_SUCCESS); 
    } 
    else 
    { 
      //父进程中 
      //写数据 
      data_processed = write(filedes[1], data, strlen(data)); 
      printf("Wrote %d bytes: %s\n", data_processed, data); 
      //休眠2秒,主要是为了等子进程先结束,这样做也只是纯粹为了输出好看而已 
      //父进程其实没有必要等等子进程结束 
      sleep(2); 
      exit(EXIT_SUCCESS); 
    } 
  } 
  exit(EXIT_FAILURE); 
} 

运行结果为:

可见,子进程读取了父进程写到filedes[1]中的数据,如果在父进程中没有sleep语句,父进程可能在子进程结束前结束,这样你可能将看到两个输入之间有一个命令提示符分隔。

四、把管道用作标准输入和标准输出

下面来介绍一种用管道来连接两个进程的更简洁方法,我们可以把文件描述符设置为一个已知值,一般是标准输入0或标准输出1。这样做最大的好处是可以调用标准程序,即那些不需要以文件描述符为参数的程序。

为了完成这个工作,我们还需要两个函数的辅助,它们分别是dup函数或dup2函数,它们的原型如下

#include  
int dup(int file_descriptor); 
int dup2(int file_descriptor_one, int file_descriptor_two); 

dup调用创建一个新的文件描述符与作为它的参数的那个已有文件描述符指向同一个文件或管道。对于dup函数而言,新的文件描述总是取最小的可用值。而dup2所创建的新文件描述符或者与int file_descriptor_two相同,或者是第一个大于该参数的可用值。所以当我们首先关闭文件描述符0后调用dup,那么新的文件描述符将是数字0.

例子

在下面的例子中,首先打开管道,然后fork一个子进程,然后在子进程中,使标准输入指向读管道,然后关闭子进程中的读管道和写管道,只留下标准输入,最后调用execlp函数来启动一个新的进程od,但是od并不知道它的数据来源是管道还是终端。父进程则相对简单,它首先关闭读管道,然后在写管道中写入数据,再关闭写管道就完成了它的任务。源文件为pipe2.c,代码如下:

#include  
#include  
#include  
#include  
 
int main() 
{ 
  int data_processed = 0; 
  int pipes[2]; 
  const char data[] = "123"; 
  pid_t pid; 
 
  if(pipe(pipes) == 0) 
  { 
    pid = fork(); 
    if(pid == -1) 
    { 
      fprintf(stderr, "Fork failure!\n"); 
      exit(EXIT_FAILURE); 
    } 
    if(pid == 0) 
    { 
      //子进程中 
      //使标准输入指向fildes[0] 
      close(0); 
      dup(pipes[0]); 
      //关闭pipes[0]和pipes[1],只剩下标准输入 
      close(pipes[0]); 
      close(pipes[1]); 
      //启动新进程od 
      execlp("od", "od", "-c", 0); 
      exit(EXIT_FAILURE); 
    } 
    else 
    { 
      //关闭pipes[0],因为父进程不用读取数据 
      close(pipes[0]); 
      data_processed = write(pipes[1], data, strlen(data)); 
      //写完数据后,关闭pipes[1] 
      close(pipes[1]); 
      printf("%d - Wrote %d bytes\n", getpid(), data_processed); 
    } 
  } 
  exit(EXIT_SUCCESS); 
} 

运行结果为:

从运行结果中可以看出od进程正确地完成了它的任务,与在shell中直接输入od -c和123的效果一样。

五、关于管道关闭后的读操作的讨论

现在有这样一个问题,假如父进程向管道file_pipe[1]写数据,而子进程在管道file_pipe[0]中读取数据,当父进程没有向file_pipe[1]写数据时,子进程则没有数据可读,则子进程会发生什么呢?再者父进程把file_pipe[1]关闭了,子进程又会有什么反应呢?

当写数据的管道没有关闭,而又没有数据可读时,read调用通常会阻塞,但是当写数据的管道关闭时,read调用将会返回0而不是阻塞。注意,这与读取一个无效的文件描述符不同,read一个无效的文件描述符返回-1。

六、匿名管道的缺陷

看了这么多相信大家也知道它的一个缺点,就是通信的进程,它们的关系一定是父子进程的关系,这就使得它的使用受到了一点的限制,但是我们可以使用命名管道来解决这个问题。命名管道将在下一篇文章:Linux进程间通信——使用命名管道中介绍。

总结

以上就是本文关于Linux通过匿名管道进行进程间通信的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!


推荐阅读
  • 手指触控|Android电容屏幕驱动调试指南
    手指触控|Android电容屏幕驱动调试指南 ... [详细]
  • Linux Shell变量初探:初始值解析与使用指南
    本文探讨了Linux Shell中变量的基本概念及其在BASH中的应用。变量是用于存储可变数据的标识符,能够代表不同的值。文章详细介绍了BASH shell的主要优势,包括强大的命令编辑能力、自动补全功能、命令别名设置、作业控制以及前后台任务管理。此外,还涵盖了编程脚本编写和通配符的使用方法,为初学者提供了全面的指导。 ... [详细]
  • 本文提供了详细的 Filebeat 部署指南,涵盖了解压安装包、配置文件编辑以及启动服务等关键步骤。具体操作包括使用 `tar -zxvf filebeat-6.1.1-linux-x86_64.tar.gz` 解压安装包,并通过 `vi /home/elk/filebeat-6.4.2-linux/filebeat.yml` 编辑配置文件。此外,文章还介绍了最佳实践,帮助用户确保 Filebeat 的高效运行和数据传输的可靠性。 ... [详细]
  • PHP与MySQL的Web应用开发技术深入解析
    PHP与MySQL的Web应用开发技术深入解析 ... [详细]
  • 《我的世界》Java版与Windows 10版(基岩版)有何不同?
    《我的世界》Java版与Windows 10版(基岩版)有何不同? ... [详细]
  • 在64位Linux系统上编译GCC时,可能会遇到一个错误,即在构建共享库过程中,`.libs/alloc.o` 文件中的重定位 `R_X86_64_32` 无法应用于 `.rodata.str1.8` 段。这一问题通常与目标文件的地址空间限制有关,需要通过调整编译选项或修改源代码来解决。具体来说,可以尝试使用 `-fPIC` 选项以生成位置无关代码,或者检查相关对象文件的链接方式。 ... [详细]
  • 在 Kubernetes 中,Pod 的调度通常由集群的自动调度策略决定,这些策略主要关注资源充足性和负载均衡。然而,在某些场景下,用户可能需要更精细地控制 Pod 的调度行为,例如将特定的服务(如 GitLab)部署到特定节点上,以提高性能或满足特定需求。本文深入解析了 Kubernetes 的亲和性调度机制,并探讨了多种优化策略,帮助用户实现更高效、更灵活的资源管理。 ... [详细]
  • Linux系统防火墙启用与端口开放详细指南及操作流程
    在Linux系统中,启用防火墙并开放特定端口是确保网络安全的重要步骤。本文详细介绍了防火墙的配置方法和操作流程,包括如何解决在使用 `service iptables save` 命令时遇到的常见问题,如命令不支持基本的LSB动作(启动、停止等)的情况。此外,还提供了多种解决方案和最佳实践,帮助用户高效地管理和维护系统安全。 ... [详细]
  • 在网络故障排查中,tcpdump 是一款强大的工具,尤其在 Linux 环境下。尽管开发环境中问题较少,但在测试或生产环境中,往往会遇到各种难以预料的异常情况。通过在问题发生的环境中启用 tcpdump 进行抓包,并重现问题,可以获取到宝贵的原始数据,为问题的诊断提供关键线索。本文将详细介绍如何使用 tcpdump 进行实战操作,帮助读者掌握这一技能。 ... [详细]
  • 在Linux系统中,针对USB输入设备(如鼠标和电容触摸屏)的动力分配后,自动创建的input节点可能导致事件处理不稳定。本文探讨了如何在Qt开发环境中,通过合理配置设备事件处理器,确保USB输入设备的稳定性和可靠性。具体实例包括使用udev规则进行设备节点的持久化管理,以及通过事件监听机制优化事件处理流程。 ... [详细]
  • 探究大数据环境下Kafka实现高性能的几个关键因素
    在大数据环境下,Kafka能够实现高性能的关键因素在于其独特的设计和优化策略。尽管Kafka的消息存储在磁盘上,这通常被认为会降低性能,但通过高效的文件管理和批量处理机制,Kafka能够在高吞吐量和低延迟之间取得平衡。此外,Kafka还利用了零拷贝技术、压缩算法和异步IO等手段,进一步提升了系统的整体性能。这些技术不仅保证了数据的可靠性和持久性,还使得Kafka成为处理大规模实时数据流的理想选择。 ... [详细]
  • 在VMware虚拟机中部署带有中文图形界面的CentOS 7 Linux系统
    本文详细介绍了在VMware虚拟机中部署带有中文图形界面的CentOS 7 Linux系统的步骤。首先,通过“文件”菜单选择“新建虚拟机”并进入自定义设置。接着,在硬盘兼容性选项中选择默认设置。为了更好地进行Linux操作系统的安装练习,建议选择稍后安装操作系统,并在虚拟机安装完成后,根据实际需求删除不必要的硬件组件。此外,本文还提供了详细的配置参数和注意事项,帮助用户顺利完成整个部署过程。 ... [详细]
  • 深入解析 Linux 内核中的 signal.c 文件:从泛读到精析
    本文将对 Linux 内核中的 `signal.c` 文件进行深入解析,从泛读到精析,全面解读其核心功能与实现机制。通过详细分析代码,探讨信号处理的内部运作,帮助读者更好地理解 Linux 内核的信号处理机制。相关学习资料已整理在 GitHub 仓库:https://github.com/GreyZhang/little_bits_of_linux。 ... [详细]
  • 在Linux系统中,通过调整内存配置可以有效提升程序运行时的性能。本文将探讨如何在Linux环境下优化内存分配,特别是针对使用Go语言开发的应用程序,提供实用的技巧和最佳实践,帮助Golang开发者提高程序的稳定性和效率。 ... [详细]
  • 在 CentOS 6.6 系统中搭建 MONO 和 Jexus 以支持 ASP.NET 及 MVC 应用的运行环境配置指南
    本文提供了在 CentOS 6.6 系统上配置 MONO 和 Jexus 以支持 ASP.NET 及 MVC 应用的详细步骤。首先,确保本机环境为 CentOS 6.6,并使用阿里云的 YUM 源来安装必要的软件包,包括 gcc、gcc-c++、bison、pkgconfig 和 glib2-devel。这些软件包是构建和运行 MONO 环境的基础,确保系统能够顺利支持 ASP.NET 和 MVC 应用的部署和运行。 ... [详细]
author-avatar
个信2502894627
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有