管道分为匿名管道和命名管道,匿名管道只能在有共同祖先的(有亲缘关系)进程中使用,而命名管道可以在任意进程中使用,以下的“管道”指的都是匿名管道
,命名管道的知识后续再进行更新
IPC (进程间通信)有多种方式, 管道是IPC的最基本的方式。管道是“半双工”的,即是单向的
。管道是FIFO(先进先出
)的。
单进程中的管道:int fd[2]
1、使用文件描述符fd[1], 向管道写数据
2、使用文件描述符fd[0], 从管道读数据
管道的局限性:
①数据一旦被读走,便不在管道中存在,不可反复读取。
②由于管道采用半双工通信方式。因此,数据只能在一个方向上流动。
③只能在有公共祖先的进程间使用管道。
参数:
一个大小为2的fd数组
返回值:
成功:返回 0
失败:返回 -1
注意:获取两个“文件描述符”分别对应管道的读端和写端。
1、fd[0]: 是管道的读端
2、fd[1]: 是管道的写端
如果对fd[0]进行写操作,对fd[1]进行读操作,可能导致不可预期的错误。
#include
#include
#include
int main(void)
{
int fd[2];
int ret;
ret = pipe(fd);
....
....
}
管道虽然可以在单进程中使用,但是其实没啥用处,更多地是在多进程的环境下使用,不过既然写了,也都举个例子吧!
创建管道后,获得该管道的两个文件描述符,不需要普通文件操作中的open操作
直接看代码吧,挺简单的
#include
#include
#include
int main(void)
{
int fd[2];
int ret;
char buff1[1024];
char buff2[1024];
ret = pipe(fd);
if (ret !=0) {
printf("create pipe failed!\n");
exit(1);
}
strcpy(buff1, "Hello!");
//写数据一定是在fd[1]中写
write(fd[1], buff1, strlen(buff1));
printf("send information:%s\n", buff1);
//将buff2清零
bzero(buff2, sizeof(buff2));
//读数据一定是在fd[0]中写
read(fd[0], buff2, sizeof(buff2));
printf("received information:%s\n", buff2);
return 0;
}
由于管道的通信是半双工的(数据只能由一端流到另外一端,而不能反过来发送数据)
,所以如果要在多进程中使用管道进行通信的话,一般是创建两个管道,这样就可以更清晰相互发送数据了(而不是说一个管道不能用来相互发送数据),采用父子进程的时候,子进程会“复制”父进程的资源,这样就有了2个fd[2]了,虽然名字一样,但是表示的不是同个fd[2],但是又可以相互通信(可以理解成虽然复制成了两个管道,但是两个管道是相通的,各个进程持有一个写端和读端
)。
#include
#include
#include
int main(void)
{
int fd[2];
int ret;
char buff1[1024];
char buff2[1024];
pid_t pd;
ret = pipe(fd);
if (ret !=0) {
printf("create pipe failed!\n");
exit(1);
}
//fork一个子进程
pd = fork();
if (pd == -1) {
printf("fork error!\n");
exit(1);
} else if (pd == 0) {
//子进程正确返回的pid会是0所以子进程执行这段代码
//读数据
bzero(buff2, sizeof(buff2));
read(fd[0], buff2, sizeof(buff2));
printf("child process(%d) received information:%s\n", getpid(), buff2);
sleep(5);
//写数据
strcpy(buff1, "Hello! father!");
write(fd[1], buff1, strlen(buff1));
printf("child process(%d) send information:%s\n", getpid(), buff1);
} else {
//父进程返回子进程的pid。所以父进程执行这段代码
//写数据
strcpy(buff1, "Hello! child!");
write(fd[1], buff1, strlen(buff1));
printf("parent process(%d) send information:%s\n", getpid(), buff1);
sleep(5);
//读数据
bzero(buff2, sizeof(buff2));
read(fd[0], buff2, sizeof(buff2));
printf("parent process(%d) received information:%s\n", getpid(), buff2);
}
close(fd[0]);
close(fd[1]);
//等待子进程执行完
if (pd > 0) {
wait();
}
return 0;
}
此类示例的主要应用场景是:
1、如果不准备再向管道写入数据,则把该管道的所有写端都关闭,则,此时再对该管道read时,就会返回0,而不再阻塞该读操作。(管道的特性)
注意,这是管道的特性。
如果有多个写端口,而只关闭了一个写端,那么无数据时读操作仍将被阻塞。
2、多个进程通信的时候,我只想要单方向的数据流动
父子进程各有一个管道的读端和写端;
1、把父进程的读端(或写端)关闭;
2、把子进程的写端(或读端)关闭;
使这个“4端口”管道变成单向的“2端口”管道(虽然复制成了两个管道,但是两个管道是相通的,各个进程持有一个写端和读端,如果在两方的端口各关闭一个的话,那么就又会变成一个单方向的数据流动
),如图:
#include
#include
#include
int main(void)
{
int fd[2];
int ret;
char buff1[1024];
char buff2[1024];
pid_t pd;
ret = pipe(fd);
if (ret !=0) {
printf("create pipe failed!\n");
exit(1);
}
pd = fork();
if (pd == -1) {
printf("fork error!\n");
exit(1);
} else if (pd == 0) {
//子进程关闭写端
close(fd[1]);
bzero(buff2, sizeof(buff2));
read(fd[0], buff2, sizeof(buff2));
printf("process(%d) received information:%s\n", getpid(), buff2);
} else {
//关闭读端
close (fd[0]);
strcpy(buff1, "Hello!");
write(fd[1], buff1, strlen(buff1));
printf("process(%d) send information:%s\n", getpid(), buff1);
close (fd[1]);
}
if (pd > 0) {
wait();
}
return 0;
}
这篇文章到此结束,非常感谢您能看到这,这篇文章还不够详细,我也将持续更新,希望这篇文章能够对您有所帮助,当然,如果对于本文有任何的疑问也请在评论区中提出来,感谢您的观看!!