Linux 命名管道(FIFO)详解:原理分析、源码封装与通信流程图解

命名管道

一、特点

有名字 :在文件系统中以特殊文件形式存在(Linux/Unix 叫 FIFO 文件,Windows 是命名管道)

跨进程 :不要求进程有父子 / 兄弟关系,任何进程知道名字就能通信

半双工:数据单向流动(一端写、一端读),要双向通信需创建两个管道

二、本质

命名管道 = 操作系统内核开辟的一段【内存缓冲区】 + 文件系统中一个【名字入口】

不是真正的文件 ,只是借用文件系统的名字,让任意进程都能找到它。

三、核心

1. 它在内核里,不在磁盘上

  • 数据不存硬盘 ,只存在内核缓冲区(重点)
  • 读写速度极快,和文件 IO 完全不是一回事
  • 关闭后数据消失,不持久

2. 为什么叫 "命名"?

因为:

  • 匿名管道只能父子进程
  • 命名管道在文件系统有路径名 (如 /tmp/mypipe
  • 任何进程只要知道这个名字,就能打开通信

这就是它能跨进程、跨终端、无亲缘关系通信的根本原因。

四、见识命名管道

1. 命令mkfifo

2. C语言封装的mkfifo函数

c++ 复制代码
int mkfifo(const char* pathname, mode_t mode);

(1)功能在文件系统中创建一个 命名管道(FIFO)特殊文件

(2)返回值

  • 0:创建成功
  • -1:创建失败(比如文件已存在、权限不够等)

(3)参数 :const char* pathname

作用:给命名管道起名字 + 放哪里

例如:

c++ 复制代码
mkfifo("./n_pipe", 0666);
  • /home/zincsweet/VSCode_remote/Linux2_vsc/test_named_pipe/n_pipe 就是管道名字
  • 本质:文件路径字符串
  • 任意进程通过这个路径就能找到同一个管道

(4)参数 :mode_t mode

作用:设置管道的访问权限(和 chmod 一样)

常用写法:

c++ 复制代码
0666   所有进程可读可写(最常用)
0644   自己可读写,别人只读

注意:

  • 前面的 0 不能丢,表示八进制
  • 权限最终 = mode & ~umask(系统会做权限掩码)

五、代码运用

1. 初步创建命名管道

函数补充(1) access

c++ 复制代码
#include <unistd.h>
int access(const char *pathname, int mode);

作用:检查当前进程 对某个文件 / 路径 是否有 某种权限 / 是否存在,不打开文件 ,只是做检查

参数说明

  1. pathname :如"./fifo"

  2. mode:(检查存不存在,还是权限)

    • F_OK文件是否存在

    • R_OK:是否可读

    • W_OK:是否可写

    • X_OK:是否可执行

返回值

  • 0 :检查 成功(存在 / 有权限)
  • -1 :检查 失败(不存在 / 没权限)

常见用法

c++ 复制代码
// 判断管道是否存在
if (access("./fifo", F_OK) == -1) {
    // 不存在 → 创建
    mkfifo("./fifo", 0666);
}

c++ 复制代码
#include <unistd.h>
int unlink(const char *pathname);

作用:删除一个文件(包括:普通文件、软链接、命名管道 FIFO) ,就是代码里的 rm 文件名 命令。

返回值

  • 0:删除成功
  • -1:删除失败(文件不存在、权限不够等)

和命名管道的关系

mkfifo 创建的管道文件会一直留在文件系统里,程序退出也不会消失。

所以:

  • 程序启动前 :用 access 判断是否存在
  • 程序退出后 :用 unlink 删除管道文件
  • 否则下次运行可能出问题

2. 完善管道的查存、销毁

3. 打开管道

4. 信息收发

测试代码

六、完整代码

cpp 复制代码
// 核心文件./pipe.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstring>

const std::string gcommfile = "./fifo";

#define FIFO_READ 1
#define FIFO_WRITE 2

class Fifo {
public:
    Fifo(const std::string& commfile = gcommfile) 
        : _commfile(commfile),
        _mode(0666),
        _fd(-1) 
    {}
    
    // 1. 创建命名管道
    void Build() {
        if (IsExist()) {
            std::cout << "命名管道已存在" << std::endl;
            return;
        }

        int n = mkfifo(_commfile.c_str(), _mode);
        if (n == -1) {
            std::cerr << "创建命名管道失败: " << strerror(errno) << std::endl;  // strerror(errno) 获取错误信息
        } else {
            std::cout << "命名管道创建成功" << std::endl;
        }
    }

    // 2. 打开管道
    void Open(int open_mode) {
        // 管道为保障通信(读、写)双方同时存在,一端打开管道时会阻塞,直到另一方打开管道
        // 判断打开模式
        if (open_mode == FIFO_READ) {
            _fd = open(_commfile.c_str(), O_RDONLY);
        }
        else if (open_mode == FIFO_WRITE) {
            _fd = open(_commfile.c_str(), O_WRONLY);
        }
        else {
            std::cerr << "无效的打开模式" << std::endl;
            return;
        }
        
        if (_fd == -1) {
            std::cerr << "打开命名管道失败: " << strerror(errno) << std::endl;  // strerror(errno) 获取错误信息
        } else {
            std::cout << "命名管道打开成功" << std::endl;
        }
    }

    // 信息通信
    void Send(const std::string& msgin) {
        if (_fd == -1) {
            std::cerr << "管道未打开,无法发送消息" << std::endl;
            return;
        }

        ssize_t n = write(_fd, msgin.c_str(), msgin.size());
        if (n == -1) {
            std::cerr << "发送消息失败: " << strerror(errno) << std::endl;
        }
    }

    int Recv(std::string& msgout) {
        if (_fd == -1) {
            std::cerr << "管道未打开,无法接收消息" << std::endl;
            return -1;
        }

        char buffer[1024];
        ssize_t n = read(_fd, buffer, sizeof(buffer) - 1);
        if (n == -1) {
            // 接收消息失败
            return -1;
        } else if (n == 0) {
            // 管道已关闭
            return 0;
        }
        else {
            buffer[n] = '\0';   // n 是实际读取的字节数,确保字符串正确结束,用n下标设置'\0'
            msgout = buffer;
            return n;
        }
    }
    
    // 3. 销毁管道
    void Destroy() {
        // unlink() 函数用于删除一个文件或命名管道
        int n = unlink(_commfile.c_str());
        if (n == -1) {
            std::cerr << "销毁命名管道失败: " << strerror(errno) << std::endl;  // strerror(errno) 获取错误信息
        } else {
            std::cout << "命名管道销毁成功" << std::endl;
        }
    }

    ~Fifo() {}

private:
    bool IsExist() {
        // access() 函数用于检查文件是否存在
        return access(_commfile.c_str(), F_OK) == 0;
    }

private:
    std::string _commfile;
    mode_t _mode = 0666;
    int _fd;
};
cpp 复制代码
// 文件./client.cpp
#include <iostream>
#include "pipe.hpp"

int main() {
    // 打开管道
    Fifo fileclient;
    fileclient.Open(FIFO_WRITE);
    while (true) {
        std::cout << "Client@请输入要发送的消息: ";
        std::string msg;
        std::getline(std::cin, msg);
        fileclient.Send(msg);
    }
    return 0;
}
cpp 复制代码
// 文件./server.cpp
#include <iostream>
#include "pipe.hpp"

int main() {
    // 创建管道 && 打开管道

    Fifo pipefifo;
    pipefifo.Build();
    pipefifo.Open(FIFO_READ);

    std::string msg;
    while (true) {
        int n = pipefifo.Recv(msg);
        if (n == -1) {
            std::cerr << "client:接收消息失败" << std::endl;
            break;
        }
        else if (n == 0) {
            std::cout << "client:管道已关闭" << std::endl;
            break;
        }
        else {
            std::cout << "client:收到消息: " << msg << std::endl;
        }
    }
    
    pipefifo.Destroy();
    return 0;
}
相关推荐
linux修理工1 小时前
使用 virt-install 命令行快速创建 KVM 虚拟机(以 CentOS 7 为例)
linux·运维·centos
|_⊙1 小时前
进程间通信(System V 标准下的多种通信方式)
linux·运维·服务器
旺仔老馒头.2 小时前
【C++】类和对象(三)
开发语言·c++·程序人生·类和对象
Zklys2 小时前
Cmake的学习笔记step1
c++·笔记·学习
zincsweet2 小时前
C++ 实现进程池:主从架构、管道通信与任务调度
linux·c++
草莓熊Lotso2 小时前
【CMake】静态库的编译、链接与引用全解析
linux·c语言·数据库·c++·软件工程·cmake
原来是猿2 小时前
性能测试(1)
运维·服务器·python·压力测试
少司府2 小时前
C++进阶:继承
c语言·开发语言·c++·继承·组合·虚继承
郝学胜-神的一滴2 小时前
CMake 012:Linux 下动态库与可执行程序的单文件构建
linux·服务器·开发语言·c++·软件构建·cmake