跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++

Linux 命名管道(FIFO)通信:原理与跨进程实战

Linux 命名管道(FIFO)通过文件系统标识实现无血缘关系进程间的字节流通信。文章解析其半双工、阻塞特性及创建方式,对比匿名管道差异,并提供基于 C++ 的完整服务端与客户端代码示例。涵盖编译运行步骤、常见阻塞处理及文件残留等坑点总结,帮助开发者掌握基础 IPC 机制并避免典型错误。

奇形怪状发布于 2026/3/24更新于 2026/10/24109 浏览
Linux 命名管道(FIFO)通信:原理与跨进程实战

在 Linux 进程间通信(IPC)中,管道是最基础的方式之一。但平时接触的'匿名管道'有个致命缺陷——只能用于有血缘关系的进程(如父子进程),无法实现无关联进程间的通信。

而**命名管道(简称 FIFO)**正是为解决这个问题而生。它给管道赋予了一个'文件名',通过文件系统作为媒介,让任意两个进程(无论是否有血缘关系)都能通过这个'命名'实现数据交互。

一、先搞懂:命名管道(FIFO)是什么?

1. 命名管道的本质

命名管道和匿名管道的核心通信逻辑完全一致——都是基于'字节流'的半双工通信(同一时刻只能单向传输数据),底层都是内核中的缓冲区。

两者的核心区别在于:命名管道有一个可见的文件系统入口(一个以.fifo 为后缀的文件),这个文件只是一个'标识',不存储实际数据(数据仍在内存缓冲区中);而匿名管道没有文件入口,只能通过父子进程继承文件描述符来通信。

简单来说:匿名管道是'隐式的、血缘专属',命名管道是'显式的、通用的',只要知道 FIFO 文件的路径,任意进程都能与之通信。

2. 命名管道的核心特点
  • 跨进程通信:无血缘关系的进程(如两个独立的程序),只要知道 FIFO 文件路径,就能通过它通信;
  • 半双工通信:数据只能单向流动,若要实现双向通信,需要创建两个 FIFO(一个用于 A→B,一个用于 B→A);
  • 基于文件标识:FIFO 文件存在于文件系统中,进程通过打开这个文件,获取读写文件描述符,进而实现通信;
  • 阻塞特性:默认情况下,打开 FIFO 的读端会阻塞,直到有进程打开写端;打开写端也会阻塞,直到有进程打开读端(可通过 O_NONBLOCK 设置为非阻塞);
  • 生命周期:FIFO 文件不会随进程退出而自动删除,需要手动删除(rm 命令或 unlink 函数),否则会一直存在于文件系统中。

文章配图 文章配图

3. 命名管道与匿名管道的对比

为了更清晰区分,做一个简单对比表,避免混淆:

对比维度匿名管道(pipe)命名管道(FIFO)
通信范围有血缘关系的进程(父子、兄弟)任意进程(无血缘关系也可)
标识方式无文件标识,通过文件描述符继承有文件系统入口(FIFO 文件)
创建方式通过 pipe() 系统调用创建通过 mkfifo() 系统调用或 mkfifo 命令创建
生命周期随进程退出而销毁(文件描述符关闭)随 FIFO 文件删除而销毁,进程退出不影响文件
使用场景父子进程间简单通信(如 shell 管道)无关联进程间通信(如两个独立程序交互)

二、命名管道的创建方式

命名管道有两种创建方式:命令行创建和代码创建,本质都是在文件系统中生成一个 FIFO 类型的文件。

2.1 命令行创建(mkfifo 命令)

直接通过**mkfifo**命令创建命名管道,语法简单,适合快速测试:

# 创建名为 myfifo 的命名管道
mkfifo myfifo
# 查看管道文件(类型标识为 p)
ls -l myfifo

我们可以发现,这里创建的管道文件(最前面的标识是 p 开头),就算我们不停地往里面进行写操作,它的文件大小是一直不变的,验证了它不是一个普通文件。

2.2 代码创建(mkfifo 函数)

通过**mkfifo()**系统调用在代码中创建命名管道,需指定管道路径和权限,原型如下:

#include <sys/stat.h>
#include <sys/types.h>
// 参数:pathname-管道路径;mode-权限(如 0644)
int mkfifo(const char *pathname, mode_t mode);

