1. 命名管道的原理
命名管道是一种特殊类型的文件。

2. 命名管道的操作(1.命令级 2.代码级)
2.1 命令级
命名管道首先的有文件,创建命名管道,命令:mkfifo
管道的本质就是一个队列:


文件类型为p:是一个管道文件

这个就叫做管道。

说明对管道文件进行重定向,文件大小一直为0,本质上文件内容没有刷新到磁盘里,是在内存中,启动另一个进程,OS瞬间创建PCB,文件描述符表,页表等等,本质是从内存中读取,结束之后该管道的文件大小依旧为0

unlink fifo 删除管道文件 fifo

2.2 代码级
需要两个进程,二者可以毫无关系
bash
#Makefile
.PHONY:all
all:server client
client:client.cpp
g++ -o $@ $^ -std=c++11
server:server.cpp
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -rf client
rm -rf server

server 用代码创建出一个管道:


client 和 server 要看见同一份资源:common.hpp
cpp
//common.hpp
#ifndef __COMMON_HPP__
#define __COMMON_HPP__
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
const std::string fifoname = "fifo";
mode_t mode = 0666;
#endif
cpp
//server.cpp
#include "common.hpp"
int main()
{
int n = mkfifo(fifoname.c_str(), mode);
if(n == 0)
{
std::cout << "mkfifo successs" << std::endl;
}
else
{
std::cout << "mkfifo failed" << std::endl;
}
return 0;
}

所以该管道文件的权限是:664
再次运行server的话,就会发现创建失败:

为什么失败呀?输出一下:(因为这个文件已经存在)


从上面的运行结果中,我们可以知道:创建管道的时候有可能会失败的。所以还得让server文件将管道释放掉:


cpp
//server.cpp
#include "common.hpp"
int main()
{
int n = mkfifo(fifoname.c_str(), mode);
if(n == 0)
{
std::cout << "mkfifo successs" << std::endl;
}
else
{
// std::cout << "mkfifo failed" << std::endl;
perror("mkfifo");
}
sleep(5);
int m = unlink(fifoname.c_str());
(void)m;
return 0;
}


至此,命名管道就被创建出来了,也能被我们删除,server端,client端都能被看见,server、client都以文件形式打开,以文件形式读写就能完成通信了。


bash
#Makefile
.PHONY:all
all:server client
client:client.cpp
g++ -o $@ $^ -std=c++11
server:server.cpp
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -rf client
rm -rf server
cpp
// conmmon.hpp
#ifndef __COMMON_HPP__
#define __COMMON_HPP__
#include <iostream>
#include <cstdio>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
const std::string fifoname = "fifo";
mode_t mode = 0666;
// int size = 128;
#define SIZE 128
#endif
cpp
// client.cpp
#include "common.hpp"
//客户端不需要创建管道文件
int main()
{
int fd = open(fifoname.c_str(),O_WRONLY);
if(fd < 0)
{
perror("open");
exit(1);
}
std::string message;
while(true)
{
std::cout << "Please Enter# ";
std::getline(std::cin,message);
write(fd,message.c_str(),message.size());
}
close(fd);
return 0;
}
cpp
//server.cpp
#include "common.hpp"
int main()
{
// 1. 创建管道文件
int n = mkfifo(fifoname.c_str(), mode);
if(n == 0)
{
std::cout << "mkfifo successs" << std::endl;
}
else
{
// std::cout << "mkfifo failed" << std::endl;
perror("mkfifo");
exit(1);
}
// 2. 打开文件,就和普通文件没有区别
// 命名管道操作特点:打开一端口,在另一端没有打开时候,open会阻塞
//server --- 读
int fd = open(fifoname.c_str(),O_RDONLY);
if(fd < 0)
{
perror("open");
exit(2);
}
std::cout <<" open file sucess" << std::endl;
// 3. 通信
// char buffer[size]; //支持,编译器比较新,一般支持变长数组;Linux的C语言标准是C99,C语言种类是GNUC,能编过的
char buffer[SIZE]; //遵守之前的规则
while(true)
{
buffer[0] = 0; //清空字符串,O(1)时间复杂度
ssize_t num = read(fd,buffer,sizeof(buffer)-1);
if(num > 0)
{
buffer[num] = 0;
std::cout << "client say#" << buffer << std::endl;
}
std::cout << "num: " << num << std::endl;
}
// 4. 归还资源
close(fd);
int m = unlink(fifoname.c_str());
(void)m;
return 0;
}
细节说明:


