Linux 命名管道
目录
一、前言
二、命名管道原理
三、优化代码
一、前言
我们之前讲的管道只能有血缘关系的进程进行通信。这个管道是匿名管道。
这节博客,我们将为大家讲解命名管道。
二、命名管道原理
什么是命名管
我们可以直接使用下面的命令去创建命名管道
mkfifo myfifo
它可以在自己当前目录下创建一个管道。
我们能看到它的权限以p开头,所以这是个管道文件。命名管道文件,它在磁盘中并没有数据,它更多的只是一种符号。因为管道文件,不需要把数据输入到磁盘,是内存级文件,内存大小一直是0。我们来测试一下。
我们输入数据到管道文件中,我们看到处于阻塞状态,因为我们还没有读取数据。
我们创建了两个窗口,我们看到当我cat读取数据时,写端也不堵塞了。
我们写入shell脚本,写端在不断写入管道中,读端不断输出。
这是两个毫不相关的进程,没有血缘关系,但他们能进行通信,因为存在命名管道。
我们之前写到过,进程通信的本质是让两个不同的进程看到同一份资源。
那我们怎么知道两个进程看到的是同一份资源呢?也就是说打开同一个文件呢?
当我们在同一路径下看到同一个文件名,则这是同一份资源(同一个文件)
因为路径+文件名具有唯一性。
我们接下来用代码测试一下我们的原理。
三、编码测试
上面的函数可以去创建一个管道文件,第一个参数是路径,第二个是管道的权限是什么,如果成功是0,否则返回-1。
这样我们就创建了一个管道
当我们要删除一个管道的时候可以用unlink接口,我们在讲解软链接的时候为大家提到过。
我们写一个具体的代码介绍该原理。
做一个服务端和用户端,用户端用来写入数据,服务端用来读取数据。
我们把所有的头文件和宏定义全部放到我们自己创建的头文件中。
comm.hpp
#include <iostream>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <cstdlib>
#include <cerrno>
#include <cstdio>
#include <fcntl.h>
#include <string>
#define FIFO_FILE "./myfifo"
#define MODE 0666
enum{
CREAT_FIFO_ERR = 1,
OPEN_ERR = 2,
DELETE_ERR = 3,
};
servers.cpp
#include "comm.hpp"
using namespace std;
int main()
{
//创建管道
int n=mkfifo(FIFO_FILE,MODE);
if(n==-1)
{
perror("mkfifo");
exit(CREAT_FIFO_ERR);
}
//打开管道文件
int fd = open(FIFO_FILE,O_RDONLY);
if(fd<0)
{
perror("open");
exit(OPEN_ERR);
}
cout <<"server open file sueccess" <<endl;
//读取文件
while(1)
{
char buffer[1024];
int n = read(fd,buffer,sizeof(buffer));
if(n > 0)
{
buffer[n]=0;
cout<<"客户端接收到信息:"<<buffer<<endl;
}
else if(n == 0)
{
cout<<"客户端读完所有信息"<<endl;
break;
}
else break;
}
//收尾工作
close(fd);
int m = unlink(FIFO_FILE);
if(m==-1)
{
perror("unlink");
exit(DELETE_ERR);
}
return 0;
}
client.cc
#include "comm.hpp"
using namespace std;
int main()
{
int fd = open(FIFO_FILE,O_CREAT|O_WRONLY);
if(fd < 0)
{
perror("open");
exit(OPEN_ERR);
}
cout<<"open file success!"<<endl;
string line;
while(1)
{
cout<<"please enter @:";
getline(cin,line);
write(fd,line.c_str(),line.size());
}
close(fd);
return 0;
}
运行结果如下:
当只运行读端会阻塞,等待输入
当我们运行客户端写入后,同时出现打开文件成功。
接下来我们从客户端往里面写入数据,我们能看到一一对应。
最终我们我们关闭客户端的同时,由于关闭了写端,但是读端没有关闭,就会读入0,最终服务端的代码逻辑会检测到这个0,从而结束进程。
同样的,如果我们先关闭了服务端,那么就会杀掉客户端的进程
三、优化代码
我们创建一个类,把管道初始化和清理放到类里面。
定一个Init对象,会自己调用其构造函数和析构函数。
客户端代码不变。