代码示例(创建命名管道)

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#define FIFO_PATH "./myfifo"

int main() {
    // 创建命名管道,权限 0644(所有者读 + 写,其他读)
    int ret = mkfifo(FIFO_PATH, 0644);
    if (ret == -1) {
        // 若管道已存在,errno 为 EEXIST,可忽略该错误
        if (errno != EEXIST) {
            perror("mkfifo error");
            return 1;
        }
        printf("命名管道已存在\n");
    } else {
        printf("命名管道创建成功\n");
    }
    return 0;
}
2.3 命名管道的打开规则

命名管道的打开(open())行为与普通文件不同,核心是'读端与写端的同步'——仅当管道的读端和写端都被打开后,通信才能正常进行,具体规则如下:

  • O_RDONLY:以只读方式打开(读端),默认阻塞,直到有进程以 O_WRONLY 方式打开;
  • O_WRONLY:以只写方式打开(写端),默认阻塞,直到有进程以 O_RDONLY 方式打开;
  • O_RDWR:以读写方式打开,不阻塞,直接打开(同时具备读和写权限,可实现单向通信的'自我循环')

注意:实际开发中,建议读端以**O_RDONLY打开,写端以O_WRONLY**打开,避免使用 O_RDWR(可能导致通信逻辑混乱)。


三、实操实现:手搓命名管道通信

下面我们通过 C++ 实现一个简单的服务端与客户端通信模型。

  • server.cpp:作为服务端,监听管道,接收客户端消息并打印;
  • client.cpp:作为客户端,向管道发送消息,实现交互。
3.1 前置准备(Makefile && comm.h)

Makefile:

all: client server

client: client.cpp
	g++ -o $@ $^ -std=c++11

server: server.cpp
	g++ -o $@ $^ -std=c++11

.PHONY: clean
clean:
	rm -f client server

comm.h:

#pragma once
#include <string>
const std::string fifoname="fifo";
3.2 服务端程序(server.cpp)
#include <iostream>
#include <cstdio>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include "comm.h"

int main() {
    // 1. 创建管道文件
    umask(0);
    int n = mkfifo(fifoname.c_str(), 0666);
    if (n < 0) {
        perror("mkfifo");
        return 1;
    }

    // 2. 打开管道文件
    int rfd = open(fifoname.c_str(), O_RDONLY);
    if (rfd < 0) {
        perror("open");
        return 2;
    }

    char inbuffer[1024];
    // 3. 进行通信
    while (true) {
        ssize_t n = read(rfd, inbuffer, sizeof(inbuffer) - 1);
        if (n > 0) {
            inbuffer[n] = 0;
            std::cout << "client say# " << inbuffer << std::endl;
        } else if (n == 0) {
            // 写端关闭了
            break;
        } else {
            perror("read");
            break;
        }
    }

    // 4. 关闭
    close(rfd);
    // 5. 删除管道文件
    unlink(fifoname.c_str());
    return 0;
}
3.3 客户端程序(client.cpp)
#include <iostream>
#include <string>
#include <cstdio>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "comm.h"

int main() {
    int wfd = open(fifoname.c_str(), O_WRONLY);
    if (wfd < 0) {
        perror("open");
        return 1;
    }

    std::string outstring;
    while (true) {
        std::cout << "Please Enter@ ";
        std::cin >> outstring;
        write(wfd, outstring.c_str(), outstring.size());
        // 要不要写'\0'? 不需要!
    }
    close(wfd);
    return 0;
}
3.4 编译运行

执行 make 编译后,先在终端启动服务端,再启动客户端即可看到交互效果。


四、命名管道的实际应用场景

命名管道因其'跨进程、简单易用'的特点,在 Linux 开发中应用广泛,常见场景包括:

  • 后台服务与前台程序通信:如后台守护进程(如日志服务),通过 FIFO 接收前台程序发送的日志指令,实现日志的动态控制;
  • 多个独立程序的数据交互:如两个不同的服务程序(如数据库服务和缓存服务),通过 FIFO 传递数据,无需复杂的 IPC 机制;
  • shell 脚本间通信:在 shell 脚本中,通过 mkfifo 命令创建 FIFO,实现两个脚本之间的数据传输(比匿名管道更灵活);
  • 简单的客户端/服务器模型:基于 FIFO 实现简单的 C/S 架构,客户端向 FIFO 写入请求,服务器从 FIFO 读取请求并处理,返回结果。

