关于命名管道
当了解了匿名管道的通信机制只能用于具有血缘关系的进程之间时,似乎是出于本能的提出疑问–如果两个进程没有任何关系呢?
假如两个进程之间没有血缘关系,彼此进程就没法轻易拥有对方的文件资源,即不能看到同一份共享资源。这时候我们需要除了pipe函数创建管道的另一种方法,可以支持任意两个进程看到同一份共享资源。于是可以考虑使用命名管道。
命名管道是一个特殊的文件。=可以使不相关的进程之间进行通信,创建管道时创建一个名字,以后其它进程就可以通过这个名字来使用这个管道的另一端。这也是为什么我们称这样的管道为命名管道。命名管道是以一个普通的文件形式出现的,包括创建管道、写管道、读管道。值得注意的是,打开普通文件建立内核级缓冲区后,操作系统会及时刷新里面的数据到磁盘文件中,而管道文件(FIFO)的缓冲区则不会。
创建命名管道
使用命令行创建命名管道(FIFO)
使用mkfifo filename
指令创建命名管道,其中filename表示文件名。
命名管道文件又叫FIFO文件,其文件类型为p
,表示是一个管道文件。
在程序中创建
命名管道也可以在程序中使用mkfifo
函数创建。具体使用方式如下:
#include<sys/types.h> #include<sys/stat.h> int mkfifo(const char* pathname,mode_t mode);
其中,参数pathname
表示的是创建管道文件的路径,如果pathname是相对的,那么会再去进程的环境变量中去找默认路径。mode
表示管道文件的权限,即文件最终的权限为mode& ~umask
。如果创建成功返回0,否则-1.
当某个进程要使用管道文件时,像普通文件那样,得先打开文件(open),且要确定以什么方式打开(write or read)。
匿名管道和命名管道的区别
- 从代码层面上来说,匿名管道是通过pipe函数创建并打开。而命名管道由mkfifo函数创建,打开得用open
- 命名管道可以使两个没有关系的进程建立通信,而匿名管道只能用于具有血缘关系的进程之间
- 命名管道本质是磁盘上的一个文件
除了创建方式的不同,命名管道和匿名管道具有相同的意义。
命名管道的打开规则
- 如果当前是以读的方式打开FIFO文件,而此时没有进程以写的方式打开该FIFO文件时,读端进程就会阻塞,直到有进程以写的方式打开该FIFO。
- 如果当前是以写的方式打开FIFO文件,而此时没有进程以读的方式打开该FIFO文件时,写端进程就会堵塞,直到有进程以读的方式打开该FIFO。
用命名管道实现server和client通信
下面通过命名管道来实现server和client的通信。具体步骤如下:
- 将命名管道的操作方法及其属性封装成一个类,方便代码复用
- server端创建管道之后接收数据
- client端使用管道发送数据
- server端回收管道
NamePipe.hpp
用来封装命名管道的操作以及属性
#include <iostream> #include <string> #include <cstdio> #include <sys/stat.h> #include <sys/types.h> #include <fcntl.h> #include <unistd.h> using namespace std; const string comm_path = "./myfifo"; #define Creater 1//身份码,1表示创建管道者,2表示使用者 #define User 2 #define Defaultfd -1 #define Read_Mode O_RDONLY #define Write_Mode O_WRONLY #define BaseSize 4096//默认读管道的数据大小 class NamePipe { private: void CreatFifo() { int res = mkfifo(_fifo_path.c_str(), 0777); if (res != 0) { perror("mkfifo"); } } bool OpenNameFifo(int mode) { _fd = open(_fifo_path.c_str(), mode); if (_fd < 0) return false; return true; } public: NamePipe(const string &path, int who)//构造函数,根据身份码来决定谁创建管道 : _fifo_path(path), _id(who), _fd(Defaultfd) { if (_id == Creater) { CreatFifo();//创建管道 cout << "creat a fifo" << endl; } } //读写操作 bool OpenForWrite() { return OpenNameFifo(Write_Mode); } bool OpenForRead() { return OpenNameFifo(Read_Mode); } int ReadNamePipe(string &out) { // 向管道中读取数据 char buffer[BaseSize]; int n = read(_fd, buffer, sizeof(buffer) - 1); if (n > 0) { buffer[n] = '\0'; out = buffer; } return n; } int WriteNamePipe(string in) { // 像管道中写数据 return write(_fd, in.c_str(), in.size()); } ~NamePipe()//析构,删除管道文件,关闭文件 { if (_id == Creater) { int res = unlink(_fifo_path.c_str()); // 删除管道文件 if (res != 0) { perror("unlink"); } } if (_fd != Defaultfd) { // 关闭文件 close(_fd); } } private: int _fd;//FIFO描述fu const string _fifo_path;//管道路径 int _id;//身份码 };
Server.cpp
服务端,读取管道的数据。
#include "NamePipe.hpp" // Read int main() { NamePipe fifo(comm_path, Creater); // 如果写端没有打开,就会阻塞等待 cout << "Server 在等待Client打开文件....." << endl; sleep(2); cout << fifo.OpenForRead() << endl; if (fifo.OpenForRead()) { cout << "server 已经打开管道,准备通信" << endl; while (true) { string message = ""; int n = fifo.ReadNamePipe(message); // cout<<message<<endl; if (n > 0) { // 读取成功 cout << message << endl; } else if (n == 0) { // 写端已经关闭,直接退出 break; } else { // 读取失败 cout << "fifo.ReadNamePipe error" << endl; break; } } } return 0; }
client.cpp
客户端,发送数据。
#include "NamePipe.hpp" // Write int main() { NamePipe fifo(comm_path, User); // 如果读端没有打开,就会阻塞等待 cout << "Client 在等待Server打开文件....." << endl; if (fifo.OpenForWrite()) { cout << "client 已经打开管道,准备通信" << endl; while (true) { string message = ""; cout << "Please Input: "; getline(cin, message); fifo.WriteNamePipe(message); } } return 0; }