我们有一个这样的场景:主程序希望与子程序进行通信。我们用pipe()函数为他们创建管道,这样他们就可以通过管道来通信了。
为了演示方便我们只考虑:
子程序把数据发送到父进程中来的情况。因此要用管道把子进程的标准输出和父进程的标准输入连接起来

我们将用pipe()函数建立管道。每当我们打开数据流时,它都会加入描述符表。pipe()函数也是如此,它创建两条相连的数据流,并把它们加到描述符表中,然后只要我们往其中一条数据流中写数据,就可以从另一条数据流中读取。pipe()在描述符表中创建这两项时,会把它们的文件描述符存放在一个包含两个元素的数组中。

int fd[2];  //文件描述符将保存在fd数组中。
if(pipe(fd) == -1){
fprintf(stderr,"Can't create the pipe!");
exit(2);
}

pipe()函数创建了管道,并返回了两个描述符:fd[0]用来从管道读数据,fd[1]用来向管道写数据。我们将在父、子进程中使用这两个描述符。

我们通过一个实例来说明:
aaa.c

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

void open_url(char *url);
int main(){
int fd[2];
if(pipe(fd) == -1){ //创建管道,并把文件描述符保存在fd[0]和fd[1]中。
fprintf(stderr,"%s\n","Can't create the pipe!");
exit(1);
}
pid_t pid = fork();
if(pid == -1){
fprintf(stderr,"%s\n","Can't fork process!");
exit(2);
}
if(!pid){ //能进这里,表示已在子进程中
dup2(fd[1],1); //把标准输出设为管道写入端
close(fd[0]); //因为子进程不会去读取管道,因此将读取端关闭
if(execl("bbb","bbb",NULL) == -1){
fprintf(stderr,"%s\n","Can't open subprocess");
exit(1);
}
}
//从这开始是属于父进程的
dup2(fd[0],0); //把标准输入设为管道读取端
close(fd[1]);//因为父进程不会向子进程发数据,无需写入管道,因此将写入端关闭
char line[255];
while(fgets(line,255,stdin)){
open_url(line+1);
}
return 0;
}
void open_url(char *url){
char launch[255];
//sprintf(launch,"cmd /c start %s",url); //在windows上打开网页
// system(launch);
sprintf(launch,"x-www-browser '%s' &",url);//在Linux上打开网页
system(launch);
//sprintf(launch,"open '%s'",url); //在Mac上打开网页
//system(launch);
}

bbb.c

#include <stdio.h>
int main(){
char line[255];
fgets(line,255,stdin);
printf("%s",line);
return 0;
}

编译运行:

~/Desktop/MyC$ gcc aaa.c -o aaa
~/Desktop/MyC$ gcc bbb.c -o bbb
~/Desktop/MyC$ ./aaa
|

查进程信息:

~$ ps -ef
UID PID PPID C STIME TTY TIME CMD
wong 17525 5363 0 17:47 pts/1 00:00:00 ./aaa
wong 17526 17525 0 17:47 pts/1 00:00:00 bbb

输入地址按回车:

~/Desktop/MyC$ ./aaa
http://www.baidu.com

接着就会打开浏览器。

管道是文件吗?这不一定。这取决于操作系统创建管道的方式,一般来说用pipe()创建的管道都不是文件。而创建基于文件的管道,通常叫做有名管道或FIFO文件(先进先出文件)。

因为基于文件的管道有名字,所以两个进程只要知道管道的名字也能用它来通信,即使它们是非父子进程关系。要使用有名管道,可以通过使用mkfifo()系统调用来实现。如果不是通过文件来实现管道的话,那通常是用内存,数据写到内存某个位置,然后再从另一个位置读取。

如果试图读取一个空的管道,也不会发生错误,因为程序会等待管道中出现东西。

父进程是如何知道子进程结束的?
其实,当子进程结束时,管道会关闭。fgets()将会收到EOF(End Of File,文件结束符) ,于是fgets()函数返回0,循环就结束了。

另外,管道只能单向通信。但是可以通过创建两个管道,一个从父进程连接到子进程,另一个从子进程连接到父进程来实现双向通信 。

谢谢阅读!