Linux:匿名管道的五大特性(进程间通信三)

元旦快乐,期末周悲伤,这两个星期没时间学习计算机相关内容啦,今天是开年的的一篇博客,延续上一篇博客内容,今天我们来谈谈匿名管道的五大特征~~

先说结论,五大特征分别是:

  1. 匿名管道,只能用来进行具有血缘关系的进程进行进程间通信 (常用与父子)
  2. 管道文件,自带同步机制
  3. 管道是面向字节流的属于半双工的一种特殊情况
  4. 管道是单向通信的任何一个时刻,一个发,一个收 --- 半双工,任何一个时刻,可以同时收发 --- 全双工
  5. (管道) 文件的生命周期,是随进程的

1.匿名管道,只能用来进行具有血缘关系的进程进行进程间通信 (常用与父子)

因为匿名管道创建之初就是想在父子关系中进行通信,所以匿名管道只能在具有血缘关系的进程进行进程间通信

cpp 复制代码
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstdio>
#include <cstring>

void ChildWrite(int wfd)
{
    char buffer[1024];
    int cnt = 0;
    while (true)
    {
        buffer[0] = '\0';
        snprintf(buffer, sizeof(buffer), "我是子进程, pid : %d, cnt : %d", getpid(), cnt++);
        write(wfd, buffer, strlen(buffer));
        sleep(1);
    }
}

void FutherRead(int rfd)
{
    char buffer[1024];
    int cnt = 0;
    while (true)
    {
        buffer[0] = '\0';
        ssize_t n = read(rfd, buffer, sizeof(buffer) - 1);
        if (n > 0)
        {
            buffer[n] = '\0';
            std::cout << buffer << std::endl;
        }
    }
}

int main()
{
    // 创建管道
    int pfd[2] = {0};
    int n = pipe(pfd);
    if (n == -1)
    {
        std::cerr << "pipe error!" << std::endl;
        return 1;
    }
    std::cout << "pfd[0] : " << pfd[0] << std::endl;
    std::cout << "pfd[1] : " << pfd[1] << std::endl;
    // 一般来说,pfd[0] -> 读端   pfd[1] -> 写端
    // 创建子进程
    pid_t id = fork();
    if (id == 0)
    {
        // 关闭子进程读端
        close(pfd[0]);

        // 子进程向管道写入内容
        ChildWrite(pfd[1]);

        // 关闭子进程写端
        close(pfd[1]);
    }
    // 关闭父进程写端
    close(pfd[1]);

    // 父进程读取管道内容
    FutherRead(pfd[0]);

    // 等待子进程结束
    waitpid(id, nullptr, 0);

    // 关闭父进程读端
    close(pfd[0]);
    return 0;
}

2.管道文件,自带同步机制

在上面的代码中,我们是慢写快读,运行结果如下

父进程会等待子进程写入,只有当子进程写入内容时,父进程才会读取管道内容,更深一步,在子进程没有写入时,父进程处于阻塞状态,这就是管道文件自带的同步机制

3.管道是面向字节流的,属于半双工的一种特殊情况

cpp 复制代码
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstdio>
#include <cstring>

void ChildWrite(int wfd)
{
    char buffer[1024];
    int cnt = 0;
    while (true)
    {
        buffer[0] = '\0';
        snprintf(buffer, sizeof(buffer), "我是子进程, pid : %d, cnt : %d", getpid(), cnt++);
        write(wfd, buffer, strlen(buffer));
        //sleep(1);
    }
}

void FutherRead(int rfd)
{
    char buffer[1024];
    int cnt = 0;
    while (true)
    {
        sleep(3);
        buffer[0] = '\0';
        ssize_t n = read(rfd, buffer, sizeof(buffer) - 1);
        if (n > 0)
        {
            buffer[n] = '\0';
            std::cout << buffer << std::endl;
        }
    }
}

int main()
{
    // 创建管道
    int pfd[2] = {0};
    int n = pipe(pfd);
    if (n == -1)
    {
        std::cerr << "pipe error!" << std::endl;
        return 1;
    }
    std::cout << "pfd[0] : " << pfd[0] << std::endl;
    std::cout << "pfd[1] : " << pfd[1] << std::endl;
    // 一般来说,pfd[0] -> 读端   pfd[1] -> 写端
    // 创建子进程
    pid_t id = fork();
    if (id == 0)
    {
        // 关闭子进程读端
        close(pfd[0]);

        // 子进程向管道写入内容
        ChildWrite(pfd[1]);

        // 关闭子进程写端
        close(pfd[1]);
    }
    // 关闭父进程写端
    close(pfd[1]);

    // 父进程读取管道内容
    FutherRead(pfd[0]);

    // 等待子进程结束
    waitpid(id, nullptr, 0);

    // 关闭父进程读端
    close(pfd[0]);
    return 0;
}

此时是快写慢读,会发现,在3秒后,一次性读取管道内容,并且写的时候不会一直进行,而是在写到一定程度的时候停止,这说明管道是由内存限制的,写满以后就无法继续写了,此时需要读取内容,让管道空间释放(读进程每读取 N 字节,管道就释放 N 字节空间,写进程就能继续写 N 字节),才能继续写入内容,也印证了上面的结论--管道文件自带同步机制,而且我们不关心怎么写入,怎么读取,因为管道是面向字节流的,属于半双工的一种特殊情况!

  • 管道不关心数据的 "写入批次"(比如写进程分 10 次写、每次写 1KB),只把数据当成连续的字节流存在缓冲区;
  • 读进程慢读(3 秒后才读)时,管道里已经积累了所有未读字节,读进程调用read()会一次性取走所有可用字节(只要读缓冲区足够大);
  • 对比 "面向消息" 的 IPC(比如消息队列):消息队列必须按 "消息批次" 读,而管道无此限制 ------ 这是字节流最核心的特征,也是 "不关心怎么写入、怎么读取" 的本质原因。

4.管道是单向通信的,任何一个时刻,一个发,一个收 --- 半双工,任何一个时刻,可以同时收发 --- 全双工

在最开始,我们需要一个即基于现有情况,而且实现简单的进程间通信,所以我们决定使用文件来进行,并且一端关闭读,一端关闭写,我们将这种叫做单向通信,形象的称为管道通信,而匿名管道只是我们为了区分不同的管道通信而取的名字

由于我们双方各关闭了一个端口,于是只能一个写,一个读,不能同时读写,所以我们将这种成为半双工

而全双工,可以类比生活中两个人发生口角,两个人不仅要输出,而且还要接受别人的输出

5.(管道) 文件的生命周期,是随进程的

之前我们就知道,文件的生命周期,是随进程的,比如我们打开了一个文件,却忘了关闭,我们不用担心,当进程结束的时候,操作系统会自动帮我们回收(文件引用计数清零,操作系统帮我们关闭)

同理,管道也是这样的,我们通过pipe打开管道,其实相当于打开两个文件(pfd[2]),在外面忘记关掉的时候,系统会帮我们解决这个问题

尽管操作系统会兜底,但工程上依然建议你手动调用close()关闭管道 fd:

  • 避免 "fd 泄漏":如果进程长期运行(比如守护进程),反复创建管道却不关闭 fd,会耗尽进程的 fd 资源(每个进程的 fd 数量有限,默认几百 / 几千)
  • 明确逻辑:手动关闭 fd 能让代码逻辑更清晰,也符合 "谁打开、谁关闭" 的资源管理原则

好啦,这就是关于匿名管道的五大特征啦,我们下一篇博客会讲四种通信情况,不见不散哦~~

相关推荐
naruto_lnq2 分钟前
C++与自动驾驶系统
开发语言·c++·算法
奶茶树11 分钟前
【数据结构进阶】AVL树(详解)
数据结构·c++
面条有点辣19 分钟前
C++内存管理基础概念入门到理解
c++
蒹葭玉树23 分钟前
【C++上岸】C++常见面试题目--网络篇(第二十六期)
网络·c++·面试
若风的雨29 分钟前
【 ARMv8多核处理器启动方案设计】
linux·arm开发
是火云哦29 分钟前
打包你的开发环境:Docker 从入门到上瘾
运维·docker·容器
老四啊laosi29 分钟前
[C++初阶] 10. string模拟实现
c++·stl·string
我送炭你添花37 分钟前
树莓派 3B+ 部署 TR-069 ACS(自动配置服务器)GenieACS 实录
运维·服务器·网络协议
皓月盈江42 分钟前
MoonBit国产编程语言创建新包和使用新包
linux·moonbit·国产编程语言·moonbit教程·moonbit创建包·moonbit使用包
EmbedLinX44 分钟前
C++ STL 学习笔记
c++·stl