命名管道(用命名管道模拟server和client之间的通信)
目录
- 命名管道
- 创建命名管道
- 使用命令行创建命名管道(FIFO)
- 在程序中创建
- 命名管道的打开规则
- 用命名管道实现server和client通信
命名管道
bash进程并不会给我们写的两个不同的程序创建通信的管道,即使这两个进程看起来好像都是bash的子进程,但是此时再用血缘关系那一套来看就不适用了。通信的本质还是要有一个共同的访问的内存,此时bash并不会帮我们创建。所以此时需要通信就需要自己开辟一个内存空间去进行两个不相关进程的通信。
具体方式是创建一份有名管道文件,文件可以让两个进程进行通信,这个文件就叫命名管道。
命名管道是一个特殊的文件。可以使不相关的进程之间进行通信,创建管道时创建一个名字,以后其它进程就可以通过这个名字来使用这个管道的另一端。这也是为什么我们称这样的管道为命名管道。命名管道是以一个普通的文件形式出现的,包括创建管道、写管道、读管道。值得注意的是,打开普通文件建立内核级缓冲区后,操作系统会及时刷新里面的数据到磁盘文件中,而管道文件(FIFO)的缓冲区则不会。
需要知道的是,命名管道也符合管道的四种情况和五大特性。
创建命名管道
使用命令行创建命名管道(FIFO)
使用mkfifo
指令创建命名管道,其中filename表示文件名。
命名管道文件又叫FIFO(first in first out)文件,其文件类型为p
,表示是一个管道文件。
删除管道使用unlink
命令。
在程序中创建
命名管道也可以在程序中使用mkfifo
函数创建。此时mkfifo
是函数,而上面那个是指令。
具体使用方式如下:
#include<sys/types.h>
#include<sys/stat.h>
int mkfifo(const char* pathname,mode_t mode);
其中,参数pathname
表示的是创建管道文件的路径可以是绝对路径也可以是相对路径。mode
表示管道文件的权限,但文件最终的权限还要考虑umask
(权限掩码)。如果创建成功返回0,否则-1.
当某个进程要使用管道文件时,像普通文件那样,得先打开文件(open),且要确定以什么方式打开(write or read)。
命名管道的打开规则
如果当前是以读的方式打开FIFO文件,而此时没有进程以写的方式打开该FIFO文件时,读端进程就会阻塞,直到有进程以写的方式打开该FIFO。
如果当前是以写的方式打开FIFO文件,而此时没有进程以读的方式打开该FIFO文件时,写端进程就会堵塞,直到有进程以读的方式打开该FIFO。
用命名管道实现server和client通信
下面通过命名管道来实现server(服务端)和client(用户端)的通信。具体步骤如下:
- 将命名管道的操作方法及其属性封装成一个类,方便代码复用
- server端创建管道之后接收数据
- client端使用管道发送数据
- server端回收管道,使用
unlink
函数。
NamePipe.hpp
#pragma once
#include <iostream>
#include <cstdio>
#include <cerrno>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
const std::string comm_path = "./myfifo";
#define DefaultFd -1
#define Creater 1
#define User 2
#define Read O_RDONLY
#define Write O_WRONLY
#define BaseSize 4096
class NamePiped
{
private:
bool OpenNamedPipe(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(_fifo_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);
}
// const &: const std::string &XXX
// * : std::string *
// & : std::string &
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;
};
client.cc
#include "namedPipe.hpp"
// write
int main()
{
NamePiped fifo(comm_path, User);
if (fifo.OpenForWrite())
{
std::cout << "client open namd pipe done" << std::endl;
while (true)
{
std::cout << "Please Enter> ";
std::string message;
std::getline(std::cin, message);
fifo.WriteNamedPipe(message);
}
}
return 0;
}
server.cc
#include "namedPipe.hpp"
// server 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;
}