
◆ 博主名称: 小此方-CSDN博客 大家好,欢迎来到小此方的博客。
⭐️Linux系列个人专栏: 【主题曲】Linux
⭐️此方的GitHub: github_此方
⭐️ Re系列专栏:我们思考 (Rethink) · 我们重建 (Rebuild) · 我们记录 (Record)
文章目录
概要&序論
Hello,大家好,我是此方。上一篇我们讲了匿名管道,但是事实上匿名管道的局限性实在是太强了------两个通信的进程必须要有血缘关系 (最常见的就是父子关系)。很显然不能够满足我们的实际开发需求对不对。
于是命名管道出现了。------他支持两个毫不相干的进程进行通信。 我们本文想讲的就是它。好,我们废话少说,开始吧。
一、引入命名管道
你是怎么想到命名管道的 ?首先,还是一句最本质的------"进程间通信必须要让两个进程看到同一块资源。 ",这块资源在我们的命名管道任然指的是文件。但是不再是那个内存级管道文件。
通过打开同一个路径下的同一个文件,就像两个进程在同一个显示器上打印/读取信息那样。两个进程就可以看到同一块资源。
而文件有唯一的路径,有名字,于是我们把这个文件一个统一的名字------命名管道。
解答疑问:两个进程打开同一个路径下的同一个文件,这个文件不会被加载两次吗?不会。内核不会做浪费时间和空间并且没有意义的事情。
解答疑问:一个文件在内核中的映射实际上并不包括struct file ,只是inode ops(读写接口) 和缓冲区 。

二、创建命名管道
命名管道文件和普通文件不同,它只进不出------只会被打开,不需要被刷新回到磁盘中。同时它要保证打印不乱序,而普通文件做不到。
2.1命令行创建命名管道并实现最简单的通信
指令mkfifo + 文件名 可以创建一个命名管道。
指令unlink + 文件名 可以删除一个命名管道。




2.2创建命名管道的系统调用
系统调用:mkfifo() 第一个参数是带有路径的管道名称,第二个参数是权限掩码。 返回值依旧是成功返回0,失败返回 -1 并设置错误码。


用两个通信例子讲一讲接口的使用,以及命名管道通信在实际运用中是怎样的。
三、实际案例
3.1实际案例一:打电话
3.1.1代码

Makefile:
bash
ALL : Mr_List Mr_Zhang
Mr_List : Mr_List.cpp
g++ -o $@ $^
Mr_Zhang : Mr_Zhang.cpp
g++ -o $@ $^
.PHONY : clean
clean :
rm -f Mr_List Mr_Zhang fifo
共享文件:必要头文件+两者约定好的信道名称+用来打印错误的代码块。
cpp
#ifndef __COMMON_NAMEDPIPE_
#define __COMMON_NAMEDPIPE_
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <errno.h>
#include <cstdlib>
#include <string>
#include <fcntl.h>
#include <cstring>
#define PIPE_FILE "./fifo"
#define ERR_FIFO(m) do{ \ // 将多条语句打包,确保if-else安全展开
perror(m) ; \
exit(EXIT_FAILURE) ;\ // 标准库宏,向系统报告程序异常退出
} while(false) \
//有些初学者会把行延续符号打错,"/",
//只需要想想它和注释用的斜杠相反就行
#endif
我们约定张三负责说话,李四负责接收。这是张三是代码:
cpp
#include "Common.hpp"
int main()
{
umask(0);
int n = mkfifo(PIPE_FILE, 0666);
if (n == -1)
ERR_FIFO("mkfifo");
int wfd = open(PIPE_FILE, O_WRONLY);
if (wfd == -1)
ERR_FIFO("mkfifo");
else
{
while (true)
{
std::cout<<"#PleaseEnter:";
std::cout.flush();
char buffer [1024] ;
memset(buffer , 0 , sizeof(buffer));
int m = read( 0 ,buffer ,sizeof(buffer));
if(m < 0 )
ERR_FIFO("read");
else
{
int i = write(wfd , buffer ,m);
if(i < 0 )
ERR_FIFO("write");
}
}
}
return 0;
}
这是李四的代码:
cpp
#include "Common.hpp"
int main()
{
int rfd = open(PIPE_FILE ,O_RDONLY );
if(rfd == -1)
ERR_FIFO("open");
while(true){
char buffer[1024] ;
memset(buffer , 0 , sizeof(buffer)) ;
std::cout<< "#PleaseWait ..."<<std::endl;
int n = read(rfd , buffer ,sizeof(buffer)-1) ;
if(n < 0 )
ERR_FIFO("read") ;
else if(n == 0 ){
std:: cout<<"Mr_Zhang Hang Up The Telephone!"<<std::endl;
break;
}
else{
buffer[n] = '\0' ;
std :: cout << "Mr_Zhang say :"<<buffer ;
}
}
return 0 ;
}
3.1.2测试




