当前位置: 首页 > news >正文

pipe匿名管道实操(Linux)

管道相关函数

1 pipe

  • 是 Unix/Linux 系统中的一个系统调用,用于创建一个匿名管道
#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 错误码

错误码宏含义
EPERM1操作无权限
ENOENT2文件或目录不存在
EINTR4系统调用被中断
EIO5输入/输出错误
EBADF9错误的文件描述符
EAGAIN11资源暂时不可用
ENOMEM12内存不足
EACCES13权限不足
EFAULT14非法内存访问
EEXIST17文件已存在
EDOM33数学参数超出定义域
ERANGE34结果超出范围

一般和和strerror配合一起使用 

#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 值)转换为可读的错误描述字符串。下面我会详细解释它的用法和实际应用场景。
#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 访问(尽管实践中很少需要)。

fork() 系统调用详解

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

6 exit

  • 是一个标准库函数,用于终止当前进程,并返回一个状态码给操作系统。它是进程正常退出的标准方式
#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)

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

    getpid()  getppid()

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

    sizeof 

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

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

    语法规则

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

    示例 

    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

    mypipe:mypipe.cc
    	g++ -o $@ $^ -std=c++11
    .PHONY:clean
    clean:
    	rm -rf mypipe

    mypipe.cc

    #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

    ctrlProcess:ctrlProcess.cc
    	g++ -o $@ $^ -std=c++11
    .PHONY:clean
    clean:
    	rm -rf ctrlProcess
    

    Task.hpp

    #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

    #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;
    }

    相关文章:

  • SpringBoot集成Ollama本地模型
  • AllData数据中台升级发布 | 支持K8S数据平台2.0版本
  • 系统变量和用户变量的区别是什么
  • Android WiFi获取动态IP地址
  • python函数的定义与使用
  • Docker Harbor
  • 连表查询的时候,子查询的条件应该写到子查询里面,不能放到外面
  • 大模型在网络安全领域的七大应用
  • qml之锚点Anchors
  • Google Cloud Next‘25大会 Gemini 支持 Anthropic MCP 协议及推出 A2A 协议剑指医疗AI情况分析
  • QBitmap、QPixmap、QImage 和 QPicture 使用方法和特点以及转换
  • Windows10 ssh无输出 sshd服务启动失败 1067报错 公钥无法认证链接 解决办法
  • Android 中绕过hwbinder 实现跨模块对audio 的HAL调用
  • Java面试黄金宝典45
  • POSIX线程(pthread)库:线程的终止与管理
  • C#异步方法返回Task<T>的同步调用
  • LLM相关代码笔记
  • 【Docker基础】容器技术详解:生命周期、命令与实战案例
  • Java网络编程实战(多人聊天室-CS模式)
  • ollama加载本地自定义模型