管道
理解为内核为我们申请的一段内存
匿名管道
只能进行具有亲缘关系之间的进程进行通信
调用pipe()函数创建一个管道
int pipe(int pipefd[2]);//参数为一个数组,用于操作系统给管道分配两个文件描述符
pipefd[0]为管道的读端
pipefd[1]为管道的写端
下来看一个例子,实现从标准输入读取数据,写入管道,从管道读取数据,写入标准输入
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main()
{
int pipefd[2];
int pi_ret=pipe(pipefd);
if(pi_ret<0)
{
perror("pipe");exit(1);
}
else
{
char buf_forstdin[1024]={0};
char buf_forpipe[1024]={0};
read(0,buf_forstdin,sizeof(buf_forstdin)-1);//从标准输入读到管道中
write(pipefd[1],buf_forstdin,sizeof(buf_forstdin));//从标准输入的数据写入到管道中
read(pipefd[0],buf_forpipe,sizeof(buf_forpipe)-1);//从管道读取数据
write(1,buf_forpipe,sizeof(buf_forpipe));//将数据写在标准输出上
}
return 0;
}
用fork来共享管道
管道就是一种文件,不能同时读或者同时写,当两个要往管道中读时 应该将管道的写端关闭,当要进行写时应该将读端关闭。
父子进程共同完成上面读写任务
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main()
{
int pipefd[2];
int pi_ret=pipe(pipefd);
if(pi_ret<0)
{
perror("pipe");exit(1);
}
else
{
pid_t pid=fork();
if(pid<0)
{
perror("fork");exit(1);
}
if(pid==0)//子进程往管道中写
{
char buf_forstdin[1024]={0};
read(0,buf_forstdin,sizeof(buf_forstdin)-1);//从标准输入读数据
close(pipefd[0]);
write(pipefd[1],buf_forstdin,sizeof(buf_forstdin));//将数据写入到管道中
exit(0);
}
else//父进程从管道中读
{
close(pipefd[1]);
char buf_forpipe[1024]={0};
read(pipefd[0],buf_forpipe,sizeof(buf_forpipe)-1);//从管道读取数据
write(1,buf_forpipe,sizeof(buf_forpipe));//将数据写在标准输出上
wait(NULL);
}
}
return 0;
}
再来看管道的几种情况
(1)当管道写满时
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main()
{
int pipefd[2];
int pi_ret=pipe(pipefd);
if(pi_ret<0)
{
perror("pipe");exit(1);
}
else
{
pid_t pid=fork();
if(pid<0)
{
perror("fork");exit(1);
}
if(pid==0)//子进程往管道中写
{
close(pipefd[0]);
const char * str="hello world\n";
int count=0;
while(1)//让子进程一直往管道中写,并打印出写了几次
{
write(pipefd[1],str,strlen(str));//从标准输入的数据写入到管道中
printf("%d \n",count++);
}
exit(0);
}
else//父进程从管道中读
{ // ssize_t size=read(pipefd[0],buf_forpipe,sizeof(buf_forpipe)-1);//从管道读取数据
// if(size>0)
// {
// printf("%d \n",count++);
// buf_forpipe[size]='\0';
// printf("%s",buf_forpipe);
// }
// wait(NULL);
}
}
return 0;
}
看到子进程将管道写满了就不会再写了
write()调用阻塞,直到有进程读取管道中的数据
(2)管道没有数据可读时
#include <stdio.h> #include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main()
{
int pipefd[2];
int pi_ret=pipe(pipefd);
if(pi_ret<0)
{
perror("pipe");exit(1);
}
else
{
pid_t pid=fork();
if(pid<0)
{
perror("fork");exit(1);
}
if(pid==0)//子进程往管道中写
{
close(pipefd[0]);
const char * str="hello world\n";
int count=0;
while(1)//让子进程往管道中写
{
write(pipefd[1],str,strlen(str));//从标准输入的数据写入到管道中
if(++count==5)
{//让子进程只写5 次
sleep(3);
break;
}
sleep(1);//写一次睡一会保证让父进程将一次写入的数据读走
}
exit(0);
}
else//父进程从管道中读
{
close(pipefd[1]);
char buf_forpipe[1024]={0};
int count=0;
while(1)
{
ssize_t size=read(pipefd[0],buf_forpipe,sizeof(buf_forpipe)-1);//从管道读取数据
if(size>0)
{
printf("%d \n",count++);
buf_forpipe[size]='\0';
printf("%s",buf_forpipe);
}
}
wait(NULL);
}
}
return 0;
}
我们看到这里的父进程就阻塞在这里等着读取数据,一直等着有数据为止
上面两种情都没有将读端和写端关闭,只要写满了就必须等有进程将其数据读走,只要没有数据可以读,就必须等有进程将其数据写入,这也体现了进程之间的同步现象(3)子进程停止写后将管道读端关闭
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>i
int main()
{
int pipefd[2];
int pi_ret=pipe(pipefd);
if(pi_ret<0)
{
perror("pipe");
exit(1);
}
else
{
pid_t pid=fork();
if(pid<0)
{
perror("fork");
exit(1);
}
if(pid==0)//子进程往管道中写
{
close(pipefd[0]);
const char * str="hello world\n";
int count=0;
while(1)//让子进程往管道中写
{
write(pipefd[1],str,strlen(str));//从标准输入的数据写入到管道中
if(++count==5)
{
//让子进程只写5 次
sleep(3);
close(pipefd[1]);
break;
}
sleep(1);//写一次睡一会保证让父进程将一次写入的数据读走
}
exit(0);
}
else//父进程从管道中读
{
close(pipefd[1]);
char buf_forpipe[1024]={0};
int count=0;
while(1)
{
ssize_t size=read(pipefd[0],buf_forpipe,sizeof(buf_forpipe)-1);//从管道读取数据
if(size>0)
{
printf("%d \n",count++);
buf_forpipe[size]='\0';
printf("%s",buf_forpipe);
}
else if(size==0)
{
printf("read done size :%lu\n",size);
break;
}
}
wait(NULL);
}
}
return 0;
}
这里当子进程停止写将管道读端关闭 ,父进程再去读的时候就会读到文件结束符,返回值为0
(4)进程读完之后将管道读端关闭
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main()
{
int pipefd[2];
int pi_ret=pipe(pipefd);
if(pi_ret<0)
{
perror("pipe");exit(1);
}
else
{
pid_t pid=fork();
if(pid<0)
{
perror("fork");exit(1);
}
if(pid==0)//子进程往管道中写
{
close(pipefd[0]);
const char * str="hello world\n";
while(1)//让子进程往管道中写
{
write(pipefd[1],str,strlen(str));//从标准输入的数据写入到管道中
sleep(1);//写一次睡一会保证让父进程将一次写入的数据读走
}
exit(0);
}
else//父进程从管道中读
{
close(pipefd[1]);
char buf_forpipe[1024]={0};
int count=0;
while(1)
{
ssize_t size=read(pipefd[0],buf_forpipe,sizeof(buf_forpipe)-1);//从管道读取数据
if(size>0)
{
buf_forpipe[size]='\0';
printf("%s",buf_forpipe);
printf("%d \n",++count);
if(count==4)
{
close(pipefd[0]);
break;
}
}
}
int status;
wait(&status);
printf("signal:%d\n",status&0x7f);//打印子进程的退出码
}
}
return 0;
}
上面代码让子进程一直在往管道中写,父进程读取4次后将管道读端关闭,这时如果再尝试进行写入的时候系统就会将进程异常终止,这里把终止信号打印出来为13
总结如下:
- 匿名管道只能用于具有亲缘关系的进程之间通信
- 管道提供流式服务,可以分多次读写
- 生命周期随进程(文件描述符)
- 内置同步互斥机制
- 同步:当没有数据可以读时,read()调用阻塞,一直等到有数据来为止;当管道写满时,write()调用阻塞,一直等到有数据来读
- 互斥:不能同时进行读或者同时进行写数据
- 半双工通信,数据只能一个方向流动,若需要全双工通信,就需要建立两个管道
- 当将管道的所有写端文件描述符关闭,则read()返回值为0.(读到文件结束标志EOF)
- 当将管道的所有读端文件描述符关闭,则write()操作会产生SIGPIPE信号,进程退出
命名管道:
- 匿名管道由pipe函数创建并打开
- 命名管道由mkfifo函数创建,打开open
- FIFO(命名管道)于pipe(匿名管道)之间唯一的区别在于他们创建与打开方式不同
完。