可以看见运行server之后,只显示了 mkfifo sucess 的信息,没有输出上面的字符串,不是管道没有打开,而是因为命名管道有一个操作上的特点:打开一端,在另一端没有打开时候,open会阻塞。



当我们写端client关闭,server端读到的就是0:

所以对server端进行补充:
cpp
while(true)
{
buffer[0] = 0; //清空字符串,O(1)时间复杂度
ssize_t num = read(fd,buffer,sizeof(buffer)-1);
if(num > 0)
{
buffer[num] = 0;
std::cout << "client say#" << buffer << std::endl;
}
else if(num == 0)
{
std::cout << "client quit,me too!" << std::endl;
break;
}
else
{
break;
}
std::cout << "num: " << num << std::endl;
}

命名管道的4种情况和匿名管道的四种情况是一样的。
**命名管道:主要解决,毫无关系的进程之间,进行文件级进程通信!!!**剩下的特点和匿名管道的特点是一样的。
2.3 将以上代码进行调整一下,用C++面向对象的方式:
为什么不把close 和 remove 写进析构函数中,因为将来客户端只关闭不删除,服务器是既关闭又删除,所以分开写比较好。
cpp
//common.hpp
#ifndef __COMMON_HPP__
#define __COMMON_HPP__
#include <iostream>
#include <cstdio>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
const std::string fifoname = "fifo";
mode_t mode = 0666;
// int size = 128;
#define SIZE 128
#endif
cpp
//NamedPipe.hpp
#pragma once
#include "common.hpp"
const int defaultfd = -1;
class NamedPipe
{
public:
NamedPipe(const std::string &name) : _name(name), _fd(defaultfd)
{
}
bool Creat()
{
// 1. 创建管道文件
int n = mkfifo(fifoname.c_str(), mode);
if (n == 0)
{
std::cout << "mkfifo success " << std::endl;
}
else
{
// std::cout << "mkfifo failed "<<std::endl;
perror("mkfifo");
return false;
}
return true;
}
void Close()
{
if (_fd == defaultfd)
return;
else
close(_fd);
}
bool OpenForRead()
{
// 2. 打开文件,就和普通文件没有区别
// 命名管道操作特点:打开一端口,在另一端没有打开时候,open会阻塞
// server --- 读
_fd = open(fifoname.c_str(), O_RDONLY);
if (_fd < 0)
{
perror("open");
return false;
}
std::cout << " open file sucess" << std::endl;
return true;
}
bool OpenForWrite()
{
_fd = open(fifoname.c_str(), O_WRONLY);
if (_fd < 0)
{
perror("open");
return false;
}
return true;
}
// 输入参数:connst &
// 输出参数:*
// 输入输出参数:&
bool Read(std::string *out)
{
// 3. 通信
// char buffer[size]; //支持,编译器比较新,一般支持变长数组;Linux的C语言标准是C99,C语言种类是GNUC,能编过的
char buffer[SIZE] = {0}; // 清空字符串,O(1)时间复杂度
ssize_t num = read(_fd, buffer, sizeof(buffer) - 1);
if (num > 0)
{
buffer[num] = 0;
*out = buffer;
}
else if (num == 0)
{
return false;
}
else
{
return false;
}
return true;
}
void Write(const std::string &in)
{
write(_fd,in.c_str(),in.size());
}
void Remove()
{
int m = unlink(fifoname.c_str());
(void)m;
}
~NamedPipe()
{
}
private:
std::string _name;
int _fd;
};
cpp
//server.cpp
#include "NamedPipe.hpp"
int main()
{
NamedPipe named_pipe(fifoname);
named_pipe.Creat();
named_pipe.OpenForRead();
// 3. 通信
std::string message;
while(true)
{
bool res = named_pipe.Read(&message);
if(!res)
break;
std::cout << "client say# " << message << std::endl;
}
// 4. 归还资源
named_pipe.Close();
named_pipe.Remove();
return 0;
}
cpp
//client.cpp
#include "NamedPipe.hpp"
// 客户端不需要创建管道文件
int main()
{
NamedPipe named_pipe(fifoname);
named_pipe.OpenForWrite();
while (true)
{
std::cout << "Please Enter# ";
std::string line;
std::getline(std::cin, line);
named_pipe.Write(line);
}
named_pipe.Close();
return 0;
}
3. 总结
命名管道:主要解决,毫无关系的进程之间,进行文件级进程通信!!!
剩下的特点,匿名管道和命名管道特点相同。
前面的这两种通信方案都是文件级的,进程间通信。但是依旧有内核参与的。OS给我们创建一个双方都能看见的文件。这两种只能解决简单的文件通信