【Linux】命名管道

简介: 【Linux】命名管道

一、命名管道的原理

      在前面的博客中,我们学习了匿名管道,了解到了两个具有血缘关系的进程之间是如何进行通信的?那么在没有血缘关系(毫不相关)的进程之间是如何进行通信的?

      大致思路是一样的,我们还是需要能够进行让两个进程之间可以看到同一份资源,然后在同一份资源中进行读写和通信。如何让两个进程看见同一份文件呢??由于在Linux系统中,Linux的树形结构保证了一个文件只有一个唯一的路径。我们可以根据文件的路径来找到同一份文件。

二、使用指令来看一下命名管道

在man手册中,可以查到指令的命名管道:

mkfifo  XXXX

      我们可以进行通信,复制一下终端,利用两个终端进行通信:在一个终端中利用echo来打印到屏幕中,用另一个终端进行接收显示。

1. // 循环打印到命名管道的命令为:
2. while :; do echo "Hello world" ; sleep 1; done > myfifo
3. 
4. // 接收的命令为:
5. cat < myfifo

在一个过程中有两个细节:

1. 在向命名管道中写入时,管道中的容量大小怎么进行改变?

为什么会有上面的现象呢??

      命名管道和匿名管道一样,都是内存文件,只不过命名管道在磁盘有一个简单的映像,但这个映像的大小永远为0,因为命名管道和匿名管道都不会将通信数据刷新到磁盘当中。

2. 我在接收端将读端进行关闭,为什么进程会直接退出呢?

      在之前的匿名管道的博客中,我们知道:读端直接关闭,写端一直在写,写端进程会被操作系统直接使用13号信号进行关闭,相当于程序出现异常。因为echo是内建命令,是有bash进程进行控制的。当我们终止掉读端进程后,因为写端执行的循环脚本是由命令行解释器bash执行的,所以此时bash就会被操作系统杀掉,我们的云服务器也就退出了

三、命名管道的代码

      现在,我们使用代码来创建一个命名管道,具体代码如下:

3.1 先来介绍一下使用的函数

3.1.1 mkfifo函数

函数的原型:

函数的参数部分:

  • pathname:创建管道文件的文件路径
  • mode:存放创建管道文件的权限

函数的返回值:

  • 如果函数成功返回0,如果失败返回-1

函数的功能:

  • 该函数用于在文件系统中创建一个文件,该文件用于提供FIFO功能,即命名管道。

3.1.2 unlink函数

函数的原型:

函数的参数部分:

  • pathname:创建管道文件的文件路径

函数的返回值:

  • 函数的返回值为 0 表示成功,-1 表示失败,并设置相应的错误码。

函数的功能:

  • 该函数用于删除文件系统中的文件。它通过删除文件系统中文件的链接,从而使文件系统中不再存在该文件的链接。
  • 当所有链接(包括硬链接和符号链接)都被删除之后,文件系统便会回收文件占用的磁盘空间。
  • 需要注意的是,删除文件并不会立即释放文件的磁盘空间,而是在文件的引用计数为零时才会真正回收空间。

3.1.3 open函数

函数的原型:

函数的参数:

  • pathname:创建管道文件的文件路径
  • flags:一些标志位,我们需要认识一些标志位,这些标志位通过按位与进行传参,我们需要通过位图的知识点来将每一个标志位进行分开,分别进行不同函数的操作。open函数的一些标志位的写法和用途:

函数的返回值:

  • 调用成功时返回一个文件描述符fd,调用失败时返回-1,并修改errno

函数的功能:

  • 打开文件

3.1.4 write函数

3.1.5 read函数

3.2 命名管道类的编写

3.2.1 创建命名管道的代码编写

const std::string comepath = "./myfifo"; // 先确定要打开的文件路径
 
int CreateNamePipe(const std::string &path)
{
    int res = mkfifo(path.c_str(), 0666); // 在相应的路径创建管道文件,并设置其权限
    if (res != 0)
    {
        perror("mkfifo");
        std::cerr << "errno:" << errno << std::endl;
    }
    return res;
}

