有了匿名管道的知识,命名管道对我们来说就不是很难了!
匿名管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。如果我们想在不相关的进程之间交换数据,可以创建和使用 FIFO 文件来做这项工作,它经常被称为命名管道。
命名管道是一种特殊类型的文件!
命名管道既可以在命令行上创建,也可以在程序中创建!
mkfifo filename
下面我们写个脚本向这个管道文件中不断输入数据观察一下结果怎么样!
如下图可以看到如果向管道文件不断的写入信息,那么 管道文件的大小还是保持为 0
不变!为什么呢❓❓❓
其实就是因为操作系统知道这是一个管道文件,知道这个文件是管道类型,所以对于管道文件和普通文件的处理方式不一样,对于普通文件来说它加载到内存中,我们不断的向普通文件里面写入数据,它是会不断的刷新到磁盘上的(与刷新策略有关),所以如果这里是普通文件的话那么我们会看到大小一直在变;而如果是管道文件,它被加载到内存之后,无论是对它写入还是刷新,它都不会刷新到磁盘上面,这样子一来就达到了内存级别的交互,大大的提高了效率,和我们的匿名管道是类似的!
再看下面的结果,我们仅仅使用了 cat
指令一次,就不断的循环将写入管道的数据一个一个的读到屏幕上面,说明只要有信息在向管道文件写入的时候,我们读取管道文件,会进入阻塞状态一直等待写入的信息!而对于普通文件,则不会有读取的时候阻塞是情况!
那么命名管道是如何做到让不同的进程看到同一份资源的❓❓❓
其实是因为操作系统可以让不同的进程打开指定名称(路径+文件名)的同一个文件,而对于同一个目录下,是不允许有同名文件的,这也就确定了文件的唯一性!这也就是为什么命名管道叫做命名管道,因为**它是通过文件名称来标定唯一性的!而对于匿名管道**来说,它是通过父子进程的继承属性来实现的!
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char* pathname, mode_t mode);
// 功能:创建一个fifo特殊文件也就是命名管道
// 参数:pathname代表创建该文件的路径,mode代表创建文件的权限,会受掩码影响,可使用umask(0)将掩码设为0
// 返回值:成功返回0,失败返回-1,并且有可能设置错误信息errno
接下来我们来写一串代码,让 server.cpp
负责打开通信、接收信息和关闭通信,让 client.cpp
负责发送信息,并且我们将它们的公共信息放在头文件 commom.hpp
中(.hpp
指用 C/C++
语言编写的头文件,通常用来定义数据类型,声明变量、函数、结构和类),所以在程序中要实现的话,我们就得用在程序中创建管道文件的语言级别接口 mkfifo
!下面代码中默认将管道文件放到系统中的共享目录 /tmp
中!
下面还会配合一个我们以前使用过的接口函数 unlink
,它是用来移除目录项的,当然也可以用 rm
来删除,但是我们这里可以用用 unlink
!
#include <unistd.h>
int unlink(const char *path);
// 功能:移除一个目录项目
// 参数:path表示要移除目录的路径
// 返回值:成功移除返回0,失败则返回-1,并且设置错误码,源文件内容不变
Makefile
:
.PHONY : all
all : server client
server : server.cpp
g++ -o $@ $^ -std=c++11 -g
client : client.cpp
g++ -o $@ $^ -std=c++11 -g
.PHONY : clean
clean:
rm -f server client
common.hpp
:
#pragma once
#include <iostream>
#include <string>
#include <cassert>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#define NAMED_PIPE "/tmp/my_named_pipe" // 存放到系统中的共享目录下
bool createFifo(const std::string& path)
{
umask(0);
int n = mkfifo(path.c_str(), 0666); // 创建管道文件
if(n == 0)
return true;
else
{
std::cout << "errno: " << errno << " err string: " << strerror(errno) << std::endl;
return false;
}
}
void removeFifo(const std::string& path)
{
int n = unlink(path.c_str()); // 移除管道文件目录项
assert(n == 0);
(void)n;
}
server.cpp
:
#include "commom.hpp"
int main()
{
// 创建管道
bool flag = createFifo(NAMED_PIPE);
assert(flag);
(void)flag;
// 打开文件后,接收信息,最后关闭文件
std::cout << "server begin" << std::endl;
int rfd = open(NAMED_PIPE, O_RDONLY);
std::cout << "server end" << std::endl;
if(rfd < 0)
exit(1);
char buffer[1024];
while(true)
{
ssize_t n = read(rfd, buffer, sizeof(buffer) - 1);
if(n > 0)
{
buffer[n] = '\0';
std::cout << "client->server# " << buffer << std::endl;
}
else if(n == 0)
{
std::cout << "client quit, me too!" << std::endl;
break;
}
else
{
std::cout << "errno: " << errno << " err string: " << strerror(errno) << std::endl;
break;
}
}
close(rfd);
// 删除管道
removeFifo(NAMED_PIPE);
return 0;
}
client.cpp
:
#include "commom.hpp"
int main()
{
// 打开文件后发送信息,最后关闭文件
std::cout << "client begin" << std::endl;
int wfd = open(NAMED_PIPE, O_WRONLY | O_TRUNC, 0666);
std::cout << "client end" << std::endl;
if(wfd < 0)
exit(1);
char buffer[1024];
while(true)
{
std::cout << "Please Say# ";
fgets(buffer, sizeof(buffer), stdin); // 从键盘写到buffer
buffer[strlen(buffer) - 1] = '\0'; // 将最后的回车去掉,不必担心会得到buffer[-1],因为我们始终要按一次回车
// 将buffer写入文件
ssize_t n = write(wfd, buffer, strlen(buffer));
assert(n == strlen(buffer));
(void)n;
}
close(wfd);
return 0;
}
接下来我们运行一下看看效果:
可以看到 client
进程结束掉了,这是因为我们还没有执行 server
,那么其就不会创建管道文件。如果我们在 client 中是以 O_CREAT
顺便创建了管道文件,那么我们执行 server
的时候会发现管道文件已经存在,会直接触发 assert
断言的,所以一定要先开启 server
!
这种情况就不会出现以上的问题了,那么我们来看看会有什么效果:
若没有还没有打开 client
,那么 server
就会像上图一样,卡在 open
函数那里,因为我们学了匿名管道的读写规则也知道,如果管道的写入端没有启动,那么读取端的话会一直阻塞等待写入端的启动!
下面我们看看 client
打开后的情况:
我们可以看到这样子就实现了正常的程序之间的通信啦!client
发送信息后,server
进行接收!期间我们也可以发现 如果 client
还没有发送消息的话,但是 client
的进程是正在执行的,那么 server
就会一直阻塞着等待 client
的写入!
pipe
函数创建并通过文件描述符 pipefd[2]
进行操作mkfifo
函数创建,打开用 open
函数FIFO
(命名管道)与 pipe
(匿名管道)之间 唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。FIFO
时: O_NONBLOCK disable
:阻塞直到有相应进程为写而打开该 FIFO
O_NONBLOCK enable
:立刻返回成功FIFO
时: O_NONBLOCK disable
:阻塞直到有相应进程为读而打开该 FIFO
O_NONBLOCK enable
:立刻返回失败,错误码为 ENXIO