• 【Linux】命名管道


    一、命名管道的原理

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

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

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

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

    mkfifo  XXXX

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

    1. // 循环打印到命名管道的命令为:
    2. while :; do echo "Hello world" ; sleep 1; done > myfifo
    3. // 接收的命令为:
    4. 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 创建命名管道的代码编写

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

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

    1. int RemoveNamePipr(const std::string &path)
    2. {
    3. int res = unlink(path.c_str());
    4. if (res != 0)
    5. {
    6. perror("RemoveNamePipe");
    7. }
    8. return res;
    9. }

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

    1. class NamePipe
    2. {
    3. public:
    4. // 创建管道
    5. NamePipe(const std::string &path, int who)
    6. : fifo_pipe(path), _id(who), _fd(default)
    7. {
    8. if (_id == Creater)
    9. {
    10. int res = mkfifo(path.c_str(), 0666);
    11. if (res != 0)
    12. {
    13. perror("mkfifo");
    14. }
    15. std::cout << "Craeter create name pipe" << std::endl;
    16. }
    17. }
    18. // 销毁管道
    19. ~NamePipe()
    20. {
    21. if (_id == Creater)
    22. {
    23. int res = unlink(fifo_pipe.c_str());
    24. if (res != 0)
    25. {
    26. perror("RemoveNamePipe");
    27. }
    28. std::cout << "Create free name pipe" << std::endl;
    29. }
    30. if(_fd != default) close(_fd);
    31. }
    32. private:
    33. const std::string &fifo_pipe; // 存放管道文件的路径
    34. int _id; // 检查是创建者还是使用者
    35. int _fd; // 存放文件描述符
    36. };

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

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

    1. bool opennamepipe(int mode)
    2. {
    3. _fd = open(fifo_pipe.c_str(), mode); // mode传递的是标志位
    4. if (_fd < 0)
    5. {
    6. return false;
    7. }
    8. return true; // _fd是文件描述符
    9. }

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

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

    1. #define Read O_RDONLY // 标志位
    2. #define Write O_WRONLY
    3. // 以读的形式打开
    4. bool openforread()
    5. {
    6. return opennamepipe(Read);
    7. }
    8. // 以写的形式打开
    9. bool operforwrite()
    10. {
    11. return opennamepipe(Write);
    12. }

    3.5 读管道和写管道

    1. // 读管道
    2. int ReadNamePipe(std::string *out)
    3. {
    4. char buff[Basesize];
    5. int n = read(_fd, buff, sizeof buff);
    6. if(n > 0)
    7. {
    8. buff[n] = '\0';
    9. *out = buff;
    10. }
    11. return n;
    12. }
    13. // 写管道
    14. int WriteNamePipe(const std::string& in)
    15. {
    16. return write(_fd, in.c_str(), in.size());
    17. }

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

    读端的代码:

    1. #include "namedpipe.hpp"
    2. // server进行读取,管理命名管道的整个生命周期
    3. int main()
    4. {
    5. // 创建管道
    6. NamePipe fifo(comepath, Creater);
    7. // 对于读端而言,如果我们打开文件,但是写端还没来,我会阻塞在open调用中,直到对方打开
    8. // 进程同步,
    9. if (fifo.openforread())
    10. {
    11. while (true)
    12. {
    13. std::string message;
    14. int n = fifo.ReadNamePipe(&message);
    15. if (n > 0)
    16. {
    17. std::cout << "Client sat:" << message << std::endl;
    18. }
    19. else if(n == 0)
    20. {
    21. std::cout << "Client quit, server too" << std::endl;
    22. break;
    23. }
    24. else
    25. {
    26. std::cout << "fifo.ReadNamePipe error" << std::endl;
    27. break;
    28. }
    29. }
    30. }
    31. return 0;
    32. }

    写端的代码:

    1. #include "namedpipe.hpp"
    2. int main()
    3. {
    4. NamePipe fifo(comepath, User);
    5. if (fifo.operforwrite())
    6. {
    7. while (true)
    8. {
    9. std::cout << "Plass Enter:" << std::endl;
    10. std::string message;
    11. std::getline(std::cin, message);
    12. fifo.WriteNamePipe(message);
    13. }
    14. }
    15. return 0;
    16. }

    有一个细节

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

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

    四、回归概念

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

  • 相关阅读:
    并发编程模型的两个关键问题
    融合正弦余弦和无限折叠迭代混沌映射的蝴蝶优化算法-附代码
    算法leetcode|11. 盛最多水的容器(rust重拳出击)
    【会议征稿】第五届土木工程、环境资源与能源材料国际学术会议(CCESEM 2023)
    1794 - 最长不下降子序列(LIS)
    vue 常用指令
    深入理解计算机系统——第四章 Processor Architecture
    C++中的std::move函数到底是做什么的?
    通信信道:无线信道中衰落的类型和分类
    为什么力扣中std::sort的cmp函数不加static会出错?
  • 原文地址:https://blog.csdn.net/2301_77868664/article/details/139327924