🏠 大家好,我是Yui_,一位努力学习C++/Linux的博主
💬
🍑 如果文章知识点有错误的地方,请指正!和大家一起学习,一起进步👀
🚀 如有不懂,可以随时向我提问,我会全力讲解
🔥 如果感觉博主的文章还不错的话,希望大家关注、点赞、收藏三连支持一下博主哦~!
🔥 你们的支持是我创作的动力!
🧸 我相信现在的努力的艰辛,都是为以后的美好最好的见证!
🧸 人的心态决定姿态!
💬 欢迎讨论:如有疑问或见解,欢迎在评论区留言互动。
👍 点赞、收藏与分享:如觉得这篇文章对您有帮助,请点赞、收藏并分享!
🚀 分享给更多人:欢迎分享给更多对 Linux 感兴趣的朋友,一起学习!
文章目录
1.什么是命名管道
在 Unix/Linux 系统中,管道(Pipe)是一种重要的进程间通信(IPC,Inter-Process Communication)机制。除了前面介绍的匿名管道(Anonymous Pipe),系统还提供了命名管道(Named Pipe),通常称为 FIFO(First In, First Out)。命名管道通过一个在文件系统中存在的路径名来标识,使得不相关的进程之间也能通过它进行通信。
命名管道是一种特殊类型的文件,它在文件系统中有一个明确的名称,可以被多个进程打开和访问。与匿名管道不同,命名管道不局限于具有亲缘关系的进程(如父子进程),任何具有访问权限的进程都可以通过命名管道进行通信。
我们可以把命名管道看成”挂名“的匿名管道,把匿名管道加入文件系统中,但仅仅是挂个名而已,目的是为了人其他进程也能看到也看到这个文件(文件系统中的文件可以被所有的进程看到)
注意:
命名管道虽然能在文件系统被看到,但是它是没有Data block的,也就它不会存储在磁盘中,是一个内存文件。所以命名管道这个特殊文件大小为0
1.2 命名管道的特点
- 持久性:命名管道存在于文件系统中,直到被删除。即使创建它的进程退出,命名管道仍然存在,等待其他进程的连接。
- 双向通信:虽然管道本质上是半双工的(单向),但通过两个命名管道可以实现全双工通信。
- 跨进程通信:命名管道允许不相关的进程之间进行通信,只需知道管道的路径即可。
- 无需父子关系:任何进程都可以打开命名管道进行读写,不需要继承关系。
2. 创建命名管道
创建命名管道有两种方法:
- 直接在命令行上创建。
- 在程序中创建。
在命令行创建:
mkfifo mypipe
效果如下:
ubuntu@VM-20-9-ubuntu:~/pipeTest/namePipe$ mkfifo mypipe
ubuntu@VM-20-9-ubuntu:~/pipeTest/namePipe$ ls -l
total 0
prw-rw-r-- 1 ubuntu ubuntu 0 Nov 1519:56 filename
prw-rw-r-- 1 ubuntu ubuntu 0 Nov 1520:21 mypipe
2.1 在C程序中创建命名管道
为了在C程序中创建命名管道,我们需要用到的函数也是
mkfifo
。
mkfifo:
头文件:
#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>
格式:
intmkfifo(constchar*pathname,mode_t mode);
参数介绍:
pathname
- 命名管道的路径名。- 在文件系统中创建一个文件,代表命名管道。mode
- 管道文件的权限(类似于文件的权限),是一个mode_t
类型值。- 常见值: - 例如0666
,允许所有用户读写。-mode
会受到 进程的umask
设置 的影响。 为了防止mode的设置被umask影响,可以事先将umsak设置为0,umask(0)
. 返回值: 成功:0 失败:-1,并设置error
下面我来实现一个进程向另一个进程发送信息: 客户端:client.cc
//客户端向服务端发送消息#include<iostream>#include<sys/types.h>#include<unistd.h>#include<sys/stat.h>#include<cstring>#include<fcntl.h>#include"common.hpp"usingnamespace std;intmain(){int mf =mkfifo(pipePath,md);if(mf<0){perror("mkfifo");exit(1);}//打开管道int fd =open(pipePath,O_WRONLY);//以写方式打开if(fd <0){perror("open");exit(1);}//char data[SIZE];
string data;while(true){//开始写入数据
cout<<"cilent message# ";getline(cin,data);if(data =="exit")break;
ssize_t n =write(fd,data.c_str(),data.size());if(n<0){perror("write");exit(1);}}//退出客户端
cout<<"exit"<<endl;close(fd);return0;}
服务端:
server.cc
//服务端接受客户端的信息#include<iostream>#include<sys/types.h>#include<unistd.h>#include<sys/stat.h>#include<cstring>#include<fcntl.h>#include"common.hpp"usingnamespace std;intmain(){//打开管道int fd =open(pipePath,O_RDONLY);//以读的方法if(fd <0){perror("open");exit(1);}char data[SIZE];while(true){int n =read(fd,data,sizeof(data)-1);if(n<0){perror("read");exit(1);}elseif(n ==0){//写端关闭
cout<<"写端关闭"<<endl;break;}
data[n]=0;
cout<<"client say:"<<data<<endl;}close(fd);if(unlink(pipePath)==-1){perror("unlink failed");return1;}//关闭管道return0;}
公共文件:
common.hpp
#pragmaonce#defineSIZE1024//存储管道的路径+名字constchar* pipePath ="./myPipe";//也可以用绝对路径,当然相对路径更简单//设置mask
mode_t md =0666;
效果图:
3. 命名管道的工作原理
再次回到文件系统:当重复多次打开一个文件时,并不会费力的打开多次,而是在第一次的基础上对
struct_file
结构体中的引用计数自增1,所以对于同一个文件,不同的进程打开了,看到的就是同一个。
这也是管道实现的本质:让不同的进程看到同一块空间。
因为命名管道适用于独立的进程
IPC
,所以无论是读端还是写端,进程
A
,进程
B
为其分配的文件描述符都是3。
**我们知道如果是匿名管道,因为是依靠继承才看到同一文件的,所以读写端的
fd
是不一样的。**
3.1 命名管道和匿名管道的区别与联系
3.1.1 命名管道与匿名管道的区别
- 匿名管道只能用于具有血缘关系的进程间通信;而命名管道就不一样了,无论有没有血缘关系都可以。
- 匿名管道是通过
pipe
函数创建出来了;而命名管道需要先通过mkfifo
函数创建,然后再通过open
打开使用。 - 当出现多条匿名管道时,可能会出现写端
fd
被重复继承的情况;而命名管道就不会出现这种情况。
3.1.2 命名管道和匿名管道的联系
- 两个都是属于管道,都是操作系统中最古老的进程通信方式,都自带有同步和异步机制,提供的都是流式数据传输 与匿名管道相同的也有在这4个场景下的处理
- 管道为空时,读端堵塞,等待写端写入数据。
- 管道已满时,写端堵塞,等待读端读取数据。
- 进程通信时,关闭读端,OS发出13号信息
SIGPIPE
终止写端进程。 - 进程通信时,关闭写端,读端读取到0字节数据,可以凭借这个特征来终止进程。
4.命名管道的简单应用
我们可以利用命名管道来实现一些简单的功能,加强我们对命名管道的理解。现在我们打算实现的文件拷贝小程序。
我们可以利用命名管道实现不同进程间
IPC
,也就是一个进程读取文件中的内容然后写进管道当中,然后另一个进程在通过管道将数据读出保存到新的文件,如此一来就是实现了一个进程的文件拷贝功能。
这也是在网络上下载应用的方式,因为下载应用的本质就是下载文件,我们将服务器看作写端,自己的电脑看作读端,那么下载这个动作的本质就是
IPC
,不过是在网络层面实现的。
公共区域
common.hpp
:
#pragmaonce#include<unistd.h>#include<sys/types.h>#include<cstdlib>#include<iostream>#include<sys/stat.h>#include<fcntl.h>#include<cstring>#defineSIZE1024//打开拷贝的文件路径constchar* filePath ="./file.txt";//要打开的管道路径constchar* pipePath ="./pipe";//mask码设置
mode_t md =0666;
服务端
server.cc
/**
* 服务端通过命名管道将本地文件拷贝到客户端
* 先打开命名管道,再打开需要被拷贝的文件,将文件通过命名管道发送给另一个程序
*
*/#include"common.hpp"intmain(){//创建命名管道if(mkfifo(pipePath,md)==-1){perror("mkfifo");exit(1);}//打开管道int fd =open(pipePath,O_WRONLY);if(fd ==-1){perror("open");exit(1);}//打开需要被拷贝的文件
FILE* fp =fopen(filePath,"r");if(fp ==nullptr){perror("fread");exit(1);}//将文件传给管道char buf[SIZE];
size_t bytes =0;while(true){
size_t n =fread(buf,1,sizeof(buf),fp);if(n>0){//写入管道
ssize_t sst =write(fd,buf,n);if(sst ==-1){perror("write");close(fd);fclose(fp);exit(1);}
bytes+=n;}// 检查是否到达文件末尾或出错if(n <sizeof(buf)){if(feof(fp)){break;// 文件读取完毕}if(ferror(fp)){perror("fread");fclose(fp);close(fd);exit(1);}}}
std::cout<<"传递字节数"<<bytes<<std::endl;fclose(fp);close(fd);unlink(pipePath);return0;}
客户端
client.cc
//客户端接受服务端的文件/**
* 客户端接受服务端的文件
* 打开命名管道,开始读取服务端传递给客户端的信息
*/#include"common.hpp"intmain(){//打开管道,读取管道数据int fd =open(pipePath,O_RDONLY);if(fd ==-1){perror("open");exit(1);}//将读取的数据打印到显示屏char buf[SIZE];
ssize_t n =read(fd,buf,sizeof(buf)-1);if(n ==-1){perror("read");exit(1);}
std::cout<<"文件内容:\n"<<buf<<"读取字节数:"<<n<<std::endl;close(fd);return0;}
运行结果:
此时服务端是写端,客户端是读端,实现的是下载服务;当服务端是读端,客户端是写端是,实现的就是上传服务,搞两条管道就能实现模拟简单的数据双向传输服务。
注意:创建管道文件后,无论先启动读端还是启动写端,都要堵塞式的等待另一方进行交互。
往期Linux文章:Linux专栏
5.总结
作为匿名管道的兄弟,命名管道具备匿名管道的大部分特性,使用方法也基本一致,不过二者在创建和打开方式上各有不同:匿名管道简单,但只能用于具有血缘关系进程间通信,命名管道虽麻烦些,但适用于所有进程间通信场景。
版权归原作者 Yui_ 所有, 如有侵权,请联系我们删除。