使用管道进行IPC通信 | 青训营笔记

132 阅读5分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第5篇笔记

匿名管道

UNIX系统IPC的最古老形式,所有的UNIX系统都支持这种通信机制

管道的特点

  • 管道其实是一个在内核内存中维护的缓冲器,这个缓冲器的存储能力是有限的,不同的操作系统大小不一定相同
  • 管道拥有文件的特质:读操作,写操作,匿名管道没有文件实体,有名管道有文件实体,但不存储数据。可以按照操作文件的方式对于管道进行操作
  • 一个管道是一个字节流,使用管道时不存在消息或者消息边界的概念,从管道读取数据的进程可以读取任意大小的数据块,而不管写入进程写入管道的数据块大小是多少
  • 通过管道传递的数据是顺序的,从管道中读取出来的字节的顺序和他们被写入管道的顺序是完全一样的
    • 管道内部的数据结构是一个循环队列
  • 在管道中的数据的传递方向是单向的,一端用于写入,一端用于读取,管道是半双工的。
    • 半双工:同一时刻只能一端向另一端传递信息
  • 从管道读数据是一次性操作,数据一旦被读走,它就从管道中被拋弃,释放空间以便写 更多的数据,在管道中无法使用lseek() 来随机的访问数据。
  • 匿名管道只能在具有公共祖先的进程(父进程与子进程,或者两个兄弟进程,具有亲缘 关系)之间使用。
    • 管道由文件描述符实现,父子进程共享同样的文件描述符,其中一个指向管道入口,一个指向管道出口

匿名管道的使用

  • 创建匿名管道

    #include <unistd.h>
    int pipe(int pipefd[2]);
    
  • 查看管道缓存大小命令

    • ulimit -a
  • 查看管道缓存大小函数

    #include <unistd.h>
    long fpathconf(int fd, int name);
    

父子进程通过匿名管道通信

/* 
    #include <unistd.h>
    int pipe(int pipefd[2]); 
        功能:创建一个匿名管道,用来进程间通信。
        参数: int pipefd[2] 这个数组是一个传出参数。
            pipefd[0]对应的是管道的读端
            pipefd[1]对应的是管道的写端
        返回值:
            成功0
            失败-1

        管道默认是阻塞的
            如果管道中没有数据,read阻塞
            如果管道中数据满,write阻塞

        注意:匿名管道只能用于具有关系的进程之间的通信(父子进程, 兄弟进程)
*/

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
//子进程发送数据给父进程,父进程读取数据并输出
int main(int argc, char const *argv[])
{
    // fork之前创建管道
    int pipefd[2];
    int ret = pipe(pipefd);
    if(ret == -1){
        perror("pipe");
        exit(-1);
    }

    //创建子进程
    pid_t pid = fork();
    if(pid > 0 ){
        //关闭写端,不关闭如果进行读写,可能进程会读取到自己写入的数据
        close(pipefd[1]);
        //父进程读取数据,从管道的读取端
        char buf[1024] = {0};
        read(pipefd[0],buf,sizeof(buf));
        printf("father get pipe: %s",buf);
    } else if(pid == 0){
        //关闭读端
        cloe(pipefd[0]);
        //子进程写入数据
        char *str = "hello ,i am child\n";
        write(pipefd[1],str,strlen(str));
    }
    return 0;
}

管道的读写特点

  • 读管道:
    • 管道中有数据,read返 回实际读到的字节数。
    • 管道中无数据:
      • 写端被全部关闭,read返回0 (相当于读到文件的末尾)
      • 写端没有完全关闭,read阻塞等待
  • 写管道:
    • 管道读端全部被关闭,进程异常终止(进程收到SIGPIPE信号)
    • 管道读端没有全部关闭:
      • 管道已满,write阻塞
      • 管道没有满,write将数据写入,并返回实际写入的字节数

有名管道

  • 匿名管道,由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道(FIFO) ,也叫命名管道、FIF0文件。
  • 有名管道(FIF0) 不同于匿名管道之处在于它提供了一个路径名与之关联,以FIFO的文件形式存在于文件系统中,并且其打开方式与打开一个普通文件是一样的,这样即使与FIFO 的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO 相互通信,因此,通过FIFO 不相关的进程也能交换数据。
  • 一旦打开了FIFO,就能在它上面使用与操作匿名管道和其他文件的系统调用一样的I/0系统调用了(如read()、 write()和close()) 。与管道一样,FIFO也有一个写入端和读取端,并且从管道中读取数据的顺序与写入的顺序是一样的。

有名管道与匿名管道的不同

  1. FIFO 在文件系统中作为一个特殊文件存在,但FIFO中的内容却存放在内存中。
  2. 当使用FIFO的进程退出后,FIFO文件将继续保存在文件系统中以便以后使用。
  3. FIFO有名字,不相关的进程可以通过打开有名管道进行通信。

有名管道的使用

  • 通过命令

    mkfifo 名字

  • 通过函数

    /*
        #include <sys/types.h>
        #include <sys/stat.h>
        int mkfifo(const char *pathname, mode_t mode);
            参数
                - pathname 管道路径名
                - mode 文件权限 与open的mode一样
            返回值  成功返回0,失败返回1,设置errno
    */
    
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <stdio.h>
    
    int main(int argc, char const *argv[])
    {
        int ret = mkfifo("test", 0777);
        if(ret == -1){
            perror("mkfifo");
            exit(-1);
        }
        return 0;
    }