初识Linux · 命名管道

打印 上一主题 下一主题

主题 990|帖子 990|积分 2970

目录
前言:
代码编写

前言:

有了前文匿名管道的基础,我们先容匿名管道的时间就轻松许多了,匿名管道和命名管道的区别主要是在于,匿名管道不需要文件路径,而且匿名管道常用于父子进程这种具有血缘关系的场景,使用命名管道的时间,我们经常用于的情况是两个进程毫无联系,使这两个毫无关系的进程可以进行通讯。
对于匿名管道来说,我们知道文件对象以及文件对象里面的文件对象里面属性集合,操作集合都不会重新创建,对于命名管道来说也是一样的,以是对于内核级别的文件缓冲区也是这个样子的,OS就没有须要创建两个了,究竟浪费空间时间的事OS可不想做。
以上其实算是对于命名管道的原理的部门的简单先容,其实和匿名管道差不多,本文的主要内容其实还是命名管道的代码编写。

代码编写


那么准备工作是先创建三个文件,分别表示客服端,服务端,以及创建管道的文件,创建命名管道之后,让另外两个进程分别打开管道。
那么我们的第一个任务是了解创建命名管道的函数->mkfifo:

直接man mkfifo查询到的是1号手册的mkfifo,那么我们可以使用试试:

创建了对应管道文件之后,我们可以发现几个特征点,它的名字后面带有| 代表管道的意思,而且,它的文件类型部门的字母对应的是p,也就是Pipe部门了,这个其着实文件权限部门先容过。
另有一个故意思的点是在于……留个关子。
那么这是命令行部门创建命名管道,我们是要直接应用于代码层面,以是先容3号手册的函数mkpipe:


对应n个头文件,对于返回值来说的话,如果创建管道成功的话,返回的值是0,堕落了,返回的值就是-1,而且错误码被设置,以是如果返回堕落我们可以打印对应的错误码看看,我们现在不妨试试:
  1. #include <iostream>
  2. #include <sys/types.h>
  3. #include <sys/stat.h>
  4. #include <fcntl.h>
  5. #include <string>
  6. #include <cstdio>
  7. #include <cerrno>
  8. const std::string path = "./mypipe";
  9. int CreateFifo()
  10. {
  11.     int n = mkfifo(path.c_str(),0666);
  12.     if(n < 0)
  13.     {
  14.         perror("mkfifo");
  15.     }
  16.     return n;
  17. }
复制代码
这里使用的头文件相对来说也是比较多的,究竟涉及到了string mkfifo perror,以是C++的头文件有,C++版的C语言头文件也是有的,在namedpipe文件里面实现好了该函数之后,我们转到server.cc文件里面进行调用,其着实client.cc里面调用都可以,究竟之后不过就是一个进程作为读端,一个进程作为写端,以是恣意调用,这里使用server.cc:
  1. #include "namedPipe.hpp"
  2. int main()
  3. {
  4.     CreateFifo();
  5.     return 0;
  6. }
复制代码
这里需要插一个题外话了,我们经常是需要Make文件的,但是makefile好像只能编译一个文件?以是我们需要对原来的makefile进行简单的修改:
  1. .PHONY:all
  2. all:client server
  3. client:client.cc
  4.         g++ -o $@ $^ -std=c++11
  5. server:server.cc
  6.         g++ -o $@ $^ -std=c++11
  7. .PHONY:clean
  8. clean:
  9.         rm -rf server client
复制代码
由于makefile是从上到下查找的,以是我们形成一种依靠关系,从而实现两种文件的编译即可。
那么现在我们尝试编译一下server.cc文件:

也是成功创建了,那么我们再运行一下试试?

也就成功的报错了,提示说文件已经存在了。
那么创建了管道文件我们总得删除管道吧?
可以使用函数unlink:

直接给对应的文件路径就可以了。
但是问题来了,我们现在能保证创建多个管道,但是每次创建管道都要使用函数,每次还要手动的调用,岂非这不是很麻烦吗?我们使用的语言岂非不是面向对象的C++语言吗?以是我们不妨封装一个对象:
  1. class namepipe
  2. {
  3. public:
  4.     namepipe(const std::string fifo_path)
  5.         :_fifo_path(_fifo_path)
  6.     {}
  7.     ~namepipe()
  8.     {
  9.         int res = unlink(_fifo_path.c_str());
  10.         if(res != 0)
  11.         {
  12.             perror("unlink");
  13.         }
  14.     }
  15. private:
  16.     const std::string _fifo_path;
  17.     int _fd;
  18.     int _id;
  19. };