3.2.2 删除管道文件的代码编写

int RemoveNamePipr(const std::string &path)
{
    int res = unlink(path.c_str());
    if (res != 0)
    {
        perror("RemoveNamePipe");
    }
    return res;
}

3.2.3 最后将两个函数进行整合

class NamePipe
{
public:
    // 创建管道
    NamePipe(const std::string &path, int who)
        : fifo_pipe(path), _id(who), _fd(default)
    {
        if (_id == Creater)
        {
            int res = mkfifo(path.c_str(), 0666);
            if (res != 0)
            {
                perror("mkfifo");
            }
            std::cout << "Craeter create name pipe" << std::endl;
        }
    }
 
    // 销毁管道
    ~NamePipe()
    {
        if (_id == Creater)
        {
            int res = unlink(fifo_pipe.c_str());
            if (res != 0)
            {
                perror("RemoveNamePipe");
            }
            std::cout << "Create free name pipe" << std::endl;
        }
        if(_fd != default) close(_fd);
    }
 
private:
    const std::string &fifo_pipe;  // 存放管道文件的路径
    int _id;   // 检查是创建者还是使用者
    int _fd;   // 存放文件描述符
};

3.3 打开管道文件的代码编写

      我们已经知道了文件的路径,这时,我们需要调用open函数来进行文件的打开,并且表示是以读的形式打开,还是以写的形式打开。利用系统调用函数,将管道文件打开。

bool opennamepipe(int mode)
{
    _fd = open(fifo_pipe.c_str(), mode);  // mode传递的是标志位
    if (_fd < 0)
    {
        return false;
    }
    return true;  // _fd是文件描述符
}

3.4 以读的形式或者以写的形式打开文件的代码编写

在打开文件的open函数中有一个标志位,我们可以使用标志位对文件进行不同的操作。

#define Read O_RDONLY  // 标志位
#define Write O_WRONLY
 
// 以读的形式打开
bool openforread()
{
    return opennamepipe(Read);
}
 
// 以写的形式打开
bool operforwrite()
{
    return opennamepipe(Write);
}

3.5 读管道和写管道

// 读管道
int ReadNamePipe(std::string *out)
{
    char buff[Basesize];
    int n = read(_fd, buff, sizeof buff);
    if(n > 0)
    {
       buff[n] = '\0';
       *out = buff;
    }
    return n;
}
 
// 写管道
int WriteNamePipe(const std::string& in)
{
    return write(_fd, in.c_str(), in.size());
}

3.6 编写发送端和接收端的代码编写

读端的代码:

#include "namedpipe.hpp"
 
// server进行读取,管理命名管道的整个生命周期
int main()
{
    // 创建管道
    NamePipe fifo(comepath, Creater);
    // 对于读端而言,如果我们打开文件,但是写端还没来,我会阻塞在open调用中,直到对方打开
    // 进程同步,
    if (fifo.openforread())
    {
        while (true)
        {
            std::string message;
            int n = fifo.ReadNamePipe(&message);
            if (n > 0)
            {
                std::cout << "Client sat:" << message << std::endl;
            }
            else if(n == 0)
            {
                std::cout << "Client quit, server too" << std::endl;
                break;
            }
            else
            {
                std::cout << "fifo.ReadNamePipe error" << std::endl;
                break;
            }
        }
    }
    return 0;
}

写端的代码:

#include "namedpipe.hpp"
 
int main()
{
    NamePipe fifo(comepath, User);
 
    if (fifo.operforwrite())
    {
        while (true)
        {
            std::cout << "Plass Enter:" << std::endl;
            std::string message;
            std::getline(std::cin, message);
            fifo.WriteNamePipe(message);
        }
    }
 
    return 0;
}

有一个细节

3.7 对代码进行进一步的修改

使得代码中管道的释放可以顺序进行。

