管道与管道间通信详解
管道在Liunx中是一个常见的通信工具,管道在我们生活中顾名思义就是传输东西的,而在Linux中管道的工作原理是什么呢?每个进程
各自有不
同的用户地址空间,任何一个进程的全局变量在另一个进程中看不到.所以进程之间交换数据必须通过内核,在
内核中开辟
,
在内核中开辟一块
缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据拿走,内核提供
的这种机制称为
进程间通信.
管道是一种最基本的IPC机制,由Pipe函数创建:
#include<unistd.h>
int pipe(int filedes[2]);
调用pipe函数时在内核中开辟一块缓冲区(称为管道)用于通信,他有一个读端一个写端,然后通过filedes函数传出给用户程序
两个文件描述符,filedes[0] 指向管道的读端,filedes[1]指向管道的写端。 所以管道在用户程序看起来就像一个打开的文件.
#include<unistd.h>
int pipe(int filedes[2]);
调用pipe函数时在内核中开辟一块缓冲区(称为管道)用于通信,他有一个读端一个写端,然后通过filedes函数传出给用户程序
两个文件描述符,filedes[0] 指向管道的读端,filedes[1]指向管道的写端。 所以管道在用户程序看起来就像一个打开的文件.
通过read(filedes[0]) 或者 write(filedes[1]) 向这个文件读写数据其实是在读写内核缓冲区.pipe函数调用成功返回0,
调用失败返回-1;
1.父进程调用pipe开辟管道,得到两个文件描述符指向管道的两端.
2.父进程调用fork创建子进程,那么子进程也有两个描述符指向同一管道.
3.父进程关闭管道读端,子进程关闭管道写端. 父进程可以往管道里面写.
子进程可以从管道里面读,管道是用环形队列实现的,数据从写端流入从读端流出,这样就形成进程间通信.
为什么管道不是主流的通信方式呢? 就是因为管道有缺陷和限制.
两个进程通过一个管道只能单向通信,父进程写子进程读,如果你想双向通信,那么就开辟两管道.
而且通信之间必须有血缘关系.
有的人可会想到,我如果两个进程的读写端都没有关,为什么不能双向通信?
管道的读写端通过打开文件描述符的方式来传递,因此要通信两个进程必须从他们的公共祖先哪里继承管道文件描述符. 所以父进程
把文件文件标识符传给子进程后,父子进程之间通信.
接下来我们来看看一个最简单的管道通信:
这里我让子进程每隔一秒从写端写一行内容,一共写三次,然后父进程读三次并且输出. 认真看代码一定会看懂.
接下来我们来看看结果:
这是正常用法,接下来我们来看看非常规调用->
管道通信的注意事项:
1.如果所有指向管道写端的文件描述符都关闭,而仍然有进程从管道的读端读数据,那么管道中的剩余数据都被读取后,再去read
会返回0,就像读到文件末尾一样.
举例:
这里我们让子进程写端只写入一次内容,然后父进程读端读三次,然后我们可以看到只有第一次读取输出了内容,后面都是读取
到空,返回0,接下来验证一下.
2.如果有指向管道写端的文件描述符没关闭,而持有管道写端的进程也没有向管道中写数据,这时候进程从管道读端读数据,
那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回.
举例:
这里这个程序呢,我让子进程每隔一秒往管道里面写一个内容但是只写5次,然后读端这里读10次,所以当读端在第6次开始就会
开始阻塞.
直到子进程结束.
3.如果有指向管道读端的文件描述符没关闭,而持有管道读端的进程也没有从管道中读数据,进程也没有从管道中读数据,这时有
进程想向管道写数据,那么在管道被写满时再次write会阻塞,知道管道中有空位置了才会写入数据返回.
举例:
这里打开读端写端,再然后一直往管道里面写,但是父进程的读端一直没有从管道里面读取数据,再然后管道里面一直写数据,
直到把管道写满.我这里每些一次输出一次,只有我们就可以看到最后管道的长度了.
4.如果所有指向管道读取的文件描述符都关闭,这是有进程想管道的写端write,那么该进程会受到信号SIGPIPE,通常会导致进
程
异常终止.
这个验证起来比较简单,相信大家可以自己验证成功的.
相关验证代码:
//正常情况下的通信
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
int main()
{
int _pipe[2];
int ret = pipe(_pipe);
if (ret == -1)
{
printf("create pipe error! errno code is: %d\n", errno);
return 1;
}
pid_t id = fork();
if (id < 0){
printf("fork errno");
return 2;
}
else if (id == 0)
{ // child
close(_pipe[0]);
int i = 0;
char* buf = NULL;
while (i<3)
{
buf = "i love you";
write(_pipe[1], buf, strlen(buf) + 1);
sleep(1);
++i;
}
}
else{
//father
close(_pipe[1]);
char buf2[100];
int j = 0;
while (j < 3)
{
memset(buf2, '\0', sizeof(buf2));
ssize_t ret = read(_pipe[0], buf2, sizeof(buf2));
printf("%s ret :%d\n ", buf2, ret);
++j;
}
}
return 0;
}
//管道间通信注意事项1的验证代码(read读取状态)
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
int main()
{
int _pipe[2];
int ret = pipe(_pipe);
if (ret == -1)
{
printf("create pipe error! errno code is: %d\n", errno);
return 1;
}
pid_t id = fork();
if (id < 0){
printf("fork errno");
return 2;
}
else if (id == 0)
{ // child
close(_pipe[0]);
int i = 0;
char* buf = NULL;
buf = "i love you";
write(_pipe[1], buf, strlen(buf) + 1);
++i;
close(_pipe[1]);
}
else{
//father
close(_pipe[1]);
char buf2[100];
int j = 0;
sleep(3);
while (j < 3)
{
memset(buf2, '\0', sizeof(buf2));
ssize_t ret = read(_pipe[0], buf2, sizeof(buf2));
printf("%s ret :%d\n ", buf2, ret);
++j;
}
}
return 0;
}
//管道间通信注意事项2的验证代码(read阻塞)
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/wait.h>
int main()
{
int _pipe[2];
int ret = pipe(_pipe);
pid_t id = fork();
if (id == 0)
{//child
close(_pipe[0]);
int i = 0;
char* buf = NULL;
while (i < 10)
{
if (i<5){
buf = "i am child";
write(_pipe[1], buf, strlen(buf) + 1);
}
sleep(1);
++i;
}
close(1);
}
else{
close(_pipe[1]);
char* buf2[100];
int j = 0;
while (j<10)
{
memset(buf2, '\0', sizeof(buf2));
int ret = read(_pipe[0], buf2, sizeof(buf2));
printf("%s: code is:%d\n", buf2, ret);
++j;
}
}
return 0;
}
//管道间通信注意事项3的验证代码(写满管道)
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/wait.h>
int main()
{
int _pipe[2];
int ret = pipe(_pipe);
pid_t id = fork();
if (id == 0){//child
close(_pipe[0]);
int i = 0;
char * buf = NULL;
while (1)
{
buf = "i am child , i love you father";
write(_pipe[1], buf, strlen(buf) + 1);
i++;
printf("%d\n", i);
}
}
else{
close(_pipe[1]);
char buf2[100];
int j = 0;
while (j < 3)
{
read(_pipe[0], buf2, sizeof(buf2));
printf("%s\n: ret is %d\n", buf2, ret);
++j;
}
if (waitpid(id, NULL, 0) < 0)
return 3;
}
return 0;
}
下一个博客就是匿名管道的升级版命名管道,有没有很期待呢~