复制代码
像如许,封装一个对象的长处是,我们不消自己手动的去烧毁管道,由于实例化的对象出了main函数的栈帧自己就调用对应的析构函数了。
那么我们可以这个基础之上,进行一些细节的补充,比如是谁调用的?以是我们可以定义一个宏,表示是谁定义的,然后用宏来初识_id:
  1. #define Creater 1
  2. #define User 2
  3. #define Read O_RDONLY
  4. #define Write O_WRONLY
  5. class namepipe
  6. {
  7. public:
  8.     namepipe(const std::string fifo_path, int who)
  9.         : _fifo_path(_fifo_path), _id(who)
  10.     {
  11.         if (_id == Creater)
  12.         {
  13.             int res = mkfifo(_fifo_path.c_str(), 0666);
  14.             if (res != 0)
  15.             {
  16.                 perror("mkfifo");
  17.             }
  18.             std::cout << "Creater create named pipe" << std::endl;
  19.         }
  20.     }
  21.     ~namepipe()
  22.     {
  23.         if (_id == Creater)
  24.         {
  25.             int res = unlink(_fifo_path.c_str());
  26.             if (res != 0)
  27.             {
  28.                 perror("unlink");
  29.             }
  30.             std::cout << "Creater free named pipe" << std::endl;
  31.         }
  32.     }
  33. private:
  34.     const std::string _fifo_path;
  35.     int _fd;
  36.     int _id;
  37. };
复制代码
我们使用宏分别创建者的长处另有就是,client调用的时间我们就可以直接通过宏判断是谁创建的,如许就可以省略不须要的构造。
但是现在有一个问题就是,我们已经知道谁创建的,知道了对应的路径,但是打开的文件呢?这是非常紧张的,以是我们引入一个变量,_fd,那么可以给一个默认的文件形貌符,比如-1,初始化的时间总得初始化上去吧?
  1.     namepipe(const std::string fifo_path, int who,int fd = DefaultFd)
  2.         : _fifo_path(_fifo_path), _id(who),_fd(DefaultFd)
  3.     {
  4.         if (_id == Creater)
  5.         {
  6.             int res = mkfifo(_fifo_path.c_str(), 0666);
  7.             if (res < 0)
  8.             {
  9.                 perror("mkfifo");
  10.             }
  11.             std::cout << "Creater create named pipe" << std::endl;
  12.         }
  13.     }
复制代码
对于构造函数和析构函数这里已经差不多了,那么剩下的就是文件打开,怎么操作管道的事儿了。
我们先来操作打开管道:
  1.     bool OpenforRead()
  2.     {
  3.         return OpenNamePipe(Read);
  4.     }
  5.     bool OpenforWrite()
  6.     {
  7.         return OpenNamePipe(Write);
  8.     }
复制代码
打开管道我们通过宏的差异传参,就可以保证管道的打开是通过读的方式打开的还是写的方式打开的:
  1. private:
  2.     bool OpenNamePipe(int mode)
  3.     {
  4.         _fd = open(_fifo_path.c_str(), mode);
  5.         if (_fd < 0)
  6.             return false;
  7.         return true;
  8.     }
复制代码

但是究竟涉及到了_fd的修改,以是我们不盼望直接可以调用,那么将它私有是最好的选择,这是打开方式的方法,末了的就是写入读取的方法了:
  1.     int ReadNamePipe(std::string *out)
  2.     {
  3.         char buffer[BaseSize];
  4.         int n = read(_fd, buffer, sizeof(buffer));
  5.         if (n > 0)
  6.         {
  7.             buffer[n] = 0;
  8.             *out = buffer;
  9.         }
  10.         return n;
  11.     }
  12.     int WriteNamePipe(std::string &in)
  13.     {
  14.         return write(_fd,in.c_str(),in.size());
  15.     }
复制代码
此时两个一整个管道相关的操作就完成了。
对于server:
  1. #include "namedPipe.hpp"
  2. int main()
  3. {
  4.     // CreateFifo();
  5.     namepipe fifo(path, Creater);
  6.     if (fifo.OpenforRead())
  7.     {
  8.         std::cout << "server open name pipe done" << std::endl;
  9.         sleep(3);
  10.         while (true)
  11.         {
  12.             std::string message;
  13.             int n = fifo.ReadNamePipe(&message);
  14.             if (n > 0)
  15.             {
  16.                 std::cout << "Client Say> " << message << std::endl;
  17.             }
  18.             else if (n == 0)
  19.             {
  20.                 std::cout << "Client quit, Server Too!" << std::endl;
  21.                 break;
  22.             }
  23.             else
  24.             {
  25.                 std::cout << "fifo.ReadNamedPipe Error" << std::endl;
  26.                 break;
  27.             }
  28.         }
  29.     }
  30.     return 0;
  31. }
复制代码
对于client:
  1. #include "namedPipe.hpp"
  2. int main()
  3. {
  4.     namepipe fifo(path, User);
  5.     if (fifo.OpenforWrite())
  6.     {
  7.         std::cout << "client open named pipe done" << std::endl;
  8.         while (true)
  9.         {
  10.             std::cout << "Please enter> ";
  11.             std::string message;
  12.             std::getline(std::cin, message);
  13.             fifo.WriteNamePipe(message);
  14.         }
  15.     }
  16.     return 0;
  17. }
复制代码
试试结果?

符合预期。
如果我们运行./server的时间,不打开client,会发现./server也不会有后续动作,而且如果我们直接关掉写端,./server端是直接关闭的,这是上文匿名管道的知识点,现实上也是一种进程间同步!!

感谢阅读!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

农妇山泉一亩田

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表