pipe匿名管道实操(Linux)

管道相关函数

1 pipe

  • 是 Unix/Linux 系统中的一个系统调用,用于创建一个匿名管道
cpp 复制代码
#include <unistd.h>
int pipe(int pipefd[2]);
参数说明:
pipefd[2]:一个包含两个整数的数组,用于存储管道的文件描述符:
pipefd[0]:管道的读端(用于从管道读取数据)巧记:用嘴巴口型(o)读
pipefd[1]:管道的写端(用于向管道写入数据)巧记:用笔(1)写
返回值:
成功时返回 0
失败时返回 -1 并设置 errno

2 error

errno 是 C 和 C++ 中用于报告错误的全局变量(或宏),全称为 "error number"。它由系统或标准库函数在操作失败时设置,用于指示具体的错误原因。代码出错时我们更想知道出错原因,就可以用error

常见 errno 错误码

错误码宏 含义
EPERM 1 操作无权限
ENOENT 2 文件或目录不存在
EINTR 4 系统调用被中断
EIO 5 输入/输出错误
EBADF 9 错误的文件描述符
EAGAIN 11 资源暂时不可用
ENOMEM 12 内存不足
EACCES 13 权限不足
EFAULT 14 非法内存访问
EEXIST 17 文件已存在
EDOM 33 数学参数超出定义域
ERANGE 34 结果超出范围

一般和和strerror配合一起使用

cpp 复制代码
#include <iostream>
#include <cerrno>  
#include <cstring>

int main() {
    errno = 0; // 先重置 errno
    double x = sqrt(-1.0); // 尝试计算负数的平方根
    if (errno == EDOM) {   // EDOM 是域错误宏
        std::cerr << "Error: " << std::strerror(errno) << "\n";
    }
}
输出:
Error: Numerical argument out of domain

3 strerror

  • 是 C 标准库中的一个函数,用于将错误代码(errno 值)转换为可读的错误描述字符串。下面我会详细解释它的用法和实际应用场景。
cpp 复制代码
#include <string.h>  
char *strerror(int errnum);
参数说明:
errnum:错误编号(通常是 errno 的值)
返回值:
返回指向错误描述字符串的指针(静态分配的字符串,不可修改)
不会失败(永远返回有效指针)

4 推荐使用 #include <cerrno> 而不是 #include <errno.h>


1. 符合 C++ 标准库的命名规范

C++ 标准库对 C 标准库的头文件进行了重新封装,采用无 .h 后缀的形式(如 <cstdio><cstdlib><cerrno>),以区别于 C 的传统头文件(如 <stdio.h>stdlib.herrno.h>)。

  • <cerrno> 是 C++ 标准化的头文件,明确属于 C++ 标准库。
  • <errno.h> 是 C 风格的头文件,虽然 C++ 兼容它,但不推荐在新代码中使用。

2. 潜在的命名空间管理

理论上,<cerrno> 将相关名称(如 errnoEDOMERANGE)放入 std 命名空间,而 <errno.h> 直接将它们暴露在全局命名空间。虽然实际实现中(由于兼容性要求):

  • errno 仍然是全局宏(无法放入 std)。
  • EDOMERANGE 等宏通常在全局命名空间也可用。

但使用 <cerrno> 能更清晰地表达"这是 C++ 代码"的意图,并可能在未来的标准中更好地支持命名空间隔离。


