我们前面学习了匿名管道,解决了父子进程(具有血缘关系的进程)之间的通信,那如果两个进程毫无关系呢???
原理
在前面的匿名管道中,可以通过父子继承的方式看到同一个被打开的文件,那么在命名管道中,怎么保证两个毫不相关的进程,打开了同一个文件呢???每一个文件,都有文件路径,文件路径具有唯一性。
由于上面一个进程只是想完成进程间通信,没必要刷新到磁盘上,所以,就要求上图中磁盘上的hello.txt是一种特殊文件,这个文件被打开之后,在内存中一旦写入缓冲区,不会刷新到磁盘上,而是让两者在内存中完成内存级别的通信就可以了,这种通过路径表示通信文件唯一性的,叫做命名管道。所谓命名,就是文件有名字,文件会有路径,有路径必定有名字。 为什么又叫管道呢?因为它依旧是一个内存级的基于文件进行通信的通信方案!
代码
我们来介绍一条命令:mkfifo,(fifo即先进先出,是一个队列),我们在Linux中通过man mkfifo查看:
我们可以使用mkfifo创建命名管道:
myfifo的文件类型是p,我们尝试使用echo往myfifo写一些内容,发现卡在了那里,在另一个终端cat查看,可以看到刚才echo的内容:
echo启动后是一个进程,cat启动后也是一个进程,这就相当于实现了一次通信,
我们发现,在往命名管道写入内容后,myfifo的大小仍然是0,也就是并没有写入磁盘当中,但是两个进程间依然可以通信。
其实,我们也可以使用mkfifo接口创建命名管道,然后一个进程写,一个进程读,使用man 3 mkfifo查看其使用方法:
在创建出命名管道后,我们还需要能够删除管道文件,unlink指令可以删除命名管道,
unlink的参数是文件路径,通过路径删除指定路径下的文件,因此,我们在namedPipe.hpp文件中可以写两个函数,一个用于创建管道,一个用于删除管道:
#pragma once
#include<iostream>
#include<string>
#include<cstdio>
#include<cerrno>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
const std::string comm_path = "./myfifo";
int CreateNamedPipe(const std::string& path)
{
int res = mkfifo(path.c_str(), 0666);
if(res < 0)
{
perror("mkfifo");
}
return res;
}
int RemoveNamedPipe(const std::string& path)
{
int res = unlink(path.c_str());
if(res < 0)
{
perror("mkfifo");
}
return res;
}
一般来说,我们建议由一个进程来创建和删除命名管道,即由一个进程来管理管道,将来另一个进程想和这个进程通信就不用再创建管道,而只需打开管道文件就可以,也就是一个以读方式打开,一个以写方式打开,就可以完成通信了!
为了让进程更好地管理命名管道,我们为命名管道创建一个类NamedPipe():
class NamedPipe
{
private:
bool OpenNamedPipe(int mode)
{
_fd = open(_fifo_path.c_str(), mode);
if (_fd < 0)
return false;
return true;
}
public:
NamedPipe(const std::string &path, int who)
: _fifo_path(path), _id(who), _fd(DefaultFd)
{
if (who == Creater)
{
int res = mkfifo(path.c_str(), 0666);
if (res < 0)
{
perror("mkfifo");
}
std::cout << "creater create named pipe" << std::endl;
}
}
bool OpenForRead()
{
return OpenNamedPipe(Read);
}
bool OpenForWrite()
{
return OpenNamedPipe(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());
}
~NamedPipe()
{
if (_id == Creater)
{
//sleep(5);
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;
};
我们来看一种现象,先只运行读端,如果打开文件,但是写还没来,会阻塞在open调用中,直到对方打开,是一种进程同步。如果读端和写端都打开了,此时关闭读端,如果写端再写入,那么写端就会关闭,因为受到了SIGPIPE信号。综合来看,这种命名管道也遵循我们之前说的管道的所有特性!
回归概念
我们知道,进程间通信的前提是让不同的进程,看到同一份资源,对于命名管道来说,通过文件路径看到了同一份资源!
完整代码附上:
namedPipe.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstdio>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define Creater 1
#define User 2
#define BaseSize 4096
#define DefaultFd -1
#define Read O_RDONLY
#define Write O_WRONLY
const std::string comm_path = "./myfifo";
class NamedPipe
{
private:
bool OpenNamedPipe(int mode)
{
_fd = open(_fifo_path.c_str(), mode);
if (_fd < 0)
return false;
return true;
}
public:
NamedPipe(const std::string &path, int who)
: _fifo_path(path), _id(who), _fd(DefaultFd)
{
if (who == Creater)
{
int res = mkfifo(path.c_str(), 0666);
if (res < 0)
{
perror("mkfifo");
}
std::cout << "creater create named pipe" << std::endl;
}
}
bool OpenForRead()
{
return OpenNamedPipe(Read);
}
bool OpenForWrite()
{
return OpenNamedPipe(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());
}
~NamedPipe()
{
if (_id == Creater)
{
//sleep(5);
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;
};
server.cc
#include "namedPipe.hpp"
// server read:管理命名管道的整个生命周期
int main()
{
NamedPipe fifo(comm_path, Creater);
if (fifo.OpenForRead())
{
//对于读端而言,如果我们打开文件,但是写还没来,我会阻塞在open调用中,直到对方打开
//进程同步
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;
}
client.cc
#include "namedPipe.hpp"
// write
int main()
{
NamedPipe 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;
}
版权归原作者 核动力C++选手 所有, 如有侵权,请联系我们删除。