目录
1 匿名管道的特性
1.1 半双工
- 数据只能从管道的写端流向管道的读端,并不支持双向通信。
- 具有亲缘性关系的进程才能进行进程间通信
- 在内核当中是没有任何的标识符的,其他的进程是没有办法通过标识符找到这个匿名管道对应的缓冲区的。只能具有亲缘性关系的进程进行进程间通信。
- 父进程先创建匿名管道,然后父进程再创建子进程,为了保证子进程的文件描述符表当中有匿名管道的读写两端的文件描述符。
- 如果父进程先创建子进程,然后父进程再创建匿名管道,子进程当中就没有匿名管道的读写两端的文件描述符。
1.2 管道的生命周期跟随进程。
- 进程退出之后,管道也就随之被销毁了。
1.3 管道的大小(64k)
- 无脑的向管道当中写,不进行读。 看看什么时候把管道写满,写满需要多少字节。
-
#include <stdio.h> #include <unistd.h> #include <string.h> int main(){ int fd[2]; int ret=pipe(fd); if(ret<0){ perror("pipe:"); return 0; } int count=0; while(1){ const char* s="a"; write(fd[1],s,strlen(s)); printf("count:%d\n",++count); } return 0; }
- 65536字节 -> 64K
-
1.4 管道提供字节流服务
- 从读端进行读的时候,是将数据从管道当中读走了
- 1.读端读的时候,是将管道的内容读走了
-
#include <stdio.h> #include <unistd.h> #include <string.h> int main(){ int fd[2]; int ret=pipe(fd); if(ret<0){ perror("pipe:"); return 0; } //写 write(fd[1],"hello",5); //第一次读 char buf[1024]={0}; read(fd[0],buf,sizeof(buf)-1); printf("first buf:%s\n",buf); //第二次读 memset(buf,'\0',sizeof(buf)); read(fd[0],buf,sizeof(buf)-1); printf("second buf:%s\n",buf); return 0; }
- 第二次读的时候,管道中已经没内容了,read被阻塞了。
-
- 2.读端可以自定义选择读多少内容
-
#include <stdio.h> #include <unistd.h> #include <string.h> //选择性读 int main(){ int fd[2]; int ret=pipe(fd); if(ret<0){ perror("pipe:"); return 0; } //写 write(fd[1],"hello",5); //读 while(1){ char buf[2]={0}; read(fd[0],buf,sizeof(buf)-1); printf("buf:%s\n",buf); } return 0; }
-
- 1.读端读的时候,是将管道的内容读走了
1.5 pipe_size
- pipe_size:4096字节
- pipe_size是保证读写时原子性的阀值;当读/写小于pipe_ size的时候,管道保证读写的原子性。
- 原子性
- 一个操作要么全部执行了,要么一个都没有执行,不存在中间状态(非黑即白)。
- 从管道角度理解原子性
- 读/写操作在同一时刻只有一个进程在操作,保证进程在操作的时候,读写操作不会被其他进程打扰。即进程A往管道当中写的时候,如果写入的数据小于4096字节,此时进程B需要读,则管道不会让进程B进行读,如果进程A往管道当中写入的数据大于4096字节,超过阈值,此时进程B需要读,则管道不会再阻止进程B进行读。
1.6 阻塞属性
- 前提:当调用pipe创建出来的读写两端的文件描述符的属性人默认都是阻塞属性
- 当write一直调用写,读端不去读,则写满之后writ会阻塞
-
#include <stdio.h> #include <unistd.h> int main(){ int fd[2]; int ret=pipe(fd); if(ret<0){ perror("pipe:"); return 0; } //写 int count=70000; while(count--){ write(fd[1],"w",1); } return 0; }
-
- 当read一直进行读,写端不去写,当管道的内部被读完之后,则read会阻塞
-
#include <stdio.h> #include <unistd.h> //read一直读 int main(){ int fd[2]; int ret=pipe(fd); if(ret<0){ perror("pipe:"); return 0; } //读 char buf[10224]={0}; read(fd[0],buf,sizeof(buf)-1); return 0; }
-
- 当write一直调用写,读端不去读,则写满之后writ会阻塞
2.设置非阻塞属性
- int fcntl(int fd, int cmd, ... /* arg */ );
- 参数:
- fd:待要操作的文件描述符
- cmd:
- 告知fcntl函数做什么操作
- F_GETFL : 获取文件描述符的属性信息
- F_SETFL : 设置文件描述符的属性信息,设置新的属性放到可变参数列表当中
- 告知fcntl函数做什么操作
- 返回值:
- 如果参数cmd传入的值为F_GETFL:返回文件描述符的属性信息
- 如果参数cmd传入的值为F_ SETFL :
- 返回0 : 设置成功
- 返回-1 : 设置失败
- 代码:
-
#include <stdio.h> #include <unistd.h> #include <fcntl.h> int main(){ int fd[2]; int ret=pipe(fd); if(ret<0){ perror("pipe:"); return 0; } //获取fd[0]的属性信息 int read_ret=fcntl(fd[0],F_GETFL); printf("read_ret:%d\n",read_ret); //获取fd[1]的属性信息 int write_ret=fcntl(fd[1],F_GETFL); printf("write_ret:%d\n",write_ret); return 0; }
- 参数:
- 设置非阻塞属性的时候,宏的名字为 0_ NONBLOCK
- 第一步:
- 先获取文件描述符原来的属性int flag = fcnt1(fd[0], F_ GETFL) ;
- 第二步:
- 设置文件描述符新的属性的时候,不要忘记将原来来的属性也带上。新的属性=老的属性|增加的属性,fcnt1(fd[0],F_ SETFL, flag| 0_ NONBLOCK) ;
-
#include <stdio.h> #include <unistd.h> #include <fcntl.h> int main(){ int fd[2]; int ret=pipe(fd); if(ret<0){ perror("pipe:"); return 0; } //获取fd[0]的属性信息 int read_ret=fcntl(fd[0],F_GETFL); printf("read_ret:%d\n",read_ret); //获取fd[1]的属性信息 int write_ret=fcntl(fd[1],F_GETFL); printf("write_ret:%d\n",write_ret); fcntl(fd[1],F_SETFL,write_ret|O_NONBLOCK); write_ret=fcntl(fd[1],F_GETFL); printf("write_ret:%d\n",write_ret); return 0; }
- 第一步:
- 非阻塞属性
- 读设置成为非阻塞属性,fd[0]:非阻塞:
- 写不关闭,一直读,读端调用read函数之后,read返回值为-1,errno设置为EAGAIN
- 代码实现过程:1.创建匿名管道,2.创建子进程,让父子进程做不一样的事情,子进程进行读,父进程进行写,即关闭父进程读端close(fd[0]),不关闭父进程写端,设置子进程的读端为非阻塞属性,关闭子进程的写端 close(fd[1])
-
#include <stdio.h> #include <unistd.h> #include <fcntl.h> 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[1024]={0}; ssize_t r_size=read(fd[0],buf,sizeof(buf)-1); printf("r_size:%ld\n",r_size); perror("read:"); } else { //father //关闭读端,写关闭/写不关闭 close(fd[0]); //写端不断比,但是写端也不写 while(1){ sleep(1); } } return 0; }
- 父进程的写端没有关闭,但是一直没有往管道里写,由于读端被子进程设置为非阻塞,子进程调用read一直去读,但是管道里并没有内容供read去读,read返回-1,读取失败。
- 写关闭,一直读,读端read函数返回0,表示什么都没有读到
- 关闭父进程读端和写端close(fd[0]),close(fd[1]),设置子进程的读端为非阻塞属性,关闭子进程的写端 close(fd[1])
- 父进程关闭写端,管道中不会有任何内容被写入,子进程调用read去读,读到0个字节,即什么也没读到,read正常返回。
- 写不关闭,一直读,读端调用read函数之后,read返回值为-1,errno设置为EAGAIN
- 写设置成为非阻塞属性,fd[1]:非阻塞
- 读不关闭, 一直写, 当把管道写满之后,1则在调用write, 就会返回-1
- 代码实现过程:1.创建匿名管道,2.创建子进程,让父子进程做不一样的事情,子进程进行写,父进程进行读,即关闭父进程写端close(fd[1]),不关闭父进程读端,设置子进程的写端为非阻塞属性,关闭子进程的读端 close(fd[0])
-
#include <stdio.h> #include <unistd.h> #include <fcntl.h> 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 w_size=0; while(1){ w_size=write(fd[1],"a",1); if(w_size<0){ break; } } printf("w_size=%ld\n",w_size); perror("write:"); } else{ //father //关闭写端 close(fd[1]); //不关闭读端,也不去读 while(1){ sleep(1); } } return 0; }
- 子进程一直往管道里写,父进程一直没有读,当管道写满后,由于子进程写端被设置为非阻塞属性,当子进程再次调用write时,管道当中已经没有空间可以写入,write返回-1。
- 读关闭,一直写, 写端调用write进行写的时候, 就会发生崩溃。 (本质原因是因为写端的进程,收到 了SIGPIPE信号,导致写端进程崩溃)
- 读不关闭, 一直写, 当把管道写满之后,1则在调用write, 就会返回-1
- 读设置成为非阻塞属性,fd[0]:非阻塞: