进程间通信 - 管道


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

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

管子有一个
#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 开始,并随着打开的文件数量增加 1。

传递给 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()系统调用后返回。在读取文件之前需要打开该文件。在调用 pipeline() 系统调用时它会自动打开。

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

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

#include<unistd.h>

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

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

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

在写入文件之前需要先打开该文件。在调用 pipeline() 系统调用时它会自动打开。

如果成功,此调用将返回写入的字节数(如果没有写入任何内容,则返回零);如果失败,则返回 -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\n");
      return 1;
   }
   
   printf("Writing to pipe - Message 1 is %s\n", writemessages[0]);
   write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
   read(pipefds[0], readmessage, sizeof(readmessage));
   printf("Reading from pipe – Message 1 is %s\n", readmessage);
   printf("Writing to pipe - Message 2 is %s\n", writemessages[0]);
   write(pipefds[1], writemessages[1], sizeof(writemessages[0]));
   read(pipefds[0], readmessage, sizeof(readmessage));
   printf("Reading from pipe – Message 2 is %s\n", 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\n");
      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\n", readmessage);
      read(pipefds[0], readmessage, sizeof(readmessage));
      printf("Child Process - Reading from pipe – Message 2 is %s\n", readmessage);
   } else { //Parent process
      printf("Parent Process - Writing to pipe - Message 1 is %s\n", writemessages[0]);
      write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
      printf("Parent Process - Writing to pipe - Message 2 is %s\n", 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 - 创建两个管道。第一个是供父级写入和子级读取,例如 pipeline1。第二个是让孩子写,父母读,比如pipe2。

步骤 2 - 创建一个子进程。

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

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

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

步骤 6 - 根据需要执行通信。

管道有两个

示例程序

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

算法

步骤 1 - 创建 pipeline1 供父进程写入和子进程读取。

步骤 2 - 创建 pipeline2 供子进程写入和父进程读取。

步骤 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 \n");
      return 1;
   }
   returnstatus2 = pipe(pipefds2);
   
   if (returnstatus2 == -1) {
      printf("Unable to create pipe 2 \n");
      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\n", pipe1writemessage);
      write(pipefds1[1], pipe1writemessage, sizeof(pipe1writemessage));
      read(pipefds2[0], readmessage, sizeof(readmessage));
      printf("In Parent: Reading from pipe 2 – Message is %s\n", 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\n", readmessage);
      printf("In Child: Writing to pipe 2 – Message is %s\n", 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