目录
1.管道概念
2.管道分类
1.匿名管道
1.基本实现与概念
2.站在文件描述符角度-深度理解管道
3.站在内核角度-管道本质
4.管道读写规则
5.管道属性设置与阻塞验证
6.管道特点(匿名)
2.命名管道
1.创建一个命名管道
2.命名管道的打开规则
3.匿名管道与命名管道的区别
管道是Linux中的最古老的通信方式;
我们把一个进程链接到另一个进程的一个数据流称为一个"管道";
头文件: #include
功能:创建一无名管道原型int pipe(int fd[2]);
参数fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端返回值:成功返回0,失败返回错误代码
通常用fork函数来共享管道;接下来我们来实现一下匿名管道的基本读写的实现:
不关闭描述符
#include
#include
#include
#include
int main(){
int fd[2];
int ret=pipe(fd);
if(ret<0){
perror("pipe");
return 0;
}
pid_t pid=fork();
if(pid<0){
perror("fork");
return 0;
}else if(pid==0){
//child
//子进程进行接受
char buf_read[1024]={0};
read(fd[0],buf_read,sizeof(buf_read)-1);
printf("The content is :%s\n",buf_read);
}else{
//father
//父进程发送数据
char buf_write[]="success!";
write(fd[1],buf_write,strlen(buf_write));
wait(NULL);
}
return 0;
}
运行结果
我们可以看出来在父进程向管道写入信息,在子进程读取管道信息.
关闭描述符
#include
#include
#include
#include
int main(){
int fd[2];
int ret=pipe(fd);
if(ret<0){
perror("pipe");
return 0;
}
pid_t pid=fork();
if(pid<0){
perror("fork");
return 0;
}else if(pid==0){
//child
//子进程进行接受
close(fd[1]);
char buf_read[1024]={0};
read(fd[0],buf_read,sizeof(buf_read)-1);
printf("The content is :%s\n",buf_read);
}else{
//father
//父进程发送数据
close(fd[0]);
char buf_write[]="success!";
write(fd[1],buf_write,strlen(buf_write));
wait(NULL);
}
return 0;
}
运行结果
我们可以看出依旧可以进行读写~原因是什么呢?
首先父进程创建管道,系统默认打开三个文件:标准输入,标准输出,和标准错误;创建管道则有fd[0],fd[1];分别表示管道的读端和写端;
然后父进程创建子进程,子进程拷贝父进程,也拷贝了管道信息;所以父子进程共享管道;
父进程关闭读端,子进程关闭写端,但是管道是共享的,依旧可以进行信息交互;
看待管道,就如同看待文件一样!管道的使用和文件一致,迎合了“Linux一切皆文件思想”。
当没有数据可读时O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
当管道满的时候O_NONBLOCK disable: write调用阻塞,直到有进程读走数据O_NONBLOCK enable:调用返回-1,errno值为EAGAIN如果所有管道写端对应的文件描述符被关闭,则read返回0
如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出
当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。
fcntl函数原型:
#include
#include
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd ,struct flock* lock);
(1)F_DUPFD
与dup函数功能一样,复制由fd指向的文件描述符,调用成功后返回新的文件描述符,与旧的文件描述符共同指向同一个文件。
(2)F_GETFD
读取文件描述符close-on-exec标志
(3)F_SETFD
将文件描述符close-on-exec标志设置为第三个参数arg的最后一位
(4)F_GETFL
获取文件打开方式的标志,标志值含义与open调用一致
(5)F_SETF
设置文件打开方式为arg指定方式
设置读端为非阻塞:
#include
#include
#include
int main(){
int fd[2];
int ret=pipe(fd);
if(ret<0){
perror("pipe");
return 0;
}
pid_t pid=fork();
if(pid<0){
perror("fork");
return 0;
}else if(pid==0){
//child
//关闭写端
//设置读端为非阻塞属性
//读
close(fd[1]);
int flag=fcntl(fd[0],F_GETFL);
fcntl(fd[0],F_SETFL,flag|O_NONBLOCK);
char buf_read[1024]={0};
ssize_t read_size=read(fd[0],buf_read,sizeof(buf_read)-1);
printf("read_size:%ld\n",read_size);
perror("read");
}else{
//father
//关闭读端
//写关闭/不关闭
close(fd[0]);
close(fd[1]);
}
while(1){
sleep(1);
}
return 0;
}
运行结果为:
设置写端非阻塞:
#include
#include
#include
int main(){
int fd[2];
int ret=pipe(fd);
if(ret<0){
perror("pipe");
return 0;
}
pid_t pid=fork();
if(pid<0){
perror("fork");
return 0;
}else if(pid==0){
//child
//关闭子进程读端
//设置子进程写段为非阻塞
//子进程写
close(fd[0]);
int flag=fcntl(fd[1],F_GETFL);
fcntl(fd[1],F_SETFL,flag|O_NONBLOCK);
ssize_t write_size;
while(1){
write_size=write(fd[1],"a",1);
if(write_size<0){
break;
}
}
printf("write_size:%ld\n",write_size);
}else{
//father
//关闭父进程写端
//关闭父进程读端/不关闭
close(fd[1]);
//close(fd[0]);
while(1){
sleep(1);
}
}
return 0;
}
运行结果:
只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;
通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
管道提供流式服务一般而言,进程退出,管道释放,所以管道的生命周期随进程一般而言,内核会对管道操作进行同步与互斥管道是半双工的;
数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;
管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
命名管道是一种特殊类型的文件
$ mkfifo pipe1
执行结果:
也可以程序创建
函数原型:
#include
#include
int mkfifo(const char *filename,mode_t mode);
代码如下:
#include
#include
#include
int main(){
mkfifo("pipe2",0664);
return 0;
}
运行结果
如果当前打开操作是为读而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
O_NONBLOCK enable:立刻返回成功如果当前打开操作是为写而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
O_NONBLOCK enable:立刻返回失败,错误码为ENXIO
匿名管道由pipe函数创建并打开。
命名管道由mkfifo函数创建,打开用openFIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。