进程间通信 - 管道



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

管道机制可以用一个实时场景来理解,例如用管道往某个容器(比如水桶)里灌水,有人用杯子取水。灌水的过程就是写入管道,取水的过程就是从管道读取。这意味着一个输出(水)是另一个的输入(水桶)。

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用于标识相应的文件,该ID在调用open()或pipe()系统调用后返回。在从文件读取之前,需要打开该文件。在调用pipe()系统调用时,它会自动打开。

此调用成功时返回读取的字节数(如果遇到文件结尾则返回零),失败时返回-1。返回的字节数可能小于请求的字节数,如果数据不可用或文件已关闭。失败时会设置正确的错误编号。

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

#include<unistd.h>

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

上述系统调用是向指定文件写入数据,参数为文件描述符fd、已分配内存的缓冲区(静态或动态)和缓冲区大小。

文件描述符ID用于标识相应的文件,该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\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 - 创建两个管道。第一个管道用于父进程写入,子进程读取,例如pipe1。第二个管道用于子进程写入,父进程读取,例如pipe2。

步骤2 - 创建子进程。

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

步骤4 - 在父进程中关闭不需要的端点,即pipe1的读取端和pipe2的写入端。

步骤5 - 在子进程中关闭不需要的端点,即pipe1的写入端和pipe2的读取端。

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

Pipe with two

示例程序

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

算法

步骤1 - 创建pipe1,用于父进程写入,子进程读取。

步骤2 - 创建pipe2,用于子进程写入,父进程读取。

步骤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
广告