命名管道与匿名管道
命名管道是管道的一种,数据流向为单向故被称为管道;
与匿名管道相同属于一种内存级文件;
区别如下:
-
名字
-
匿名管道
没有名字,只存在于内存当中(类似内核缓冲区);
-
命名管道
有名字,基于文件系统,有对应的
Inode
以及对应属性;虽然存在
Inode
以及对应属性,但其对应的磁盘中的数据块指向为空,这表示命名管道不占用实际磁盘空间;其没有刷盘动作,因此对应数据不会刷新至磁盘中;
-
-
通信进程
-
匿名管道
只适用于具有亲缘关系的进程进行通信;
-
命名管道
可适用于毫不相关的进程间的通信;
-
-
数据流向
-
匿名管道
数据流向为单向;
-
命名管道
数据流向可以是单向也可以是双向(默认情况下的数据流向为单向);
-
-
创建方式
-
匿名管道
在进程中调用
pipe()
系统调用接口创建管道文件;创建管道文件后调用
fork()
系统调用接口创建子进程从而为父子(有亲缘关系的)进程创建通信信道; -
命名管道
在命令行中通常使用
mkfifo
命令创建管道;$ mkfifo myfifo;ll -i total 4 2360390 prw-rw-r-- 1 _LJP _LJP 0 Jul 15 13:41 myfifo # 在bash中使用fifo命令创建一个名为myfifo的命名管道文件,并用 ll -i 选项查看命名管道的状态以及对应的Inode号
结果来看命名管道存在对应的
Inode
;也可通过在进程中直接调用
mkfifo()
系统调用接口创建命名管道文件;NAME mkfifo - make a FIFO special file (a named pipe) SYNOPSIS #include <sys/types.h> #include <sys/stat.h> int mkfifo(const char *pathname, mode_t mode); RETURN VALUE On success mkfifo() returns 0. In the case of an error, -1 is returned (in which case, errno is set appropriately).
调用
mkfifo()
系统调用接口时需要包含<sys/types.h>
,<sys/stat.h>
的头文件;传入参数
pathname
与mode
分别代表 路径+管道文件名与 权限(匿名管道也是一个文件,需要具有权限) ;#include <sys/stat.h> #include <sys/types.h> #include <iostream> using namespace std; int main() { // mkfifo("./myfifo", 0664); return 0; }
结果:
$ ll -i;make;./mytest;make clean;ll -i total 8 2361492 -rw-rw-r-- 1 _LJP _LJP 80 Jul 15 14:03 makefile 2361488 -rw-rw-r-- 1 _LJP _LJP 147 Jul 15 14:02 test.cc g++ -o mytest test.cc -std=c++11 -g -Wall rm mytest total 8 2361492 -rw-rw-r-- 1 _LJP _LJP 80 Jul 15 14:03 makefile 2361494 prw-rw-r-- 1 _LJP _LJP 0 Jul 15 14:06 myfifo #命名管道文件被创建 2361488 -rw-rw-r-- 1 _LJP _LJP 147 Jul 15 14:02 test.cc
其中命名管道文件权限的最开头的
p
表示这个文件类型为一个管道文件(pipe
);
-
命名管道特点
命名管道读写端在进行通信时将会互相等待;
-
读端
当管道内不存在数据时读端会进行阻塞并等待写端向管道内写入数据;
# 读端 $ cat myfifo # 进行阻塞等待写端写入 #------------------- # 此时写端向管道内写入 $ echo "hello world" > myfifo #------------------- # 读端 $ cat myfifo hello world # 停止阻塞将管道文件内数据进行读取
-
写端
当管道内的数据没有被读端读取时写端会进行阻塞等待读端将当前管道内的数据进行读取;
# 写端 $ echo "hello world" > myfifo # 进行堵塞等待读端读取 #------------------- # 此时打开读端读取命名管道文件内信息 $ cat myfifo hello world #------------------- $ echo "hello world" > myfifo $ # 写端停止阻塞
命名管道虽然具有Inode
以及对应的属性,但其并不占用实际磁盘空间,只是用于数据传输,所以命名管道文件的大小始终为0
;
-
示例
# [会话1] $ while :; do echo "hello myfifo" > myfifo ;sleep 1 ;done # ------------------------ # [会话2] $ while :; do cat myfifo ;done hello myfifo hello myfifo hello myfifo hello myfifo ... # ------------------------ # [会话3] $ ll total 8 prw-rw-r-- 1 _LJP _LJP 0 Jul 15 14:39 myfifo $ ll total 8 prw-rw-r-- 1 _LJP _LJP 0 Jul 15 14:39 myfifo
命名管道的理解
当两个不同的进程同时打开同一个文件时在内核当中也只是打开了一个文件;
文件系统与进程之间是同级关系且进行了解耦合;
-
进程间通信的前提
先让不同的进程看到同一份资源;
进程打开文件时文件系统不需要因为被打开而对文件进行拷贝复制等操作;
命名管道不占用实际磁盘空间,只是用于传输,不存在刷盘(写入磁盘)的动作,故实际上使用的也是该文件的内核缓冲区;
两个进程通过管道文件的 路径+文件名 再通过Inode
编号来确定两个进程打开的是同一个管道文件( 路径+文件名 具有唯一性);
命名管道实现两个毫无关联的进程间通信
-
准备
-
server.cc
文件模拟实现服务端,用于管理命名管道文件,接收管道内文件并进行打印;
-
client.cc
文件模拟实现客户端,用于向客户端写入数据;
-
comm.hpp
文件用于包含所需头文件及简单声明定义所需常量等;
-
-
具体思路
两个毫不相关的进程,其中一个进程用来创建管道文件并维护管道文件,同时负责接收另一个进程向管道文件内写入的数据;
另一个进程用来向管道文件内写入数据从而进行简单交互;
-
comm.hpp
#include <fcntl.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #include <cstdlib> #include <iostream> #include <string> #define PIPEFILE "./myfifo" // 管道文件文件名 #define MODE 0666 // 管道文件权限 #define SIZE 1024 // 缓冲区所需大小 using namespace std; enum { FIFO_CREATE_ERR = 1, FIFO_DELETE_ERR, FIFO_OPEN_ERR }; // 枚举对应错误码 class PipeInit { public: // 创建管道文件 PipeInit() { int n = mkfifo(PIPEFILE, MODE); if (n == -1) { cerr << "CREATE PIPE FAIL" << endl; exit(FIFO_CREATE_ERR); } } // 销毁管道文件 ~PipeInit() { int m = unlink(PIPEFILE); if (m == -1) { cerr << "DEL PIPE FAIL" << endl; exit(FIFO_DELETE_ERR); } } };
创建一个管道类用于管理管道文件,即利用其构造函数创建管道文件,在退出后析构函数释放对应管道文件;
enum
枚举出错误信息常量; -
server.cc
#include "comm.hpp" // 服务端管理管道文件 int main() { // 创建管道信道 PipeInit myfifo; // 打开管道文件 int fd = open(PIPEFILE, O_RDONLY); if (fd < 0) { cerr << "SERVER OPEN PIPE FAIL" << endl; exit(FIFO_OPEN_ERR); } cout << "Server open file done" << endl; // 开始进行通信 char buffer[SIZE] = {0}; // 清空 当做字符串读取 while (true) { int x = read(fd, buffer, sizeof(buffer)); if (x > 0) { buffer[x] = '\0'; cout << "server get a massage from client# "; cout << buffer << endl; } if (x == 0) { cout << "Client quit" << endl; break; } } // 结束通信关闭管道 close(fd); return 0; }
实例化一个对象依次构建对话对应所需信道;
调用
open()
系统调用接口打开管道文件;创建
buffer[]
字符串充当缓冲区,并调用read()
系统调用接口从命名管道文件中读取数据并进行打印;当写端被关闭时读端所调用的
read()
系统调用接口将返回0
表示读到了文件末尾,作判断并结束程序;对话完毕后调用系统调用接口
close()
关闭对应文件描述符; -
client.cc
#include "comm.hpp" int main() { // int fd = open(PIPEFILE, O_WRONLY); if (fd < 0) { cerr << "CLIENT OPEN PIPE FAIL" << endl; exit(FIFO_OPEN_ERR); } cout << "Client open file done" << endl; string line; while (true) { cout << "Please Enter Your Massage@ "; getline(cin, line); write(fd, line.c_str(), line.size()); } close(fd); return 0; }
调用系统调用接口
open()
打开命名管道文件;创建缓冲区
buffer[]
并调用getline(cin,buffer)
从键盘获取数据并存储至缓冲区中;调用
write()
系统调用接口将缓冲区中数据写入至命名管道文件中;对话完成后调用系统调用接口
close()
关闭对应文件描述符;
完整代码(供参考):