【Linux系统编程】深入理解命名管道(Named Pipe):从原理到实战的完整指南

目录

前言:

一、命名管道

二、创建命名管道

二、命名管道与匿名管道区别

实例一:用命名管道实现文件拷贝

三、实现两个进程的简单通信

实例二:两进程通信

四、总结


前言:

上一篇博客我们讲解了匿名管道 这种进程间通信方式,不过它有个明显的限制:++只能在有亲缘关系的进程之间通信。++

那么,有没有办法让完全不相关的进程也能像使用管道一样,安全地交换数据呢?

今天我们就来介绍匿名管道的 "亲兄弟"------命名管道(FIFO),它与匿名管道有很多的相似之处,但突破了亲缘关系的限制。

一、命名管道

我们知道:通信的基础就是两个进程要看到同一份资源(内存)。

💦当进程A和进程B同时打开文件a/b/c.xx,操作系统会不会把该文件在内存中加载两次?

当然不会,因为操作系统不会加载重复的资源到内存,没有意义!

回想前面:代码在形成可执行动态链接时,如果多个进程用到一个库,操作系统也不会将动态库加载多份到内存中,而是只加载一份到内存,多个进程通过虚拟内存映射,共享同一份物理内存,彻底避免重复加载,节省内存。

所以,两个或多个进程同时打开一个文件,此时这些进程就会看到同一个文件,即同一份资源,不就可以通信了。


但我们知道该文件肯定和普通文件有一些区别,普通文件当写入数据就会刷写到磁盘,而进程进行通信,并不需要将文件数据刷写到磁盘,所以该文件跟普通文件有区别,但和匿名管道文件相似 。

由于该文件有具体路径和名字,所以叫做命名管道。


二、创建命名管道

**•**命名管道可以从命令行上创建,命令行方法是使用下面这个命令:

bash 复制代码
mkfifo file

**•**删除该管道文件可以使用:

bash 复制代码
unlink file

**•**命名管道也可以从程序里创建,相关函数有:

cpp 复制代码
int mkfifo(const char *filename,mode_t mode);

参数说明:

**pathname:**即你想在哪个路径下创建;

mode: 指创建完管道后的权限,一般 mode = 0666即可。


对命名管道的操作与普通文件完全相同,想要写就以写方式打开,想要读就以读方式打开;

cpp 复制代码
// 从管道读
int fd = open("fifo", O_RDONLY);
read(fd, buffer, strlen(buffer));

// 向管道写
int fd = open("fifo", O_CREAT | O_WRONLY);
write(fd, buffer, strlen(buffer));

二、命名管道与匿名管道区别

↗️ 创建方式:

• 匿名管道由pipe函数创建并打开。

• 命名管道由mkfifo函数创建,打开用open。

• FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些 工作完成之后,它们具有相同的语义。

↗️ 命名管道打开规则:

1、默认阻塞模式(最常用)

  • 只打开读端(O_RDONLY)--> 会阻塞,直到有进程打开写端。
  • 只打开写端(O_WRONLY)--> 会阻塞,直到有进程打开读端。

一句话总结:默认情况下,读、写两端必须都有人打开,open () 才会返回,否则一直等!

2、非阻塞模式 :打开时加 O_NONBLOCK

  • 只读打开(O_RDONLY | O_NONBLOCK)--> 立即成功返回,不等写端。
  • 只写打开(O_WRONLY | O_NONBLOCK)--> 立即失败返回 - 1(因为没人读)。

3、两端都打开,管道就进入通信状态

读和写都打开,开始正常工作,此时读写行为与匿名管道往前相同:

  • 写慢:读阻塞;
  • 读慢:写满阻塞;
  • 写关:读到文件末尾,返回0;
  • 读关:写进程收到信号,被操作系统杀掉。

实例一:用命名管道实现文件拷贝

cpp 复制代码
#pragma once

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string>
#include <unistd.h>

#define PATH "."        // 表示当前路径
#define FIFONAME "fifo" // 命名管道的名字

#define ERR_EXIT(m)        \
    do                     \
    {                      \
        perror(m);         \
        exit(EXIT_FAILURE);\
    }while (0)    
 

读取文件abc内容,写入命名管道

cpp 复制代码
int main()
{
    mkfifo("tp", 0644); // 创建管道
    
    // 打开想要拷贝的文件(读)
    int infd;
    infd = open("abc", O_RDONLY);
    if (infd == -1)
        ERR_EXIT("open");
    
    // 以写方式打开管道
    int outfd;
    outfd = open("tp", O_WRONLY);
    if (outfd == -1)
        ERR_EXIT("open");
    char buf[1024];
    
    // 从文件读并写入管道
    int n;
    while ((n = read(infd, buf, 1024)) > 0)
    {
        write(outfd, buf, n);
    }
    close(infd);
    close(outfd);
    return 0;
}