3.2实际案例二:传输文件内容
这个代码更加简单,上面的代码我们采用了面向过程,接下来这个是面向对象,逻辑类似。
Makefile文件:
bash
ALL : Client Server
Client : Client.cpp
g++ -o $@ $^
Server : Server.cpp
g++ -o $@ $^
.PHONY: clean
clean :
rm -f Client Server
程序的主体部分,包含这个命名管道被创建、销毁、打开(读/写)、读和写的各种必要的方法。
当然还有一种设计方案是去掉Create和Destory方法,把他们放在构造和析构函数中,但是这样就必须把其他的方法额外封装在一个opt类中了(因为创建管道的任务只需要通信双方的其中一人完成。)
cpp
#ifndef __COMMON_NAMEDPIPE_
#define __COMMON_NAMEDPIPE_
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <errno.h>
#include <cstdlib>
#include <string>
#include <fcntl.h>
#include <cstring>
#define PIPE_FILE "./fifo"
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
\
} while (false)
class NamedPipe
{
public:
void Create(std ::string file = PIPE_FILE)
{
umask(0);
int n = mkfifo(file.c_str(), 0666);
if (n == -1)
ERR_EXIT("mkfifo");
}
void Destory(std ::string file = PIPE_FILE)
{
int n = unlink(file.c_str());
if (n == -1)
ERR_EXIT("unlink");
}
void OpenForRead(std ::string file = PIPE_FILE)
{
_pipefd = open(file.c_str(), O_RDONLY);
if (_pipefd == -1)
ERR_EXIT("open_read");
}
void OpenForWrite(std ::string file = PIPE_FILE)
{
_pipefd = open(file.c_str(), O_WRONLY);
if (_pipefd == -1)
ERR_EXIT("open_write");
}
void Read(int outfd)
{
//读取管道内容到外部文件中
char _buffer[1024];
while(read(_pipefd, _buffer, sizeof(_buffer)) != 0)
{
int n = write(outfd , _buffer , sizeof(_buffer));
if(n < 0 )
ERR_EXIT("write") ;
memset(_buffer , 0 , sizeof(_buffer));
}
}
void Write(int infd)
{
char _buffer[1024];
//将文件中的内容读取到缓冲区buffer
while (read(infd, _buffer, sizeof(_buffer)) != 0)
{
int n = write(_pipefd, _buffer, sizeof(_buffer));
if (n < 0)
ERR_EXIT("write");
memset(_buffer , 0 , sizeof(_buffer));
}
}
private:
int _pipefd;
};
#endif
服务端读取Source文件中的内容,然后用命名管道发送给客户端。
cpp
#include "Common.hpp"
int main()
{
//读取管道内容到缓冲区
NamedPipe pipe ;
pipe.OpenForRead(PIPE_FILE);
int outfd = open("destnation.txt",O_WRONLY);
if(outfd == -1)
ERR_EXIT("open");
pipe.Read(outfd) ;
//缓冲区内容写入一个Object.txt文件。
return 0 ;
}
cpp
#include "Common.hpp"
int main()
{
//从文件Source.txt中读取内容到缓冲区
NamedPipe pipe ;
pipe.Create(PIPE_FILE) ;
pipe.OpenForWrite(PIPE_FILE);
int infd = open("Source.txt" , O_RDONLY);
if( infd == -1)
ERR_EXIT("open");
pipe.Write(infd);
pipe.Destory() ;
return 0 ;
}
好的本期内容就到这里,如果对你有帮助,还不要忘记点赞三联支持。我是此方,我们下期再见。bye!