四、回归概念

      最后,我们在来复盘一下匿名管道和命名管道。让不同的进程看到同一份资源,匿名管道和命名管道的区别是:匿名管道是父子间继承的方式来进行看到同一份管道资源,而命名管道是通过文件的唯一路径来看到同一份管道资源。

相关文章
|
4月前
|
消息中间件 Linux 开发者
Linux进程间通信秘籍:管道、消息队列、信号量,一文让你彻底解锁!
【8月更文挑战第25天】本文概述了Linux系统中常用的五种进程间通信(IPC)模式:管道、消息队列、信号量、共享内存与套接字。通过示例代码展示了每种模式的应用场景。了解这些IPC机制及其特点有助于开发者根据具体需求选择合适的通信方式,促进多进程间的高效协作。
176 3
|
4月前
|
存储 Unix Linux
Linux I/O 重定向与管道
【8月更文挑战第17天】重定向在Linux中改变命令I/O流向,默认有&quot;&gt;&quot;覆盖输出至文件及&quot;&gt;&gt;&quot;追加输出至文件末尾,便于保存结果;使用&quot;&lt;&quot;从文件读取输入而非键盘,高效处理数据。文件描述符如0(stdin)、1(stdout)、2(stderr)标识I/O资源,支持读写操作。管道以&quot;|&quot;连接命令,使前一命令输出成为后一命令输入,如排序用户或找出CPU占用最高的进程,构建复杂数据处理流程。
49 9
|
4月前
|
存储 Linux 数据处理
在Linux中,什么是管道操作,以及如何使用它?
在Linux中,什么是管道操作,以及如何使用它?
|
4月前
|
存储 Unix Linux
Linux I/O 重定向与管道
【8月更文挑战第14天】输出重定向可将命令结果存入文件,如`&gt;`覆盖写入或`&gt;&gt;`追加写入。输入重定向从文件读取数据,如`&lt;`代替键盘输入。这些操作利用文件描述符(如0:stdin, 1:stdout, 2:stderr)管理I/O。管道`|`连接命令,使前一命令输出作为后一命令输入,便于数据处理,如排序用户`sort -t: -k3 -n /etc/passwd | head -3`或查找CPU占用高的进程`ps aux --sort=-%cpu | head -6`。
42 4
|
4月前
|
开发者 API Windows
从怀旧到革新:看WinForms如何在保持向后兼容性的前提下,借助.NET新平台的力量实现自我进化与应用现代化,让经典桌面应用焕发第二春——我们的WinForms应用转型之路深度剖析
【8月更文挑战第31天】在Windows桌面应用开发中,Windows Forms(WinForms)依然是许多开发者的首选。尽管.NET Framework已演进至.NET 5 及更高版本,WinForms 仍作为核心组件保留,支持现有代码库的同时引入新特性。开发者可将项目迁移至.NET Core,享受性能提升和跨平台能力。迁移时需注意API变更,确保应用平稳过渡。通过自定义样式或第三方控件库,还可增强视觉效果。结合.NET新功能,WinForms 应用不仅能延续既有投资,还能焕发新生。 示例代码展示了如何在.NET Core中创建包含按钮和标签的基本窗口,实现简单的用户交互。
72 0
|
4月前
|
消息中间件 Linux
Linux0.11 管道(十一)
Linux0.11 管道(十一)
27 0
|
4月前
|
数据挖掘 Linux 应用服务中间件
在Linux中,如何在Linux中使用管道?
在Linux中,如何在Linux中使用管道?
|
4月前
|
存储 Linux 数据处理
在Linux中,管道(pipe)和重定向(redirection)的是什么?
在Linux中,管道(pipe)和重定向(redirection)的是什么?
|
4月前
|
存储 Unix Linux
在Linux中,什么是管道?它是如何工作的?
在Linux中,什么是管道?它是如何工作的?
|
5月前
|
Linux
linux中 grep过滤查找 及 管道 ”|” 的使用
linux中 grep过滤查找 及 管道 ”|” 的使用