五、常见坑点总结(必看)

  • 忘记处理阻塞:打开 FIFO 时未设置 O_NONBLOCK,导致进程一直阻塞,误以为程序卡死;
  • 忽略 SIGPIPE 信号:读端关闭后,写端继续写入,导致进程崩溃;
  • FIFO 文件残留:未用 unlink() 删除 FIFO,下次创建时报错;
  • 路径不一致:读端和写端使用的 FIFO 路径不同,导致无法通信;
  • 权限问题:mkfifo() 设置的权限过低(如 0600),导致其他进程无法打开 FIFO,建议设置为 0666;
  • 数据错乱:未处理字节流无边界问题,读端读取的数据不完整或拼接错误。

六、总结

命名管道(FIFO)是 Linux 跨进程通信的基础方式,核心是'通过文件系统标识,实现任意进程间的字节流通信'。它继承了匿名管道的简单性,又解决了匿名管道'只能用于血缘进程'的缺陷,是入门 IPC 开发的必学知识点。

本文通过'原理→API→实操→避坑'的流程,手把手教你实现命名管道的单方向和双向通信,代码可直接复用。重点掌握 FIFO 的阻塞特性、SIGPIPE 信号处理和文件删除问题,就能避免绝大多数踩坑场景。

实际开发中,若需要更高效、更复杂的通信(如消息优先级、双向通信的更优实现),可后续学习消息队列、共享内存等 IPC 机制,但命名管道的核心思想(字节流、文件标识),是理解所有 IPC 机制的基础。

目录

  1. 一、先搞懂:命名管道(FIFO)是什么?
  2. 1. 命名管道的本质
  3. 2. 命名管道的核心特点
  4. 3. 命名管道与匿名管道的对比
  5. 二、命名管道的创建方式
  6. 2.1 命令行创建(mkfifo 命令)
  7. 创建名为 myfifo 的命名管道
  8. 查看管道文件(类型标识为 p)
  9. 2.2 代码创建(mkfifo 函数)
  10. 2.3 命名管道的打开规则
  11. 三、实操实现:手搓命名管道通信
  12. 3.1 前置准备(Makefile && comm.h)
  13. 3.2 服务端程序(server.cpp)
  14. 3.3 客户端程序(client.cpp)
  15. 3.4 编译运行
  16. 四、命名管道的实际应用场景
  17. 五、常见坑点总结(必看)
  18. 六、总结

更多推荐文章

查看全部
  • Spring Boot 集成数据仓库与 ETL 工具实战
  • 命令行大模型交互工具 MCPHost 配置与实战指南
  • Chaterm:开源 AI 智能终端与 SRE 协作工具
  • 腾讯向 GitHub 发函要求下架 4000 多个微信聊天记录相关开源仓库
  • C++ 哈希表封装:模拟实现 unordered_map 和 unordered_set
  • Android 插件化核心:ClassLoader 机制与原理详解
  • Eino ADK 核心解析:为什么 Agent 必须是一层独立抽象
  • Python 开发环境安装与配置指南
  • 机器学习各大模型原理深度剖析
  • GTC 2026 前瞻:Rubin 架构与 AI 工厂化演进
  • ROS 入门实战:Linux 终端命令与基础环境搭建
  • SpringBoot 源码解析:AnnotationConfigServletWebServerApplicationContext 构造流程
  • OpenClaw 集成 MCP 协议构建通用 AI 助手
  • Web 服务架构与 I/O 模型实战指南
  • LeetCode 380 O(1) 时间插入、删除和获取随机元素
  • oicq 快速入门:从零搭建你的第一个 QQ 机器人
  • 清华大学与智谱团队探索 RLHF 的 Scaling Laws
  • C++ 类与对象进阶:初始化列表、静态成员与编译器优化
  • Dify 与 MySQL 基于 MCP 协议的整合实践
  • 劳动者被迫解除劳动合同情形及经济补偿金计算详解

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online