本文演示 CentOS 7.6:Linux testerfans 3.10.0-1160.45.1.el7.x86_64
前言
Pipes—管道是两个或多个相关或相互关联的进程之间的通信媒介,也成为无名管道。它可以在一个进程内通信,也可以在子进程和父进程之间进行通信。通信也可以是多层次的,例如父、子、孙之间的进程通信等。通信是通过一个进程写入管道和另一个进程从管道中读取来实现的。要实现管道系统调用,需要创建两个文件,一个写入文件,另一个读取文件。
可以通过现实生活中的场景来理解管道机制,例如用管道将水填充到某个容器(例如水桶)中,然后有人取水(例如用杯子)。填充过程只不过是写入管道,读取过程只不过是从管道中检索。这意味着一个输出(水)是另一个(桶)的输入。
管道特点
- 它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。
- 它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。
- 它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
系统调用pipe()
#include<unistd.h>
int pipe(int pipedes[2]);
该系统调用将为单向通信创建一个管道,即,它创建两个文件描述符,第一个连接到管道一端进行读取,另一个连接到管道进行写入。
传参
- pipedes[0] 用于读取
- pipedes[1] 用于写入
任何写入 pipedes[1] 的内容都可以从 pipedes[0] 中读取。
返回
- 0:成功
- -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);
//调用pipe()方法后pipefds会被赋值
printf("pipefds[0]:%d\npipefds[1]:%d\n",pipefds[0],pipefds[1]);
if (returnstatus == -1) {
printf("Unable to create pipe\n");
return 1;
}
//向pipefds[1]端写入
printf("Writing to pipe - Message 1 is %s\n", writemessages[0]);
write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
// 从pipefds[0]端读取
read(pipefds[0], readmessage, sizeof(readmessage));
printf("Reading from pipe – Message 1 is %s\n", readmessage);
//向pipefds[1]端写入
printf("Writing to pipe - Message 2 is %s\n", writemessages[0]);
write(pipefds[1], writemessages[1], sizeof(writemessages[0]));
// 从pipefds[0]端读取
read(pipefds[0], readmessage, sizeof(readmessage));
printf("Reading from pipe – Message 2 is %s\n", readmessage);
return 0;
}
注意- 理想情况下,需要检查每个系统调用的返回状态。为了简化流程,不会对所有调用进行检查。
编译
[root@testerfans pipes]# gcc simplepipe.c -o simplepipe
执行/输出
[root@testerfans pipes]# ./simplepipe
pefds[0]:3
pipefds[1]:4
Writing to pipe - Message 1 is Hi
Reading from pipe – Message 1 is Hi
Writing to pipe - Message 2 is Hello
Reading from pipe – Message 2 is Hello
示例程序 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;
}
//调用fork方法创建子进程,这里有一点需要注意,调用fork方法会返回两个pid
//一个是返回给父进程的子进程id,一个是在子进程内返回的0.
printf("Calling Process id is :%d\n",getpid());
pid = fork();
// Child process
if (pid == 0) {
//之后在子进程内进行读取
printf("Child Process id is :%d\n",pid);
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 id is :%d\n",pid);
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;
}
注意- 理想情况下,需要检查每个系统调用的返回状态。为了简化流程,不会对所有调用进行检查。
编译
[root@testerfans pipes]# gcc pipewithprocesses.c -o pipewithprocesses
执行/输出
[root@testerfans pipes]# ./pipewithprocesses
Calling Process id is :29428
Child Process id is :29429
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
示例程序 3 - 进程间双向通信
管道通信仅被视为单向通信,即父进程写入和子进程读取,反之亦然,不能同时进行。如果父子进程需要同时从管道写入和读取,解决方案是使用管道进行双向通信。需要两个管道来建立双向通信。
示意图
步骤
第 1 步 - 如图,创建两个管道,pipe1:父写,子读。pipe2:子写,父读。
第 2 步 - 创建一个子进程。
第 3 步 - 关闭不需要的端,因为每次通信只需要一个端。
第 4 步 - 关闭父进程中不需要的端: pipe1 的读端和 pipe2 的写端。
第 5 步 - 关闭子进程中不需要的端: pipe1 的写端和 pipe2 的读端。
第 6 步 - 根据需要执行通信。
源代码:pipewithprocesses.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];
//创建Pipe1
returnstatus1 = pipe(pipefds1);
if (returnstatus1 == -1) {
printf("Unable to create pipe 1 \n");
return 1;
}
//创建Pipe2
returnstatus2 = pipe(pipefds2);
if (returnstatus2 == -1) {
printf("Unable to create pipe 2 \n");
return 1;
}
//调用fork方法创建一个子进程
pid = fork();
if (pid != 0) {// Parent process
//父进程关闭Pipe1的读端
close(pipefds1[0]);
//父进程关闭Pipe2的写端
close(pipefds2[1]);
//父进程向Pipe1写入
printf("In Parent: Writing to pipe 1 – Message is %s\n", pipe1writemessage);
write(pipefds1[1], pipe1writemessage, sizeof(pipe1writemessage));
//父进程从Pipe2进行读取
read(pipefds2[0], readmessage, sizeof(readmessage));
printf("In Parent: Reading from pipe 2 – Message is %s\n", readmessage);
} else { //child process
//子进程关闭Pipe1的写端
close(pipefds1[1]);
//子进程关闭Pipe2的读端
close(pipefds2[0]);
//子进程从Pipe1进行读取
read(pipefds1[0], readmessage, sizeof(readmessage));
printf("In Child: Reading from pipe 1 – Message is %s\n", readmessage);
//子进程向Pipe2写入
printf("In Child: Writing to pipe 2 – Message is %s\n", pipe2writemessage);
write(pipefds2[1], pipe2writemessage, sizeof(pipe2writemessage));
}
return 0;
}
注意- 理想情况下,需要检查每个系统调用的返回状态。为了简化流程,不会对所有调用进行检查。
编译
[root@testerfans pipes]# gcc twowayspipe.c -o twowayspipe
执行/输出
[root@testerfans pipes]# ./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
总结
正常情况下Pipe管道是单向的,如果需要进行进程间双向通信需要建立两个管道并且关闭响应的读端或者写端来实现。
评论区