命名管道实现本地通信
目录
命名管道实现通信
命名管道通信头文件
创建命名管道mkfifo
删除命名管道unlink
构造函数
以读方式打开命名管道
以写方式打开命名管道
读操作
写操作
析构函数
服务端
客户端
运行结果
命名管道实现通信
命名管道通信头文件
#pragma#include <iostream> #include <cstdio> #include <cerrno> #include <string> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <fcntl.h>#define DefaultFd -1 #define Creater 1 #define User 2 #define Read O_RDONLY #define Write O_WRONLY #define BaseSize 4096const std::string comm_path = "./myfifo";class NamePiped { private:bool OpenNamePipe(int mode){_fd = open(_fifo_path.c_str(), mode);if (_fd < 0)return false;return true;}public:NamePiped(const std::string &path, int who): _fifo_path(path), _id(who), _fd(DefaultFd){if (_id == Creater){int res = mkfifo(path.c_str(), 0666);if (res != 0){perror("mkfifo");}std::cout << "creater creat named pipe" << std::endl;}}bool OpenForRead(){return OpenNamePipe(Read);}bool OpenForWrite(){return OpenNamePipe(Write);}int ReadNamedPipe(std::string* out){char buffer[BaseSize];int n = read(_fd, buffer,sizeof(buffer));if(n > 0){buffer[n] = 0;*out = buffer;}return n;}int WriteNamedPipe(const std::string &in){return write(_fd, in.c_str(),in.size());}~NamePiped(){if (_id == Creater){int res = unlink(_fifo_path.c_str());if (res != 0){perror("unlink");}std::cout << "creater free named pipe" << std::endl;}if (_fd != DefaultFd)close(_fd);}private:const std::string _fifo_path;int _id;int _fd; };
我们要想实现用命名管道并将它封装成类来进行通信我们就要想好我们需要哪些基本成员,首先既然要创建命名管道我们是不是得先了解命名管道创建的调用接口?
创建命名管道mkfifo
查询我们的3号手册我们会发现它是我们C语言库帮我们封装好的一个接口,它的两个参数一个是你的路径名,另一个是你这个命名管道文件的权限。
创建成功,返回值返回0,否则返回-1错误码被设置。
由这个函数我们就知道我们得有命名管道的文件路径名,还有它的权限设置,权限设置我们可以直接传,但我们的路径就可以作为我们的成员变量。同样的创建命名管道我们希望只有创建者身份的进程能够创建它,所以我们需要需要一个id号来当身份证。到这里我们的3个成员变量的前两个就已经说明白了。
删除命名管道unlink
unlink就是C语言为我们提供的Linux上的系统调用,它的参数只有一个我们的文件路径名。这个函数的功能是将我们提供的文件名进行删除,文件内容是否进行删除是有条件的,即使文件名不再存在,只要还有进程通过打开的文件描述符访问该文件,文件就会继续保留在磁盘上。这是因为操作系统需要确保正在使用文件的进程能够正常访问其内容。只有当所有指向该文件的文件描述符都被关闭,且没有其他硬链接指向该文件时,文件的内容才会被真正删除,其占用的磁盘空间才会变得可用。
它的返回值也是一样的,成功就返回0,失败返回-1,错误码被设置。
命名管道的创建和删除我们了解了之后,我们就要来想它接下来的逻辑了。我们有两个进程,一个进程的身份是服务端(也就是我们的创建者),这个进程只会进行读操作。另一个进程的身份是客户端(我们的使用者),它只会进行我们的写操作。而我在之前的文章里有说过,命名管道也好匿名管道也好,都是文件的一种特殊形式,我们对文件怎么读,我们就对命名管道怎么读。所以既然一个只写,一个只读,那么我们就还需要一个文件描述符fd来进行我们的读写操作。至此3个成员变量的作用我们就都说明清楚了。
前面知识铺垫完毕,我再来跟大家将这个类的实现讲清楚。
构造函数
NamePiped(const std::string &path, int who): _fifo_path(path), _id(who), _fd(DefaultFd){if (_id == Creater){int res = mkfifo(path.c_str(), 0666);if (res != 0){perror("mkfifo");}std::cout << "creater creat named pipe" << std::endl;}}
我们的构造函数首先将我们的3个成员变量赋值,分别是文件名,身份,自身持有的文件描述符(我们暂设-1,因为还没打开文件),函数内部我们只对创建者进行创建命名管道的动作。
以读方式打开命名管道
创建好之后我们就要打开它了,打开它有两种方式,我们先来读打开
bool OpenNamePipe(int mode){_fd = open(_fifo_path.c_str(), mode);if (_fd < 0)return false;return true;}bool OpenForRead(){return OpenNamePipe(Read);}
从这里我们就能看到读打开跟打开普通文件没有任何区别。打开成功我们就可以讲文件描述符设置一下了。
以写方式打开命名管道
读是如此,写也一样。
bool OpenForWrite(){return OpenNamePipe(Write);}
我们可以看到两个复用的都是同一段的代码。
读操作
int ReadNamedPipe(std::string* out){char buffer[BaseSize];int n = read(_fd, buffer,sizeof(buffer));if(n > 0){buffer[n] = 0;*out = buffer;}return n;}
读取也是一样,文件我们怎么读,这里我们就怎么读,没有啥干货这里就不浪费时间了。
写操作
int WriteNamedPipe(const std::string &in){return write(_fd, in.c_str(),in.size());}
写操作更是如此。我们不难发现,在解决了命名管道这个问题之后,其它的操作就不难了。
析构函数
~NamePiped(){if (_id == Creater){int res = unlink(_fifo_path.c_str());if (res != 0){perror("mkfifo");}std::cout << "creater free named pipe" << std::endl;}if (_fd != DefaultFd)close(_fd);}
这里跟我们的构造函数相呼应,由谁创建就理应由谁来删除,最后我们再把各自的文件描述符一关,这样命名管道的空间就释放掉了。
服务端
#include "namedPipe.hpp" // read:管理命名管道的整个生命周期 int main() {NamePiped fifo(comm_path, Creater);//对于读端而言,如果我们打开文件,但是写还没来,我会阻塞在open调用中,知道对方打开//进程同步if (fifo.OpenForRead()){std::cout << "server open named pipe done" << std::endl;sleep(3);while (true){std::string message;int n = fifo.ReadNamedPipe(&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; }
服务端作为我们命名管道的创建者,它创建管道并以读方式打开,任然后一直读取数据,如果客户端关闭,那么意味着写端也关闭了,我们同样会退出循环。
客户端
#include "namedPipe.hpp" // write int main() {NamePiped fifo(comm_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.WriteNamedPipe(message);}}return 0; }
客户端作为我们的使用者,它会一直写一直写,知道我们自己手动退出。
运行结果