本文翻译转载自:www.tutorialspoint.com, Inter Process Communication - Pipes
原创翻译,仅作为学习分享,侵删。
管道是两个或多个相关的进程之间的通信中介。这个通信中介可以是用来在同一个进程中通信,也可以是用来在父子进程之间通信。通信也可以是多层次的,比如在父进程、子进程、子孙进程之间的通信等。通信的实现是通过一个进程写入管道,另外一个进程从管道中读出来实现的。为了实现管道的系统调用,需要创建两个文件,一个用来写入文件,而另外一个用来从文件中读。
管道机制可以看作一个实时的场景——比如用一根管子往一个容器(一个桶)里面装水,另外一个人用一个杯子来取水。填充的过程无非就是写入管道,而读取的过程无非就是从管道中获取(信息)。这意味着一个输出(取出的水)是其它部分(桶)的输入。
#include<unistd.h>
int pipe(int pipedes[2]);
这一系统调用将为单向通信创建一条管道,也就是说,它创建了两个描述符,一个用来从管道中读取而另外一个用来写入管道。
描述符pipedes[0]用来读取,pipedes[1]用来写入。任何写入pipedes[1]的东西都可以从pipedes[0]中读取。
这一调用如果成功将返回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个描述符,分别是文件描述符0,1,2,分别用来输入(标准输入-stdin)、输出(标准输出-stdout)、错误(标准错误-stderr)。
这一系统调用将返回用来做进一步文件操作的文件描述符,比如读、写和搜索(Iseek)。一般情况下,文件描述符从3开始,并且随着打开的文件的数量一个一个地增长。
传入打开的系统调用的参数是pathname路径名(相对路径或者绝对路径),flags标志位表示打开文件的意图(即,为了读取打开O_RDONLY,为了写入打开O_WRONLY,为了读写打开O_RDWR,为了附加在已存在的文件后面O_APPEND,为了创建还不存在的文件而打开O_CREAT,等等),和要求的mode模式提供用户或者用户组的权限。mode可以用symbols提供。(?)
读取-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)
以上的系统调用将关闭已经打开的文件描述符。这意味着文件不再使用且相关的资源可以被其他进程占用。这一系统调用成果返回0而失败返回-1。错误的原因可以通过errno变量或者perror()函数得到。
#include<unistd.h>
ssize_t read(int fd, void *buf, size_t count)
以上的系统调用是用来从特定的文件中读取(用参数fd传入要读取的文件的文件描述符),将分配合适大小的(或者给定的大小)内存(动态的或者静态的)给缓冲区。
文件描述符id用来识别一个特定的文件,是通过调用open()和pipe()返回得到的。在从文件读取之前需要打开文件。再调用pipe()系统调用的情况下,文件是自动打开的。
这个调用成功时会返回读取到的字节数(当遇到文件结尾时返回0),失败时会返回-1。当没有数据可读或者文件被关闭时,返回的字节数可能会比要求读取的字节数要少。不同的失败有不同的对应错误号。
如果要查看失败的原因,请查看errno变量或者使用perror()函数。
#include<unistd.h>
ssize_t write(int fd, void *buf, size_t count)
以上系统调用是用来写入特定的文件,按照给定的文件描述符fd、给定的已分配好内存的缓冲区(静态或动态的)、缓冲区的大小。
文件描述符id用来识别一个特定的文件,是通过调用open()和pipe()返回得到的。在写入文件之前需要打开文件。当调用pipe()时,文件将自动打开。
这一调用将返回成功写入的字节数(如果什么都没写的话就是0),失败则返回-1。失败的时候,不同的原因对应不同的错误号。
如果要查看失败的原因,请查看errno变量或者使用perror()函数。
例程
以下是一些例程。
例程1-程序通过管道写入和读取两个信息。
算法
第一步-创建一个管道。
第二步-向管道中送入一个信息。
第三步-从管道中取出信息,并且写入标准输出。
第四步-向管道中送入另外一个信息。
第五步-从管道中取出信息,并且写入标准输出。
提示-可以等到发送完所有的信息后再读取。
源代码: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-父子进程通过管道写入并读取两个信息。
算法
第一步-创建管道。
第二步-创建子进程。
第三步-父进程写入管道。
第四步-子进程从管道中读取信息并写入标准输出。
第五步-再一次重复第三步和第四步。
源代码: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
使用管道进行双向通信
管道通信被看作单向的,也就是父进程写入而子进程读取,又或者反过来,但不是双向通信的。然而,如果父子进程都需要同时地从管道中读取或者写入的话,解决方法是用管道进行双向通信。双向通信需要建立两条管道。
通过以下的步骤来实现双向通信-
第一步-创建两条管道。第一个管道作为pipe1,为了父进程写入而子进程读出。第二个作为pipe2,为了子进程写入而父进程读出。
第二步-创建子进程。
第三步-把不需要的端点关掉,因为通信的时候只需要一端的端口。
第四步-关闭父进程不需要的端口,从pipe1的一端读取,从pipe2的一端写入。
第五步-关闭子进程不需要的端口,从pipe1的一端写入,从pipe2的一端读取。
第六步-按照要求实现需要的通信。
例程
例程1-用管道实现双向通信。
算法
第一步-创建管道pipe1:父进程写入,子进程读取。
第二步-创建管道pipe2:子进程写入,父进程读出。
第三步-在父子进程中关闭管道不需要的端口。
第四步-父进程写入一个信息,子进程读取并显示在屏幕上。
第五步-子进程写入一个信息,父进程读取并显示在屏幕上。
源代码: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关闭不需要的pipe1读取端
close(pipefds2[1]); // Close the unwanted pipe2 write side关闭不需要的pipe2写入端
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关闭不需要的pipe1写入端
close(pipefds2[0]); // Close the unwanted pipe2 read side关闭不需要的pipe2读取端
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