1 命名管道
前面讲到匿名管道,有一个很大的限制,那就是只有具有相同祖先(具有亲缘关系)的进程间才能进行通信,但是如果想实现不同进程间的通信,这个时候命名管道就发挥着巨大作用。
命名管道是一种特殊类型的文件,和匿名管道不一样的是这个命名管道是有文件名的,有路径的,有对应的inode,只是不用向磁盘中进行IO。
多个进程是否可以打开同一个文件呢?这个问题在前面讲文件描述符时,就已经知道是可以的,例如:显示器文件,多个进程都可以打开,并向显示器写入。
1.1 创建命名管道
命令行创建:
bash
mkfifo filename
系统调用创建:
cpp
int mkfifo(const char *filename,mode_t mode);
参数为:创建管道的文件名,权限。
1.2 匿名管道和命名管道使用不同
匿名管道由pipe函数创建并打开。
命名管道由mkfifo函数创建,打开用open
FIFO(命名管道)与pipe(匿名管道)之间唯一区别在它们创建与打开的方式不同,⼀但这些工作完成之后,它们具有相同的语义。
2 用命名管道实现server与client之间的通信
读写端共同包含的部分:
common.hpp
cpp
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <string>
#include <fcntl.h>
#include <unistd.h>
const std::string filename="fifo";
const int mode=0666;
const int size=1024;
权限,默认文件名。
namepipe类
默认构造函数
cpp
namepipe(const std::string &filename) : _filename(filename),_fd(defaultfd)
{}
成员_fd表示文件描述符,所以要得等到,打开文件时再确认,这里默认为-1。
初始化创建管道文件
cpp
bool InitNamepipe()
{
int n = mkfifo(filename.c_str(), mode);
if (n == 0)
{
std::cout << "mkfifo success " << std::endl;
}
else
{
perror("mkfifo");
return false;
}
return true;
}
利用mkfifo函数创建一个管道。
这里要注意一个从管道中写入数据,一个向管道中读取数据,所以就要有两种打开方式:
也就是在open函数设置不同的权限
读打开
cpp
bool OpenForRead()
{
_fd = open(filename.c_str(), O_RDONLY);
if (_fd < 0)
{
perror("open");
return false;
}
else
{
std::cout << "open success: " << _fd << std::endl;
}
return true;
}
写打开
cpp
bool OpenForWrite()
{
_fd = open(filename.c_str(), O_WRONLY);
if (_fd < 0)
{
perror("open");
return false;
}
else
{
std::cout << "open success: " << _fd << std::endl;
}
return true;
}
读数据
cpp
void Write(const std::string &in)
{
write(_fd, in.c_str(), in.size());
}
写数据
cpp
bool Read(std::string *out)
{
char buffer[size] = {0};
ssize_t num = read(_fd, buffer, sizeof(buffer) - 1);
if (num > 0)
{
buffer[num] = 0;
*out = buffer;
}
else if (num == 0)
{
std::cout << "write out,me too" << std::endl;
return false;
}
else
{
return false;
}
return true;
}
读取数据失败或写端关闭返回false,这里out是一个输出型参数,将读出的数据通过out带出。
这里read也是默认阻塞等待写端写入。
回收资源
关闭文件描述符:
cpp
void Close()
{
if(_fd==defaultfd)
return;
else
close(_fd);
}
防止没有打开文件,就直接close,所以这里有一个defaultfd的操作。
删除管道文件:
cpp
void Remove()
{
int m=unlink(_filename.c_str());
(void)m;
}
namepipe.hpp
cpp
#pragma once
#include "common.hpp"
const int defaultfd = -1;
class namepipe
{
public:
namepipe(const std::string &filename) : _filename(filename),_fd(defaultfd)
{}
bool InitNamepipe()
{
int n = mkfifo(filename.c_str(), mode);
if (n == 0)
{
std::cout << "mkfifo success " << std::endl;
}
else
{
perror("mkfifo");
return false;
}
return true;
}
bool OpenForRead()
{
_fd = open(filename.c_str(), O_RDONLY);
if (_fd < 0)
{
perror("open");
return false;
}
else
{
std::cout << "open success: " << _fd << std::endl;
}
return true;
}
bool OpenForWrite()
{
_fd = open(filename.c_str(), O_WRONLY);
if (_fd < 0)
{
perror("open");
return false;
}
else
{
std::cout << "open success: " << _fd << std::endl;
}
return true;
}
void Write(const std::string &in)
{
write(_fd, in.c_str(), in.size());
}
bool Read(std::string *out)
{
char buffer[size] = {0};
ssize_t num = read(_fd, buffer, sizeof(buffer) - 1);
if (num > 0)
{
buffer[num] = 0;
*out = buffer;
}
else if (num == 0)
{
std::cout << "write out,me too" << std::endl;
return false;
}
else
{
return false;
}
return true;
}
void Close()
{
if(_fd==defaultfd)
return;
else
close(_fd);
}
void Remove()
{
int m=unlink(_filename.c_str());
(void)m;
}
~namepipe()
{
}
private:
std::string _filename;
int _fd;
};
client
cpp
#include"namepipe.hpp"
int main()
{
namepipe pipe(filename);
pipe.OpenForWrite();
while(true)
{
std::string in;
std::getline(std::cin,in);
pipe.Write(in);
}
pipe.Close();
}
client端发送数据。
server
cpp
#include"namepipe.hpp"
int main()
{
namepipe pipe(filename);
pipe.InitNamepipe();
pipe.OpenForRead();
while(true)
{
std::string* out;
if(pipe.Read(out))
std::cout<<*(out)<<std::endl;
else
break;
}
pipe.Close();
pipe.Remove();
return 0;
}
server端读数据。
结果演示:

在client端输入数据,server就可以看到对应的读取数据。