命名管道的功能实现
命名管道时进程间通信的一种,那么原理也就是类似的:先让不同的进程看到同一份(操作系统)资源(“一段内存”)。
匿名管道是通过父子进程的继承关系来满足:父子进程可以看到同一段内存!这段内存会在子进程创建时的拷贝一份,所以并不需要名字,只需要通过pipefd[0] pipefd[1]
来记录其读写端的文件描述符,然后在父子进程中关闭对应的文件描述符,达到单方向通信的需求!
根据匿名管道的底层,两个毫不相干的进程就无法通过匿名管道的方式来进行通信!
那么两个毫不相干的进程如何才能看的同一片内存,才能共享一个文件缓冲区呢?当然就通过文件的路径(唯一性)
来打开!
当两个进程打开同一个文件时,他们共享该文件的内核缓冲区。为了我们的通信效率,肯定不能把缓冲区的数据刷新到硬盘中。所以这个文件必须是一个特殊的文件,只用于通信的需求!!!
这个文件就是命名管道!!!
匿名管道的创建是通过系统调用:pipe(int pipefd[2])
来建立,同样命名管道的创建也有对应的指令:mkfifo
MKFIFO(1) User Commands MKFIFO(1)
NAME
mkfifo - make FIFOs (named pipes)
SYNOPSIS
mkfifo [OPTION]... NAME...
DESCRIPTION
Create named pipes (FIFOs) with the given NAMEs.
Mandatory arguments to long options are mandatory for short options too.
-m, --mode=MODE
set file permission bits to MODE, not a=rw - umask
-Z set the SELinux security context to default type
--context[=CTX]
like -Z, or if CTX is specified then set the SELinux or SMACK security context to CTX
--help display this help and exit
--version
output version information and exit
我们使用一下来看看:
这个文件类型是p
不同于-
(普通文件)和d
(目录),p
表示管道文件,显然它是有名字的!
我们来尝试通信一下:
此时两个不同的进程就可以进行通信!!! 我们在让两个进程保持一直通信的状态,这样读端可以一直获取数据!
当我们突然关闭右侧读端时,左边的写端就直接退出来了!这是因为当读端退出了,操作系统会自动释放写端进程,操作系统不会做无用功(不会在一个没有读取的管道文件了一直写入)
当然这样的通信也就只能用来演示,我们先要通过命名管道来使我们创建的两个毫不相干的进程完成通信工作,接下来我们就来实现!
首先我们来认识一下创建管道的系统调用:
MKFIFO(3) Linux Programmer's Manual MKFIFO(3)
NAME
mkfifo, mkfifoat - make a FIFO special file (a named pipe)
SYNOPSIS
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
#include <fcntl.h> /* Definition of AT_* constants */
#include <sys/stat.h>
int mkfifoat(int dirfd, const char *pathname, mode_t mode);
Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
mkfifoat():
Since glibc 2.10:
_POSIX_C_SOURCE >= 200809L
Before glibc 2.10:
_ATFILE_SOURCE
int mkfifo(const char *pathname, mode_t mode);
第一个参数const char *pathname
是要建立的管道文件的路径与文件名,第二个参数mode_t mode
表示文件权限
返回值的意义:
ETURN VALUE On success mkfifo() and mkfifoat() return 0. In the case of an error, -1 is returned (in which case, errno is set appropriately). 创建成功返回 0 失败返回 -1!
通过这些我可以先搭建一个基础类,可以创建管道文件!
#pragma once
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstdio>
#include <unistd.h>
//文件名
const std::string path = "./myfifo";
//管道类
class NamedPipe
{
public:
NamedPipe(const std::string fifo_path)
: _fifo_path(fifo_path)
{
int n = mkfifo(_fifo_path.c_str(), 0666);
if(n != 0)
{
perror("mkfifo");
}
}
~NamedPipe()
{
sleep(10);
unlink(_fifo_path.c_str());
}
private:
const std::string _fifo_path;
int _fd;
int _id;
};
这样就可以通过类的实例化对象来建立管道!!!
接下来我们进行打开文件函数的书写:
open
接口,管理管道由操作者来控制。使用者只能使用不能管理管道的创建与关闭
private:
bool OpenNamedPipe(int mode)
{
//sleep(2);
std::cout << "OpenNamedPipe : " ;
_fd = open(_fifo_path.c_str(), mode);
if (_fd < 0)
{
std::cout << "false" << std::endl;
return false;
}
std::cout << "true" << std::endl;
return true;
}
public:
// 打开文件
int OpenForRead()
{
return OpenNamedPipe(Read);
}
int OpenForWrite()
{
return OpenNamedPipe(Write);
}
打开文件之后就是进行读取或者写入,我们在写一下相应的函数:
// 读取文件
int ReadNamedPipe(std::string *out)
{
char buffer[128];
int n = read(_fd, buffer, sizeof(buffer));
buffer[n] = 0;
*out = buffer;
return n;
}
// 写入文件
int WriteNamedPipe(const std::string &in)
{
int n = write(_fd, in.c_str(), in.size());
return n;
}
这样我们的封装就完成了,NamedPipe
具有以下功能:
OpenForRead() / OpenForWrite()
打开文件WriteNamedPipe
/ ReadNamedPipe
模拟客户端和服务器的通信过程:客户端写入数据,服务器读取数据
client.cc
#include"namedPipe.hpp"
int main()
{
NamedPipe fifo(path , user);
if(fifo.OpenForWrite())
{
std::cout << "client open named pipe done" << std::endl;
while(true)
{
std::cout << "Please Enter>" ;
std::string in ;
getline(std::cin , in);
fifo.WriteNamedPipe(in);
}
}
return 0;
}
server.cc
#include"namedPipe.hpp"
int main()
{
NamedPipe fifo(path , greater);
//服务端进行读取
if(fifo.OpenForRead())
{
std::cout << "server open named pipe done" << std::endl;
sleep(3);
while(true)
{
std::string out ;
int n = fifo.ReadNamedPipe(&out);
if(n > 0)
{
std::cout << "client say >" << out << std::endl;
}
else if (n == 0)
{
std::cout << "client quit , server too!" << out << std::endl;
break;
}
else
{
perror("Write");
break;
}
}
}
return 0;
}
来看效果:
这个效果很好玩呢!!!
注意:
SIGPIPE
)总结一下,命名管道的通信原理依然是:让两个不同的进程看到同一份资源(通过文件路径)
匿名管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。如果我们想在不相关的进程之间交换数据,可以使用命名管道(FIFO文件)来做这项工作.