进程间通信 - 管道

管道是两个或多个相关或相互关联的进程之间的通信媒介。它可以位于一个进程内,也可以是子进程与父进程之间的通信。通信也可以是多级的,例如父进程、子进程和孙进程之间的通信等。通信是通过一个进程写入管道而另一个进程从管道读取来实现的。要实现管道系统调用,请创建两个文件,一个用于写入文件,另一个用于从文件读取。

管道机制可以通过实时场景来查看,例如用管道将水灌入某个容器(例如水桶),然后有人用杯子取水。灌水过程就是写入管道,而读取过程就是从管道取水。这意味着一个输出(水)是另一个输出(桶)的输入。

Pipe with one
#include<unistd.h>

int pipe(int pipedes[2]);

此系统调用将创建一个用于单向通信的管道,即,它创建两个描述符,第一个用于从管道读取,另一个用于写入管道。

描述符 pipedes[0] 用于读取,pipedes[1] 用于写入。写入 pipedes[1] 的任何内容都可以从 pipedes[0] 中读取。

此调用在成功时返回零,在失败时返回 -1。要了解失败的原因,请检查 errno 变量或 perror() 函数。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

尽管文件的基本操作是读取和写入,但在执行操作之前打开文件并在完成所需操作后关闭文件是必不可少的。通常,默认情况下,每个进程打开 3 个描述符,分别用于输入(标准输入 - stdin)、输出(标准输出 - stdout)和错误(标准错误 - stderr),文件描述符分别为 0、1 和 2。

此系统调用将返回一个文件描述符,用于进一步的读/写/查找(lseek)文件操作。通常文件描述符从 3 开始,并随着打开的文件数量增加一个数字。

传递给 open 系统调用的参数是路径名(相对或绝对路径)、提及打开文件目的的标志(例如,打开以进行读取、O_RDONLY、写入、O_WRONLY、读写、O_RDWR、附加到现有文件 O_APPEND、创建文件、如果不存在则使用 O_CREAT 等)以及为用户或所有者/组/其他人提供读/写/执行权限的所需模式。模式可以用符号表示。

读取 - 4、写入 - 2 和执行 - 1。

例如:八进制值(以 0 开头),0764 表示所有者具有读取、写入和执行权限,组具有读取和写入权限,其他具有读取权限。这也可以表示为 S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH,表示 0700|0040|0020|0004 → 0764 的或操作。

此系统调用成功时返回新的文件描述符 ID,出错时返回 -1。错误原因可通过 errno 变量或 perror() 函数确定。

#include<unistd.h>

int close(int fd)

上述系统调用关闭已打开的文件描述符。这意味着该文件不再使用,并且相关资源可由任何其他进程重用。此系统调用在成功时返回零,在出错时返回 -1。错误原因可通过 errno 变量或 perror() 函数确定。

#include<unistd.h>

ssize_t read(int fd, void *buf, size_t count)

上述系统调用使用文件描述符 fd、分配内存(静态或动态)的适当缓冲区以及缓冲区大小作为参数,从指定文件中读取数据。

文件描述符 id 用于标识相应文件,在调用 open() 或 pipe() 系统调用后返回。在读取文件之前需要打开文件。如果调用 pipe() 系统调用,则会自动打开。

如果成功,此调用将返回读取的字节数(如果遇到文件末尾,则返回零),如果失败,则返回 -1。返回的字节数可能小于请求的字节数,以防没有可用数据或文件已关闭。如果失败,则设置适当的错误号。

要了解失败的原因,请检查 errno 变量或 perror() 函数。

#include<unistd.h>

ssize_t write(int fd, void *buf, size_t count)

上述系统调用使用文件描述符 fd、分配了内存(静态或动态)的适当缓冲区以及缓冲区大小作为参数,将数据写入指定文件。

文件描述符 id 用于标识相应文件,在调用 open() 或 pipe() 系统调用后返回。

在写入文件之前需要打开文件。如果调用 pipe() 系统调用,则会自动打开。

如果成功,此调用将返回写入的字节数(如果未写入任何内容,则返回零),如果失败,则返回 -1。如果失败,则设置适当的错误编号。

要了解失败的原因,请检查 errno 变量或 perror() 函数。

示例程序

以下是一些示例程序。

示例程序 1 −使用管道写入和读取两个消息的程序。

算法

步骤 1 − 创建管道。

步骤 2 − 向管道发送一条消息。

步骤 3 − 从管道中检索消息并将其写入标准输出。

步骤 4 − 向管道发送另一条消息。

步骤 5 − 从管道中检索消息并将其写入标准输出。

注意 − 也可以在发送所有消息后检索消息。

源代码:simplepipe.c

#include<stdio.h>
#include<unistd.h>

