无名管道的API
#include <unistd.h>
int pipe(int pipefd[2]);
(1)功能
创建一个用于亲缘进程(父子进程)之间通信的无名管道(缓存),并将管道与两个读写文件描述符关联起来
无名管道只能用于亲缘进程之间通信
(2)参数:缓存地址,缓存用于存放读写管道的文件描述符。
从这个参数的样子可以看出,这个缓存就是一个拥有两个元素的int型数组
1)元素[0]:里面放的是读管道的读文件描述符
2)元素[1]:里面放的是写管道的写文件描述符
特别需要注意的是,这里的读和写文件描述符,是两个不同的文件描述符
从这里大家也可以看出,并不是所有的文件描述符,都是通过open函数打开文件得到的
这里无名管道的读、写文件描述符,就是直接在创建管道时得到的,与open没有任何关系
而且这里也根本没办法使用open函数,因为open函数需要文件路径名,无名管道连文件名都没有,
所以说根本就没办法使用open来打开文件,返回文件描述符
(3)返回值:成功返回0,失败则返回-1,并且errno被设置。
无名管道特点
(1)无名管道只能用于亲缘进程之间通信
由于没有文件名,因此进程没办法使用open打开管道文件,从而得到文件描述符,所以只有一种办法,
那就是父进程先调用pipe创建出管道,并得到读写管道的文件描述符
然后再fork出子进程,让子进程通过继承父进程打开的文件描述符,父子进程就能操作同一个管道,从而实现通信
什么样的进程之间,我们可以称为亲缘进程呢
只要是存在继承关系的进程就是亲缘进程,继承关系分为两种。
(1)直接继承关系
父进程————>子进程
(2)间接继承关系
父进程————>子进程————>子进程————>...
读管道时,如果没有数据的话,读操作会休眠(阻塞)
1. 无名管道
1.1 无名管道的通信原理
具体来说就是,内核会开辟一个“管道”,通信的进程通过共享这个管道,从而实现通信
(1)到底什么是管道
内核的代码也是运行在物理内存上的,内核创建一个“管道”,其实就是在内核自己所在的物理内存空间中开辟出一段缓存空间,比如
char buf[1024];
(2)如何操作无名管道
以文件的方式来读写管道,以文件方式来操作时
1)有读写用的文件描述符
2)读写时会用write、read等文件Io函数
(3)为什么叫无名管道
既然可以通过“文件描述符”来操作管道,那么它就是一个文件(管道文件),但是无名管道文件比较
特殊,它没有文件名,正是因为没有文件名,所有被称为无名管道
比如有两个进程,它们是父子进程,需要进行通信,由于父子进程是亲缘进程,此时我就可以使用无名管道通信了
(1)父子进程单向通信
1)实现步骤
(a)父进程在fork之前先调用pipe创建无名管道,并获取读、写文件描述符
(b)fork创建出子进程,子进程继承无名管道读、写文件描述符
(c)父子进程使用各自管道的读写文件描述符进行读写操作,即可实现通信
为了避免干扰,我们通常会把没有使用的文件描述关闭
2)SIGPIPE信号
写管道时,如果管道的读端被close了话,向管道“写”数据的进程会被内核发送一个SIGPIPE信号,
发这个信号的目的就是想通知你,管道所有的“读”都被关闭了。
这就好比别人把水管的出口(读)给堵住了,结果你还一直往里面灌水(写),别人跟定会警告你,因为你这样可能会对水管造成损害,道理其实是类似的。
由于这个信号的默认动作是终止,所以收到这个信号的进程会被终止,如果你不想被终止的
话,你可以忽略、捕获、或者屏蔽这个信号
只有当管道所有的读端都被关闭时,才会产生这个信号,只有还有一个读端开着,就不会产生
pipe1.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <strings.h> #include <signal.h> void fun(int signum){ printf("signum=%d\n",signum ); } int main(int argc, char const *argv[]) { int ret=0; int pipefd[2]={0};//0 读,1写 ret = pipe(pipefd); if(ret == -1){ perror("pipe err:"); exit(1); } ret = fork(); if(ret >0 ){ while(1){ signal(SIGPIPE,fun); close(pipefd[0]); write(pipefd[1],"hello",5); sleep(1); } }else if(ret == 0){ close(pipefd[1]); close(pipefd[0]); while(1){ char buf[30]={0}; bzero(buf, sizeof(buf)); read(pipefd[0],buf,sizeof(buf)); printf("child recv: %s\n",buf ); sleep(1); } } return 0; }
(2)父子进程双向通信
1)单个无名管道无法实现双向通信,为什么?
因为使用单个无名管道来实现双向通信时,父子进程的管道读写肯定都打开,自己发送给对方的数据,就被自己给抢读到。
pipe1.png
2)如何实现无名管来实现双向通信
使用两个无名管道,每个管道负责一个方向的通信
pipe2.png
pipe2.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <strings.h> #include <signal.h> int main(int argc, char const *argv[]) { int ret=0; int pipefd1[2]={0};//0 读,1写 int pipefd2[2]={0};//0 读,1写 ret = pipe(pipefd1); if(ret == -1){ perror("pipe err:"); exit(1); } ret = pipe(pipefd2); if(ret == -1){ perror("pipe err:"); exit(1); } ret = fork(); if(ret >0 ){ close(pipefd1[0]); close(pipefd2[1]); char buf[30]={0}; while(1){ write(pipefd1[1],"hello",5); sleep(1); bzero(buf, sizeof(buf)); read(pipefd2[0],buf,sizeof(buf)); printf("parent recv:%s\n",buf); } }else if(ret == 0){ close(pipefd1[1]); close(pipefd2[0]); char buf[30]={0}; while(1){ bzero(buf, sizeof(buf)); read(pipefd1[0],buf,sizeof(buf)); printf("child recv: %s\n",buf ); sleep(1); write(pipefd2[1],"world",5); } } return 0; }
1.3 无名管道有两个缺点
(1)无法用于非亲缘进程之间
因为非亲缘进程之间没办法继承管道的文件描述符
(2)无法实现多进程之间的网状通信
如果非要使用无名管道实现多进程之间的网状通信的话,文件描述符的继承关系将非常的复杂
所以无名管道基本只适合两个进程间的通信
1.4 什么时候合适使用无名管道呢?
如果通信的进程只有两个,而且还是亲缘进程时,那么可以使用无名管道来通信。
比如: