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 能让代码逻辑更清晰,也符合 "谁打开、谁关闭" 的资源管理原则

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

相关推荐
忧郁的橙子.2 分钟前
02-本地部署Ollama、Python
linux·运维·服务器
醇氧11 分钟前
【linux】查看发行版信息
linux·运维·服务器
No8g攻城狮44 分钟前
【Linux】Windows11 安装 WSL2 并运行 Ubuntu 22.04 详细操作步骤
linux·运维·ubuntu
酷酷的崽7981 小时前
CANN 生态可维护性与可观测性:构建生产级边缘 AI 系统的运维体系
运维·人工智能
做人不要太理性1 小时前
CANN Runtime 运行时组件深度解析:任务调度机制、存储管理策略与维测体系构建逻辑
android·运维·魔珐星云
XiaoFan0121 小时前
免密批量抓取日志并集中输出
java·linux·服务器
souyuanzhanvip1 小时前
ServerBox v1.0.1316 跨平台 Linux 服务器管理工具
linux·运维·服务器
文静小土豆1 小时前
Docker 与 containerd 代理配置详解:镜像拉取速度慢的终极解决方案
运维·docker·容器
rainbow68892 小时前
EffectiveC++入门:四大习惯提升代码质量
c++
秋邱2 小时前
用 Python 写出 C++ 的性能?用CANN中PyPTO 算子开发硬核上手指南
开发语言·c++·python