注意事项

  • errno 仍是全局宏 :即使使用 <cerrno>errno 也不会变成 std::errno(因为它是宏)。
  • 错误码宏(如 EDOM :大多数实现仍允许全局访问,但理论上可以额外通过 std::EDOM 访问(尽管实践中很少需要)。

5 fork() 系统调用详解

  • fork() 是 Unix/Linux 系统中的一个重要系统调用,用于创建一个新的进程(子进程)
cpp 复制代码
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
返回值:
父进程:返回子进程的 PID(进程ID,> 0)。
子进程:返回 0。
出错时:返回 -1(并设置 errno)。

6 exit

  • 是一个标准库函数,用于终止当前进程,并返回一个状态码给操作系统。它是进程正常退出的标准方式
cpp 复制代码
#include <stdlib.h>
void exit(int status);
参数:
status:进程的退出状态码:
0 或 EXIT_SUCCESS:表示成功退出。
非零值(通常 EXIT_FAILURE=1):表示失败退出(具体含义由程序定义)

exit() 的运行机制

(1) 进程终止流程

当调用 exit() 时,操作系统会按顺序执行以下操作:

  1. 调用 atexit() 注册的函数(按注册的逆序执行)。

  2. 刷新所有标准 I/O 缓冲区 (如 printf 未输出的内容会被强制写入)。

  3. 关闭所有打开的文件描述符

  4. 释放进程占用的内存和其他资源

  5. 向父进程发送状态码 (可通过 wait()$? 获取)。

(2) exit() vs _exit()

函数 说明
exit() 标准 C 库函数,会执行清理(刷新缓冲区、调用 atexit() 等)。
_exit() 系统调用(<unistd.h>),直接终止进程,不执行任何清理

7 snprintf

snprintf 是 C 标准库中的一个格式化输出函数,用于安全地格式化字符串并写入缓冲区 ,比传统的 sprintf 更安全,因为它可以防止缓冲区溢出(Buffer Overflow)

cpp 复制代码
#include <stdio.h>
int snprintf(
    char *str,       // 目标缓冲区
    size_t size,     // 缓冲区大小(最多写入 size-1 个字符 + '\0')
    const char *format,  // 格式化字符串(类似 printf)
    ...              // 可变参数(要格式化的数据)
);
返回值:
成功:返回理论写入的字符数(不包括结尾的 \0),即使缓冲区不够。
错误:返回负值(如编码错误)。

8 getpid() getppid()

  • getpid() 是 Unix/Linux 系统编程中的一个基础系统调用,用于获取当前进程的进程ID(PID)
  • getppid() 是 Unix/Linux 系统调用,用于获取当前进程的父进程 PID(Process ID)
cpp 复制代码
#include <unistd.h>  // 必须包含的头文件
pid_t getpid(void);  // 返回当前进程的 PID
返回值:
成功:返回当前进程的 PID(正整数)
不会失败(无错误码)

#include <unistd.h>  // 必须包含的头文件
pid_t getppid(void); // 返回父进程的 PID
返回值:
成功:返回父进程的 PID(正整数)
不会失败(无错误码)

9 sizeof

sizeof 是 C/C++ 中的一个编译时运算符 (不是函数!),用于计算变量、类型或表达式所占的内存大小(字节数)。它是静态计算的,不会在运行时影响程序性能

cpp 复制代码
sizeof(变量或类型)
返回值:
size_t 类型的无符号整数(通常是 unsigned int 或 unsigned long)。
计算时机:在编译时确定,不会执行括号内的代码(如果传入表达式)

语法规则

操作对象 示例 是否必须加括号 备注
变量名 sizeof a 可选 更简洁,但可能降低可读性
类型名 sizeof(int) 必须 不加括号会导致编译错误
表达式 sizeof(a + b) 必须 表达式需用括号包裹

示例

cpp 复制代码
int arr[10];

变量(括号可选)
size_t s1 = sizeof arr;     // 计算数组总大小
size_t s2 = sizeof(arr);    // 等效写法

类型(括号必须)
size_t s3 = sizeof(int);    // 计算 int 类型大小

表达式(括号必须)
size_t s4 = sizeof(arr[0]); // 计算数组元素大小

结构体/类成员的大小
struct S { int x; double y; };
size_t s = sizeof(S::x);  // C++ 中合法,计算成员大小

创建管道实操

makefile

bash 复制代码
mypipe:mypipe.cc
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm -rf mypipe

mypipe.cc

cpp 复制代码
#include <iostream>
#include <string>
#include <cerrno>
#include <cassert>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
    int pipefd[2] = {0};

    int n = pipe(pipefd);
    if(n < 0)
    {
        std::cout << "pipe error, " << errno << ": " << strerror(errno) << std::endl;
        return 1;
    }

    pid_t id = fork();
    assert(id != -1); 

    if(id == 0)
    {
        close(pipefd[0]);

        int cnt = 0;
        while(true)
        {
            char x = 'X';
            write(pipefd[1], &x, 1);
            std::cout << "Cnt: " << cnt++<<std::endl;
            sleep(1);
        }

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

    close(pipefd[1]);

    char buffer[1024];
    int cnt = 0;
    while(true)
    {
        int n = read(pipefd[0], buffer, sizeof(buffer) - 1);
        if(n > 0)
        {
            buffer[n] = '\0';
            std::cout << "我是父进程, child give me message: " << buffer << std::endl;
        }
        else if(n == 0)
        {
            std::cout << "我是父进程, 读到了文件结尾" << std::endl;
            break;
        }
        else 
        {
            std::cout << "我是父进程, 读异常了" << std::endl;
            break;
        }
        sleep(1);
        if(cnt++ > 5) break;
    }
    close(pipefd[0]);

    int status = 0;
    waitpid(id, &status, 0);
    std::cout << "sig: " << (status & 0x7F) << std::endl;

    sleep(100);

    return 0;
}
  • mypipe:目标文件(可执行文件)名称
  • mypipe.cc:依赖文件(源代码文件)
  • g++ -o $@ $^ -std=c++11:编译命令
    • $@ 表示目标文件(mypipe)
    • $^ 表示所有依赖文件(这里只有 mypipe.cc
    • -std=c++11 指定使用 C++11 标准
  • .PHONY:clean:声明 clean 是一个伪目标(不是实际文件)
  • rm -rf mypipe:删除生成的可执行文件

父进程管理多个子进程实现管道通信实操

Makefile

bash 复制代码
ctrlProcess:ctrlProcess.cc
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm -rf ctrlProcess

Task.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <vector>
#include <unistd.h>

typedef void (*fun_t)(); 

void a() { std::cout << "a任务正在执行...\n" << std::endl; }
void b() { std::cout << "b任务正在执行...\n" << std::endl; }
void c() { std::cout << "c任务正在执行...\n" << std::endl; }

#define A 0
#define B 1
#define C 2

class Task
{
public:
    Task()
    {
        funcs.push_back(a);
        funcs.push_back(b);
        funcs.push_back(c);
    }

    void Execute(int command)
    {
        if (command >= 0 && command < funcs.size()) funcs[command]();
    }
    
public:
    std::vector<fun_t> funcs;
};

ctrlProcess.cc

cpp 复制代码
#include <iostream>
#include <string>
#include <vector>
#include <cassert>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include "Task.hpp"
using namespace std;

const int gnum = 3;
Task t;

class EndPoint
{
private:
    static int number;
public:
    pid_t _c_id;
    int _w_fd;
    string processname;
public:
    EndPoint(int id, int fd) :_c_id(id), _w_fd(fd)
    {
        //process-0[pid:fd]
        char namebuffer[64];
        snprintf(namebuffer, sizeof(namebuffer), "process-%d[%d:%d]", number++, _c_id, _w_fd);
        processname = namebuffer;
    }
    string name() const { return processname; }
};

int EndPoint::number = 0;

void WaitCommand()
{
    while(1)
    {
        int command = 0;
        int n = read(0, &command, sizeof(command));
        if (n == sizeof(int)) t.Execute(command);
        else if (n == 0)
        {
            std::cout << "父进程关闭了写端" << getpid() << std::endl;
            break;
        }
        else break;
    }
}

void createProcesses(vector<EndPoint> *end_points)
{
    vector<int> fds;
    for(int i = 0; i < gnum; ++i)
    {
        int pipefd[2] = {0};
        int n = pipe(pipefd);
        assert(n == 0); (void)n;

        pid_t id = fork();
        assert(id != -1);

        if (id == 0)
        {
            for(auto &fd : fds) close(fd);

            close(pipefd[1]);
            dup2(pipefd[0], 0);
            WaitCommand();
            close(pipefd[0]);
            exit(0);
        }
        close(pipefd[0]);

        end_points->push_back(EndPoint(id, pipefd[1]));
        fds.push_back(pipefd[1]);
    }
}

int ShowBoard()
{
    std::cout << "##########################################" << std::endl;
    std::cout << "|   0. 执行日志任务   1. 执行数据库任务    |" << std::endl;
    std::cout << "|   2. 执行请求任务   3. 退出             |" << std::endl;
    std::cout << "##########################################" << std::endl;
    std::cout << "请选择# ";
    int command = 0;
    std::cin >> command;
    return command;
}

void ctrlProcess(const vector<EndPoint> &end_points)
{
    int cnt = 0;
    while(true)
    {
        int command = ShowBoard();
        if (command == 3) break;
        if (command < 0 || command > 2) continue;

        int index = cnt++;
        cnt %= end_points.size();
        string name = end_points[index].name();
        cout << "选择了进程: " <<  name << " | 处理任务: " << command << endl;

        write(end_points[index]._w_fd, &command, sizeof(command));

        sleep(1);
    }
}

void waitProcess(const vector<EndPoint> &end_points)
{
    for(int i = 0; i < end_points.size(); ++i)
    {
        std::cout << "父进程让子进程退出:" << end_points[i]._c_id << std::endl;
        close(end_points[i]._w_fd);

        waitpid(end_points[i]._c_id, nullptr, 0);
        std::cout << "父进程回收了子进程:" << end_points[i]._c_id << std::endl;
    }
}

// #define A 0
// #define B 1
// #define C 2

int main()
{
    vector<EndPoint> end_points;

    createProcesses(&end_points);

    ctrlProcess(end_points);

    waitProcess(end_points);

    return 0;
}
相关推荐
低技术力的Ayase13 分钟前
[UEC++]UE5C++各类变量相关知识及其API(更新中)
开发语言·c++·ue5
前端 贾公子24 分钟前
力扣 283 移动零的两种高效解法详解
算法
YXXY3131 小时前
C/C++内存管理
c++
学习2年半1 小时前
回溯算法:List 还是 ArrayList?一个深拷贝引发的思考
数据结构·算法·list
烁3471 小时前
每日一题(小白)暴力娱乐篇30
java·数据结构·算法·娱乐
努力努力再努力wz2 小时前
【Linux实践系列】:用c/c++制作一个简易的进程池
linux·运维·数据库·c++·c
Wils0nEdwards4 小时前
Leetcode 独一无二的出现次数
算法·leetcode·职场和发展
Y.O.U..4 小时前
力扣HOT100——无重复字符的最长子字符串
数据结构·c++·算法·leetcode
CodeJourney.4 小时前
从PPT到DeepSeek开启信息可视化的全新之旅
数据库·人工智能·算法·excel·流程图
Ludicrouers6 小时前
【Leetcode-Hot100】和为k的子数组
算法·leetcode·职场和发展