Linux系统基础-进程间通信(3)_模拟实现匿名管道

个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创

Linux系统基础-进程间通信(3)_模拟实现匿名和命名管道

收录于专栏[Linux学习]
本专栏旨在分享学习Linux的一点学习笔记,欢迎大家在评论区交流讨论💌

目录

[1. 模拟实现匿名管道](#1. 模拟实现匿名管道)

[实现的功能 :](#实现的功能 :)

[实现方法 :](#实现方法 :)

[1. 获取消息的函数](#1. 获取消息的函数)

[2. 子进程写入模块](#2. 子进程写入模块)

[3. 父进程读取模块](#3. 父进程读取模块)

[4. 主函数](#4. 主函数)

[2. 效果展示:](#2. 效果展示:)


1. 模拟实现匿名管道

实现的功能 :

通过管道实现进程间通信, 父子进程之间的数据传输是单向的 (父进程只读, 子进程只写)

实现方法 :

1. 获取消息的函数

功能 : 生成一个包含消息ID 和当前进程ID 的字符串, 作为子进程发送给父进程的消息的一部分.

cpp 复制代码
std::string getOtherMessage()
{
    static int cnt = 0;
    std::string messageid = std::to_string(cnt); //stoi -> string -> int
    cnt++;
    pid_t self_id = getpid();
    std::string stringpid = std::to_string(self_id);

    std::string message = "messageid:";
    message += messageid;
    message += "my pid is : ";
    message += stringpid;

    return message;
}

这个函数的核心目的是生成一个包含唯一消息ID和当前进程ID的字符串, 用于进程间通信或调试输出, 通过使用静态变量cnt, 每次调用该函数时都能生成唯一的消息ID, 而通过getpid()函数获取当前进程的PID, 可以帮助识别消息的来源进程.

这种机制在多进程编程中非常有用, 特别是在使用管道, 信号量或其他IPC (进程间通信) 机制时.

2. 子进程写入模块

功能 : 子进程通过管道向父进程写入

cpp 复制代码
void SubProcessWrite(int wfd)
{
    std::string message = "father, I am your son process!";
    while(true)
    {
        std::cerr << "++++++++++++++++++++++++++++++++++++++" << std::endl;
        std::string info = message + getOtherMessage(); //这条消息, 就是我们子进程发给父进程的消息
        write(wfd, info.c_str(), info.size());//写入管道的时候, 没有写入\0, 有没有必要

        std::cerr << info << std::endl;
    }
    std::cout << "child quit ..." << std::endl;
}

关于\0问题 :

\0 字符: 在 C++ 中,字符串是以 null 字符 \0 结尾的。write() 函数写入的是指定长度的字节(由 info.size() 决定),因此不需要显式写入 \0。当父进程读取这条消息时,读取的字节数是已知的,所以不需要依赖字符串的结尾。

使用标准错误流打印的原因:

1. 区分标准输出和错误输出

std::cerr 是标准错误流,用于输出错误信息或调试信息,而 std::cout 是标准输出流,用于正常的程序输出。

将调试信息或错误信息发送到 std::cerr,可以清晰地将这些信息与正常的程序输出区分开来。这在复杂程序中尤其重要,因为它有助于在审查日志时快速识别问题。

2. 输出缓冲机制

std::cout 通常是带缓冲的输出流,可能会因为缓冲区未满而延迟输出信息。这在某些情况下可能导致调试信息不能立即看到。

相比之下,std::cerr 是不带缓冲的,意味着信息会立即被输出。这在调试过程中非常有用,因为你希望能即时看到任何错误或状态信息,而不是等到缓冲区填满。

3. 调试和监控

在开发和调试过程中,使用 std::cerr 输出调试信息(如进程状态、错误信息等)是个常见做法,可以帮助开发者实时监控程序的运行状态。

如果程序崩溃或出现异常,std::cerr 中的输出通常会被保留下来,而 std::cout 的输出可能因为程序的崩溃而丢失。

总结:

功能: SubProcessWrite函数的主要目的是通过管道向父进程发送消息。它构造了一条包含固定消息和动态消息的字符串,并通过管道不断地发送。

3. 父进程读取模块

功能 : 父进程从管道中读取子进程发送的消息

cpp 复制代码
void FatherProcessRead(int rfd)
{
    char inbuffer[size];
    while(true)
    {
        sleep(2);
        std::cout << "------------------------------------" << std::endl;
        size_t n = read(rfd, inbuffer, sizeof(inbuffer) - 1);//sizeof(inbuffer) -> strlen(inbuffer)
        if(n > 0)
        {
            inbuffer[n] = 0; // == '\0'
            std::cout << inbuffer << std::endl;
        }
        else if(n == 0)
        {
            //如果read返回值是0, 表示写端直接关闭了, 我们读到了文件的结尾
            std::cout << "client quit father get return val: " << n << "father quit too!" << std::endl;
            break;
        }
        else if(n < 0)
        {
            std::cerr << "read error" << std::endl;
            break;
        }
    }
}

FatherProcessRead函数的主要目的是从子进程通过管道读取数据并输出到标准输出。它处理三种读取结果:正常读取、读到文件结束和读取错误

4. 主函数

功能 : 管理进程的创建和管道的操作

cpp 复制代码
int main()
{
    // 1. 创建管道
    int pipefd[2];
    int n = pipe(pipefd); // 输出型参数, rfd, wfd
    if (n != 0)
    {
        std::cerr << "errno: " << errno << ": " << "errstring : " << strerror(errno) << std::endl;
        return 1;
    }
    //pipefd[0] -> 0 -> r(读) pipefd[1] -> 1 -> w(写)
    std::cout << "pipefd[0]: " << pipefd[0] << ", pipefd[1]: " << pipefd[1] << std::endl;
    sleep(1);

    //2. 创建子进程
    pid_t id = fork();
    if(id == 0)
    {
        std::cout << "子进程关闭不需要的fd了, 准备发消息了" << std::endl;
        sleep(1);
        //子进程 --- write
        //3. 关闭不需要的fd
        close(pipefd[0]);

        SubProcessWrite(pipefd[1]);
        close(pipefd[1]);
        exit(0);
    }

    std::cout << "父进程关闭不需要的fd了, 准备收消息了" << std::endl;
    sleep(1);
    //父进程 --- read
    //3. 关闭不需要的fd
    close(pipefd[1]);
    FatherProcessRead(pipefd[0]);
    std::cout << "5S, father close rfd" << std::endl;
    sleep(5);
    close(pipefd[0]);

    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid > 0)
    {
        std::cout << "wait child process done, exit sig : " << (status&0x7f) << std::endl;
        std::cout << "wait child process done, exit code(ign) : " << ((status >> 8)& 0xFF) << std::endl; 
    }
    return 0;
}

pipefd: 这是一个整数数组,pipefd[0] 用于读取数据(读端),pipefd[1] 用于写入数据(写端)。

**pipe():**创建管道,如果成功则返回 0,并将读端和写端的文件描述符分别存储在 pipefd[0] 和 pipefd[1] 中。

fork(): 创建一个子进程。返回值 id 为 0 表示当前进程是子进程。

调用 SubProcessWrite(pipefd[1]),假设这个函数负责将数据写入管道。

写入完成后,关闭写端 pipefd[1],并通过 exit(0) 结束子进程。

父进程关闭管道的写端 pipefd[1],因为它只需要读取数据。

调用 FatherProcessRead(pipefd[0]),假设这个函数负责从管道中读取数据。

等待子进程结束并获取状态 :

**waitpid():**等待子进程结束并获取其状态。

状态处理:

status & 0x7f用于获取子进程的退出信号。

**((status >> 8) & 0xFF)**用于获取子进程的退出码。

如果成功,输出子进程的退出信号和退出码。

2. 效果展示:

将我们的程序编译运行 :

相关推荐
Stanford_11069 分钟前
【2026新年启程】学习之路,探索之路,技术之路,成长之路……都与你同行!!!
前端·c++·学习·微信小程序·排序算法·微信开放平台
rocksun15 分钟前
Neovim,会是你的下一款“真香”开发神器吗?
linux·python·go
youngee1115 分钟前
hot100-60子集
数据结构·算法
郝学胜-神的一滴18 分钟前
Linux线程属性设置分离技术详解
linux·服务器·数据结构·c++·程序人生·算法
qq_3176203118 分钟前
01:Docker 概述
运维·docker·容器·docker安装
知识分享小能手20 分钟前
Ubuntu入门学习教程,从入门到精通, Ubuntu 22.04中的进程管理详解(15)
linux·学习·ubuntu
Timmylyx051821 分钟前
2025年最后一搏—— Educational Codeforces Round 186 (Rated for Div. 2) 题解
算法·codeforces·比赛日记
zfj32124 分钟前
Linux内核和发行版的的区别、职责
linux·运维·服务器·内核·linux发行版
微光闪现35 分钟前
国际航班动态提醒与延误预测优选平台指南
大数据·人工智能·算法
leoufung35 分钟前
LeetCode 120. Triangle:从 0 分到 100 分的思考过程(含二维 DP 与空间优化)
linux·算法·leetcode