读取管道,写入目标文件 abc.bak:

cpp 复制代码
int main()
{
    int outfd;
    // 打开目标文件
    outfd = open("abc.bak", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (outfd == -1)
        ERR_EXIT("open");
    
    // 以读方式打开管道
    int infd;
    infd = open("tp", O_RDONLY);
    if (outfd == -1)
        ERR_EXIT("open");
    char buf[1024];
    
    // 从管道读并写入文件
    int n;
    while ((n = read(infd, buf, 1024)) > 0)
    {
        write(outfd, buf, n);
    }
    close(infd);
    close(outfd);
    unlink("tp");
    return 0;
}

效果演示:


三、实现两个进程的简单通信

实例二:两进程通信

cpp 复制代码
// ------------------头文件---------------------
#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string>
#include <unistd.h>
#include <cstdio>

#define PATH "."        // 表示当前路径
#define FIFONAME "fifo" // 命名管道的名字

#define ERR_EXIT(m)        \
    do                     \
    {                      \
        perror(m);         \
        exit(EXIT_FAILURE);\
    }while (0)   

以读方式打开管道,接收消息:

cpp 复制代码
#include "comm.h"
int main()
{
    // 创建命名管道
    umask(0);
    int n = mkfifo(FIFONAME, 0666);
    if (n == 0)
        std::cout << "mkfifo success!" << std::endl;
    else if (n < 0)
        ERR_EXIT("mkfifo");

    // 打开文件进行读
    while (true)
    {
        int fd = open(FIFONAME, O_RDONLY);
        if (fd < 0)
        {
            ERR_EXIT("open");
        }
        else
        {
            char buffer[1024];
            // 从管道读
            ssize_t number = read(fd, buffer, sizeof(buffer) - 1);
            if (number > 0)
            {
                buffer[number] = 0; // 手动放置'\0'
                std::cout << "client say: " << buffer << std::endl;
            }
            else if (number == 0)
            {
                std::cout << "client quit!" << std::endl;
                break;
            }
            else
                ERR_EXIT("read");
        }
        close(fd);
    }
    // 删除管道
    n = unlink("./fifo");
    if (n == 0)
        std::cout << "remove fifo success!" << std::endl;
    else
        std::cout << "remove fifo failed!" << std::endl;
    return 0;
}

以写方式打开管道,发送消息:

cpp 复制代码
#include "comm.h"
int main()
{
    int fd = open(FIFONAME, O_WRONLY);
    if (fd < 0)
    {
        ERR_EXIT("open");
    }
    else
    {
        std::string message;
        int cnt = 1;
        while (true)
        {
            std::cout << "Please Enter: ";
            getline(std::cin, message);
            message += (", message number:" + std::to_string(cnt++) + ",[" + std::to_string(getpid()) + "]");
            // 写入管道,发送
            write(fd, message.c_str(), message.size()); 
        }
    }
    close(fd);

    return 0;
}

效果演示:

四、总结

通过本文的学习,我们深入理解了命名管道(Named Pipe)这一重要的进程间通信机制。从基础概念到实际应用,从与匿名管道的对比到跨进程通信的实现,命名管道展现了其在Linux/Unix系统中不可替代的价值。

如果你在学习过程中遇到问题,欢迎在评论区留言交流。觉得文章有帮助的话,别忘了点赞收藏,转发给更多需要的朋友!!!

相关推荐
淼淼爱喝水2 小时前
Ansible Playbook 入门实战:自动化创建 Linux 用户
linux·运维·服务器·网络·ansible
努力的搬砖人.2 小时前
CentOS 7 系统(内核版本 3.10.0-229.el7.x86_64)无法使用 yum 更新
linux·运维·centos
代码中介商2 小时前
Linux vi/vim 配置与程序调试完全指南
linux·运维·vim·gbd
张火火isgudi2 小时前
OpenWrt 部署 EasyTier 进行异地组网
linux·运维·网络
HZ·湘怡2 小时前
任意位置 单链表 回归
c语言·链表
佩洛君2 小时前
Ubuntu22.04系统apt换国内源
linux·运维·ubuntu
凤年徐2 小时前
Vim编辑器使用详解:多模式、常用命令与配置技巧
linux·编辑器·vim
工作log2 小时前
从 Ubuntu 22.04 到 ROS 2 Humble 完整环境搭建与 Java 控制指南
java·linux·ubuntu
Peregrine92 小时前
数据结构 - > 双链表
c语言·数据结构·算法