目录
前言:
代码编写
前言:
有了前文匿名管道的基础,我们先容匿名管道的时间就轻松许多了,匿名管道和命名管道的区别主要是在于,匿名管道不需要文件路径,而且匿名管道常用于父子进程这种具有血缘关系的场景,使用命名管道的时间,我们经常用于的情况是两个进程毫无联系,使这两个毫无关系的进程可以进行通讯。
对于匿名管道来说,我们知道文件对象以及文件对象里面的文件对象里面属性集合,操作集合都不会重新创建,对于命名管道来说也是一样的,以是对于内核级别的文件缓冲区也是这个样子的,OS就没有须要创建两个了,究竟浪费空间时间的事OS可不想做。
以上其实算是对于命名管道的原理的部门的简单先容,其实和匿名管道差不多,本文的主要内容其实还是命名管道的代码编写。
代码编写
那么准备工作是先创建三个文件,分别表示客服端,服务端,以及创建管道的文件,创建命名管道之后,让另外两个进程分别打开管道。
那么我们的第一个任务是了解创建命名管道的函数->mkfifo:
直接man mkfifo查询到的是1号手册的mkfifo,那么我们可以使用试试:
创建了对应管道文件之后,我们可以发现几个特征点,它的名字后面带有| 代表管道的意思,而且,它的文件类型部门的字母对应的是p,也就是Pipe部门了,这个其着实文件权限部门先容过。
另有一个故意思的点是在于……留个关子。
那么这是命令行部门创建命名管道,我们是要直接应用于代码层面,以是先容3号手册的函数mkpipe:
对应n个头文件,对于返回值来说的话,如果创建管道成功的话,返回的值是0,堕落了,返回的值就是-1,而且错误码被设置,以是如果返回堕落我们可以打印对应的错误码看看,我们现在不妨试试:
- #include <iostream>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <string>
- #include <cstdio>
- #include <cerrno>
- const std::string path = "./mypipe";
- int CreateFifo()
- {
- int n = mkfifo(path.c_str(),0666);
- if(n < 0)
- {
- perror("mkfifo");
- }
- return n;
- }
复制代码 这里使用的头文件相对来说也是比较多的,究竟涉及到了string mkfifo perror,以是C++的头文件有,C++版的C语言头文件也是有的,在namedpipe文件里面实现好了该函数之后,我们转到server.cc文件里面进行调用,其着实client.cc里面调用都可以,究竟之后不过就是一个进程作为读端,一个进程作为写端,以是恣意调用,这里使用server.cc:
- #include "namedPipe.hpp"
- int main()
- {
- CreateFifo();
- return 0;
- }
复制代码 这里需要插一个题外话了,我们经常是需要Make文件的,但是makefile好像只能编译一个文件?以是我们需要对原来的makefile进行简单的修改:
- .PHONY:all
- all:client server
- client:client.cc
- g++ -o $@ $^ -std=c++11
- server:server.cc
- g++ -o $@ $^ -std=c++11
- .PHONY:clean
- clean:
- rm -rf server client
复制代码 由于makefile是从上到下查找的,以是我们形成一种依靠关系,从而实现两种文件的编译即可。
那么现在我们尝试编译一下server.cc文件:
也是成功创建了,那么我们再运行一下试试?
也就成功的报错了,提示说文件已经存在了。
那么创建了管道文件我们总得删除管道吧?
可以使用函数unlink:
直接给对应的文件路径就可以了。
但是问题来了,我们现在能保证创建多个管道,但是每次创建管道都要使用函数,每次还要手动的调用,岂非这不是很麻烦吗?我们使用的语言岂非不是面向对象的C++语言吗?以是我们不妨封装一个对象:
- class namepipe
- {
- public:
- namepipe(const std::string fifo_path)
- :_fifo_path(_fifo_path)
- {}
- ~namepipe()
- {
- int res = unlink(_fifo_path.c_str());
- if(res != 0)
- {
- perror("unlink");
- }
- }
- private:
- const std::string _fifo_path;
- int _fd;
- int _id;
- };
复制代码 像如许,封装一个对象的长处是,我们不消自己手动的去烧毁管道,由于实例化的对象出了main函数的栈帧自己就调用对应的析构函数了。
那么我们可以这个基础之上,进行一些细节的补充,比如是谁调用的?以是我们可以定义一个宏,表示是谁定义的,然后用宏来初识_id:
- #define Creater 1
- #define User 2
- #define Read O_RDONLY
- #define Write O_WRONLY
- class namepipe
- {
- public:
- namepipe(const std::string fifo_path, int who)
- : _fifo_path(_fifo_path), _id(who)
- {
- if (_id == Creater)
- {
- int res = mkfifo(_fifo_path.c_str(), 0666);
- if (res != 0)
- {
- perror("mkfifo");
- }
- std::cout << "Creater create named pipe" << std::endl;
- }
- }
- ~namepipe()
- {
- if (_id == Creater)
- {
- int res = unlink(_fifo_path.c_str());
- if (res != 0)
- {
- perror("unlink");
- }
- std::cout << "Creater free named pipe" << std::endl;
- }
- }
- private:
- const std::string _fifo_path;
- int _fd;
- int _id;
- };
复制代码 我们使用宏分别创建者的长处另有就是,client调用的时间我们就可以直接通过宏判断是谁创建的,如许就可以省略不须要的构造。
但是现在有一个问题就是,我们已经知道谁创建的,知道了对应的路径,但是打开的文件呢?这是非常紧张的,以是我们引入一个变量,_fd,那么可以给一个默认的文件形貌符,比如-1,初始化的时间总得初始化上去吧?
- namepipe(const std::string fifo_path, int who,int fd = DefaultFd)
- : _fifo_path(_fifo_path), _id(who),_fd(DefaultFd)
- {
- if (_id == Creater)
- {
- int res = mkfifo(_fifo_path.c_str(), 0666);
- if (res < 0)
- {
- perror("mkfifo");
- }
- std::cout << "Creater create named pipe" << std::endl;
- }
- }
复制代码 对于构造函数和析构函数这里已经差不多了,那么剩下的就是文件打开,怎么操作管道的事儿了。
我们先来操作打开管道:
- bool OpenforRead()
- {
- return OpenNamePipe(Read);
- }
- bool OpenforWrite()
- {
- return OpenNamePipe(Write);
- }
复制代码 打开管道我们通过宏的差异传参,就可以保证管道的打开是通过读的方式打开的还是写的方式打开的:
- private:
- bool OpenNamePipe(int mode)
- {
- _fd = open(_fifo_path.c_str(), mode);
- if (_fd < 0)
- return false;
- return true;
- }
复制代码
但是究竟涉及到了_fd的修改,以是我们不盼望直接可以调用,那么将它私有是最好的选择,这是打开方式的方法,末了的就是写入读取的方法了:
- int ReadNamePipe(std::string *out)
- {
- char buffer[BaseSize];
- int n = read(_fd, buffer, sizeof(buffer));
- if (n > 0)
- {
- buffer[n] = 0;
- *out = buffer;
- }
- return n;
- }
- int WriteNamePipe(std::string &in)
- {
- return write(_fd,in.c_str(),in.size());
- }
复制代码 此时两个一整个管道相关的操作就完成了。
对于server:
- #include "namedPipe.hpp"
- int main()
- {
- // CreateFifo();
- namepipe fifo(path, Creater);
- if (fifo.OpenforRead())
- {
- std::cout << "server open name pipe done" << std::endl;
- sleep(3);
- while (true)
- {
- std::string message;
- int n = fifo.ReadNamePipe(&message);
- if (n > 0)
- {
- std::cout << "Client Say> " << message << std::endl;
- }
- else if (n == 0)
- {
- std::cout << "Client quit, Server Too!" << std::endl;
- break;
- }
- else
- {
- std::cout << "fifo.ReadNamedPipe Error" << std::endl;
- break;
- }
- }
- }
- return 0;
- }
复制代码 对于client:
- #include "namedPipe.hpp"
- int main()
- {
- namepipe fifo(path, User);
- if (fifo.OpenforWrite())
- {
- std::cout << "client open named pipe done" << std::endl;
- while (true)
- {
- std::cout << "Please enter> ";
- std::string message;
- std::getline(std::cin, message);
- fifo.WriteNamePipe(message);
- }
- }
- return 0;
- }
复制代码 试试结果?
符合预期。
如果我们运行./server的时间,不打开client,会发现./server也不会有后续动作,而且如果我们直接关掉写端,./server端是直接关闭的,这是上文匿名管道的知识点,现实上也是一种进程间同步!!
感谢阅读!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |