1.管道的概念
管道本质上是内核中的一个缓冲区,让具有血缘关系的进程之间可以进行通信。它其实相当于一个伪文件,不占用磁盘空间,我们可以通过和对文件一样的读写方式去操作管道。
管道具有两部分,读端和写端,分别对应两个文件描述符,数据从写端流入,从读端流出。
操作管道的进程被销毁之后,管道会被系统自动释放。
管道读写的时候默认是阻塞的。
2.管道的原理
管道的内部是通过循环队列实现的,先进先出。
管道的大小即缓冲区的大小默认是4k,但会根据实际情况做适当调整。
3.管道的局限性
由于管道的实现形式是队列,使用数据只能读取一次,不能重复读取。
管道实现形式是半双工的,所以在一端写数据的时候,另一段只能读数据,不能写数据。
匿名管道pipe只适合于有血缘关系的进程使用。
4.创建匿名管道
int pipe(int fd[2]);
fd:传出参数
fd[0]:读端
fd[1]:写端
5.进程之间使用管道通信
单个进程可以使用管道完成读写操作。
一个进程在读数据的时候要关闭写端,同理,在写数据的时候要关闭读端。
例:父子进程间通信,实现ps aux | grep bash
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <fcntl.h> 5 6 int main() 7 { 8 int fd[2]; 9 int ret = pipe(fd); 10 if(ret == -1){ 11 perror("pipe error:"); 12 exit(-1); 13 } 14 15 pid_t pid = fork(); 16 if(pid == -1){ 17 perror("fork error:"); 18 exit(-1); 19 }else if(pid > 0){ 20 close(fd[0]); 21 dup2(fd[1],STDOUT_FILENO); 22 execlp("ps","ps","aux",NULL); 23 perror("execlp error:"); 24 exit(-1); 25 }else{ 26 close(fd[1]); 27 dup2(fd[0],STDIN_FILENO); 28 execlp("grep","grep","--color=auto","bash",NULL); 29 perror("execlp error:"); 30 exit(-1); 31 } 32 return 0; 33 }
兄弟进程间通信,实现ps aux | grep bash
1 int fd[2]; 2 int ret = pipe(fd); 3 if(ret == -1){ 4 perror("pipe error:"); 5 exit(-1); 6 } 7 8 int i; 9 int n = 2; 10 pid_t pid; 11 for(i=0;i<n;i++){ 12 pid = fork(); 13 if(pid == 0){ 14 break; 15 } 16 } 17 18 if(i == 0){ 19 close(fd[0]); 20 dup2(fd[1],STDOUT_FILENO); 21 execlp("ps","ps","aux",NULL); 22 perror("execlp error:"); 23 exit(-1); 24 }else if(i == 1){ 25 close(fd[1]); 26 dup2(fd[0],STDIN_FILENO); 27 execlp("grep","grep","--color=auto","bash",NULL); 28 perror("execlp error:"); 29 exit(-1); 30 }else{ 31 pid_t wpid; 32 close(fd[0]); 33 close(fd[1]); 34 while((wpid = wait(NULL))!=-1){ 35 printf("child = %d\n",wpid); 36 } 37 printf("child are all died\n"); 38 } 39 return 0; 40 }
6.管道的读写操作
读操作:
有数据时:read(fd)正常读,返回读出的字节数。
无数据时:①写端全部关闭,read接触阻塞,返回0(相当于读文件读到了尾部)
②没有全部关闭,read阻塞
写操作:
读端全部关闭:写的内容超出缓冲区大小时,管道破裂,内核给当前进程发信号SIGPIPE,进程被终止。
读端没有全部关闭:①缓冲区写满了:write阻塞
②缓冲区没有写满:write继续写
设置管道为非阻塞
管道默认两端都为阻塞,由于fcntl函数可以修改文件属性,所以可通过fcntl函数设置管道为非阻塞。如设置读端为非阻塞:
设置方法:
获取原来的flags:int flags = fcntl(fd[0],F_GETFL);
设置新的flags:flags |= O_NONBLOCK;
fcntl(fd[0],F_SETFL,flags);
7.查看管道缓冲区大小
命令:ulimit -a
函数:fpathconf
8.有名管道fifo
特点:
①有文件名
②在磁盘上有这样一个文件(通过ls -l查看 -> p)
③伪文件,大小为0
④在内核中有一个对应的缓冲区
⑤半双工的通信方式
使用场景:
没有血缘关系的进程间通信。
创建方式:
命令:mkfifo 管道名
函数:mkfifo
操作方式:与文件大致相同
open/close
read/write
不能执行lseek操作
进程间通信:
读端:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <fcntl.h> 4 #include <unistd.h> 5 #include <sys/types.h> 6 #include <sys/stat.h> 7 8 int main(int argc,char *argv[]) 9 { 10 if(argc < 2){ 11 printf("./a.out filename\n"); 12 exit(-1); 13 } 14 15 int ret; 16 ret = access(argv[1],F_OK); 17 if(ret == -1){ 18 int r = mkfifo(argv[1],0664); 19 if(r == -1){ 20 perror("mkfifo error:"); 21 exit(-1); 22 } 23 } 24 25 int fd = open(argv[1],O_RDONLY); 26 if(fd == -1){ 27 perror("open error:"); 28 exit(-1); 29 } 30 char buf[128]; 31 while(1){ 32 ret = read(fd,buf,sizeof(buf)); 33 buf[ret] = 0; 34 printf("%s\n",buf); 35 } 36 close(fd); 37 return 0; 38 }
写端:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <fcntl.h> 4 #include <unistd.h> 5 #include <string.h> 6 #include <sys/types.h> 7 #include <sys/stat.h> 8 9 int main(int argc,char *argv[]) 10 { 11 if(argc < 2){ 12 printf("./a.out filename\n"); 13 exit(-1); 14 } 15 16 int ret; 17 ret = access(argv[1],F_OK); 18 if(ret == -1){ 19 int r = mkfifo(argv[1],0664); 20 if(r == -1){ 21 perror("mkfifo error:"); 22 exit(-1); 23 } 24 } 25 int fd = open(argv[1],O_WRONLY); 26 if(fd == -1){ 27 perror("open error:"); 28 exit(-1); 29 } 30 31 char *buf = "hello,world"; 32 while(1){ 33 sleep(1); 34 ret = write(fd,buf,strlen(buf)+1); 35 } 36 close(fd); 37 return 0; 38 }