在之前的文章中我们讲述了匿名管道的原理的以及对应的简单的两个小例子,在本文中,我们将来继续管道的学习 -- 命名管道。
命名管道
- 管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
- 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
- 命名管道是一种特殊类型的文件
可以使用mkfifo来在bash中创建一个管道 :
简要介绍命名管道原理
之前我们在文件相关的内容讲述过引用计数的知识,当有多个文件指向同一个inode时,inode就会++。进程间通信首先需要的就是让两个进程能够看到同一份内容,那么下面我们就来简要的讲述一下如何看到同一份内容:首先一个进程打开一个新的文件,然后当有另一个进程需要打开这个文件的时候,首先需要做的就是,在所有打开的文件列表中查看是否已经打开了该文件,若是已经打开了就将文件结构体中的一个变量ref(引用计数)++。当关闭文件时,先将引用计数--,当引用计数归零时就删除该文件。这样两个进程就看到了在OS中的同一份资源,我们就达到了两个进程看到同一份内容的目标。
在这里与我们之前学习过的普通文件不同,普通文件我们需要定时将文件刷新到磁盘中,而这里的文件不需要这样的操作,只需要在内存中打开,然后让多个进程进行通信, 这种文件也被称为内存级文件(命名管道文件),不会进行刷盘。
如何保证两个毫不相关的进程,看到的是同一个文件。文件的唯一性是用路径来确定的,让不同的进程通过文件路径+文件名看到同一个文件,并打开,就是看到了同一份资源 --- 具备了进程间通信的前提。
匿名管道与命名管道的区别
- 匿名管道由pipe函数创建并打开。
- 命名管道由mkfifo函数创建,打开用open
- FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。
例子-用命名管道实现server&client通信
// comm.hpp
#pragma once
#include <iostream>
#include <string>
#define NUM 1024
const std::string fifoname = "./fifo"; // 文件所在的路径
uint32_t mode = 0666; // 创建文件的模式 - 权限
// server
#include <iostream>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "comm.hpp"
#include <unistd.h>
int main()
{
// 1. 创建管道文件,只需要创建一次
umask(0); // 这个设置不会影响系统的默认配置,只会影响当前进程
int n = mkfifo(fifoname.c_str(), mode);
if (n != 0)
{
std::cout << errno << " : " <<strerror(errno) << std::endl;
return 1;
}
std::cout << "creat fifo file success" << std::endl;
// 2. 让服务端直接开启管道文件
int rfd = open(fifoname.c_str(), O_RDONLY);
if (rfd <= 0)
{
std::cout << errno << " : " <<strerror(errno) << std::endl;
return 2;
}
std::cout << "open fifo success, begin ipc" << std::endl;
// 3. 正常通信
char buffer[NUM];
while (true)
{
buffer[0] = 0;
ssize_t n = read(rfd, buffer, sizeof(buffer) - 1);
if (n > 0)
{
buffer[n] = '\0';
std::cout << "client# " << buffer << std::endl;
}
else if (n == 0)
{
std::cout << "client quit, me too" << std::endl;
break;
}
else
{
std::cout << errno << " : " << strerror(errno) << std::endl;
break;
}
}
close(rfd);
unlink(fifoname.c_str()); // 对生成的管道文件删除
std::cout << "管道已成功删除" << std::endl;
return 0;
}
// client
#include <iostream>
#include <cstdio>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "comm.hpp"
#include <unistd.h>
#include <cassert>
int main()
{
//1. 不需创建管道文件,我只需要打开对应的文件即可!
int wfd = open(fifoname.c_str(), O_WRONLY);
if(wfd < 0)
{
std::cerr << errno << ":" << strerror(errno) << std::endl;
return 1;
}
// 可以进行常规通信了
char buffer[NUM];
while(true)
{
std::cout << "请输入你的消息# ";
char *msg = fgets(buffer, sizeof(buffer) - 1, stdin); // 这里不用-1,fgets会自己处理,但是统一 -1,C的接口不用-1,但是其余的接口需要,这里可以进行统一的处理
assert(msg);
(void)msg;
// 处理输入消息时获得的 \n 符号
buffer[strlen(buffer) - 1] = 0;
// abcde\n\0
// 012345
if(strcasecmp(buffer, "quit") == 0) break; // 忽略大小写进行比较
ssize_t n = write(wfd, buffer, strlen(buffer)); // 这是往文件里面写不会关心是否有'\0'
assert(n >= 0);
(void)n;
}
close(wfd);
return 0;
}
下面就是上述代码运行的结果:从中可以看出当sever端开始运行的时候管道成功创建,但是开没有成功打开,再将客户端运行时,管道就正常的打开并运行。