int main() {
   int pipefds[2];
   int returnstatus;
   char writemessages[2][20]={"Hi", "Hello"};
   char readmessage[20];
   returnstatus = pipe(pipefds);
   
   if (returnstatus == -1) {
      printf("Unable to create pipe
");
      return 1;
   }
   
   printf("Writing to pipe - Message 1 is %s
", writemessages[0]);
   write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
   read(pipefds[0], readmessage, sizeof(readmessage));
   printf("Reading from pipe – Message 1 is %s
", readmessage);
   printf("Writing to pipe - Message 2 is %s
", writemessages[0]);
   write(pipefds[1], writemessages[1], sizeof(writemessages[0]));
   read(pipefds[0], readmessage, sizeof(readmessage));
   printf("Reading from pipe – Message 2 is %s
", readmessage);
   return 0;
}

注意 − 理想情况下,需要检查每个系统调用的返回状态。为了简化流程,不会对所有调用进行检查。

执行步骤

编译

gcc -o simplepipe simplepipe.c

执行/输出

Writing to pipe - Message 1 is Hi
Reading from pipe – Message 1 is Hi
Writing to pipe - Message 2 is Hi
Reading from pipe – Message 2 is Hell

示例程序 2 − 使用父进程和子进程通过管道写入和读取两条消息的程序。

算法

步骤 1 − 创建管道。

步骤 2 − 创建子进程。

步骤 3 − 父进程写入管道。

步骤 4 − 子进程从管道检索消息并将其写入标准输出。

步骤 5 − 再次重复步骤 3 和步骤 4。

源代码:pipewithprocesses.c

#include<stdio.h>
#include<unistd.h>

int main() {
   int pipefds[2];
   int returnstatus;
   int pid;
   char writemessages[2][20]={"Hi", "Hello"};
   char readmessage[20];
   returnstatus = pipe(pipefds);
   if (returnstatus == -1) {
      printf("Unable to create pipe
");
      return 1;
   }
   pid = fork();
   
   // Child process
   if (pid == 0) {
      read(pipefds[0], readmessage, sizeof(readmessage));
      printf("Child Process - Reading from pipe – Message 1 is %s
", readmessage);
      read(pipefds[0], readmessage, sizeof(readmessage));
      printf("Child Process - Reading from pipe – Message 2 is %s
", readmessage);
   } else { //Parent process
      printf("Parent Process - Writing to pipe - Message 1 is %s
", writemessages[0]);
      write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
      printf("Parent Process - Writing to pipe - Message 2 is %s
", writemessages[1]);
      write(pipefds[1], writemessages[1], sizeof(writemessages[1]));
   }
   return 0;
}

执行步骤

编译

gcc pipewithprocesses.c –o pipewithprocesses

执行

Parent Process - Writing to pipe - Message 1 is Hi
Parent Process - Writing to pipe - Message 2 is Hello
Child Process - Reading from pipe – Message 1 is Hi
Child Process - Reading from pipe – Message 2 is Hello

使用管道进行双向通信

管道通信被视为仅单向通信,即父进程写入而子进程读取,反之亦然,但不能同时进行。但是,如果父进程和子进程都需要同时从管道写入和读取,解决方案是使用管道进行双向通信。需要两个管道才能建立双向通信。

以下是实现双向通信的步骤 −

步骤 1 − 创建两个管道。第一个管道用于父进程写入而子进程读取,即 pipe1。第二个管道用于子进程写入而父进程读取,即 pipe2。

步骤 2 − 创建子进程。

步骤 3 −关闭不需要的端,因为每次通信只需要一个端。

步骤4 − 在父进程中关闭不需要的端,读取管道1的端并写入管道2的端。

步骤5 − 在子进程中关闭不需要的端,写入管道1的端并读取管道2的端。

步骤6 − 根据需要进行通信。

Pipe with two

示例程序

示例程序1 − 使用管道实现双向通信。

算法

步骤1 −创建管道1,供父进程写入,子进程读取。

步骤2 − 创建管道2,供子进程写入,父进程读取。

步骤3 − 从父进程和子进程端关闭管道不需要的末端。

步骤4 − 父进程写入消息,子进程读取并显示在屏幕上。

步骤5 − 子进程写入消息,父进程读取并显示在屏幕上。

源代码:twowayspipe.c

#include<stdio.h>
#include<unistd.h>

int main() {
   int pipefds1[2], pipefds2[2];
   int returnstatus1, returnstatus2;
   int pid;
   char pipe1writemessage[20] = "Hi";
   char pipe2writemessage[20] = "Hello";
   char readmessage[20];
   returnstatus1 = pipe(pipefds1);
   
   if (returnstatus1 == -1) {
      printf("Unable to create pipe 1 
");
      return 1;
   }
   returnstatus2 = pipe(pipefds2);
   
   if (returnstatus2 == -1) {
      printf("Unable to create pipe 2 
");
      return 1;
   }
   pid = fork();
   
   if (pid != 0) // Parent process {
      close(pipefds1[0]); // Close the unwanted pipe1 read side
      close(pipefds2[1]); // Close the unwanted pipe2 write side
      printf("In Parent: Writing to pipe 1 – Message is %s
", pipe1writemessage);
      write(pipefds1[1], pipe1writemessage, sizeof(pipe1writemessage));
      read(pipefds2[0], readmessage, sizeof(readmessage));
      printf("In Parent: Reading from pipe 2 – Message is %s
", readmessage);
   } else { //child process
      close(pipefds1[1]); // Close the unwanted pipe1 write side
      close(pipefds2[0]); // Close the unwanted pipe2 read side
      read(pipefds1[0], readmessage, sizeof(readmessage));
      printf("In Child: Reading from pipe 1 – Message is %s
", readmessage);
      printf("In Child: Writing to pipe 2 – Message is %s
", pipe2writemessage);
      write(pipefds2[1], pipe2writemessage, sizeof(pipe2writemessage));
   }
   return 0;
}

执行步骤

编译

gcc twowayspipe.c –o twowayspipe

执行

In Parent: Writing to pipe 1 – Message is Hi
In Child: Reading from pipe 1 – Message is Hi
In Child: Writing to pipe 2 – Message is Hello
In Parent: Reading from pipe 2 – Message is Hello