ipc之无名管道

无名管道的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

  15-IPC之无名管道_IPC


2)如何实现无名管来实现双向通信
  使用两个无名管道,每个管道负责一个方向的通信
  pipe2.png

15-IPC之无名管道_IPC_02

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 什么时候合适使用无名管道呢?

如果通信的进程只有两个,而且还是亲缘进程时,那么可以使用无名管道来通信。
比如:
15-IPC之无名管道_linux网络编程_03