一、命名管道介绍
管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
命名管道是一种特殊类型的文件
我们可以直接使用下面的命令去创建命名管道
mkfifo myfifo
它的文件类型是以p开头的,也就是命名管道
其实这个myfifo命名管道文件,它在磁盘中并没有数据,它更多的只是一种符号
如下所示
当我们左侧将内容输入到管道的时候,左侧会先进入阻塞。直到右侧使用cat拿到数据以后,左侧才会结束
首先这两个指令是两个进程,这两个进程是毫无关系,但是他们是可以利用这个命名管道进行通信的
也可以看到,在写入的过程中,命名管道的大小一直是0
我们也知道,如果两个不同的进程,打开同一个文件的时候,在内核中,操作系统也是打开一个文件,还是我们前面的这一套
所以:
进程间通信的前提:先让不同的进程看到同一份资源
现在我们的这个管道文件其实就是一个内存级文件,它是不需要刷盘的!
那么我们怎么保证我们两个进程打开的是同一个文件呢?
只要同路径下的同一个文件名即可–>路径 + 文件名具有唯一性
二、编码
1.mkfifo
如下所示
上面的函数可以去创建一个管道文件,第一个参数是路径,第二个是管道的权限是什么,如果成功是0,否则返回-1
如下代码所示
2.unlink
如果我们想要删除一个文件我们可以使用unlink接口
如果成功,返回,如果失败返回-1
如下代码所示
我们可以先编译运行一下,我们会发现会先报错说管道文件已经存在,这是正常的,因为我们之前的并没有删除掉
当我们将原来的管道删除以后,重新运行,就可以看到了
3.一个简单的例子
#pragma once
#include <iostream>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <string>
#define FIFO_FILE "./myfifo"
#define MODE 0664
enum
{
FIFO_CREATE_ERR = 1,
FIFO_DELETE_ERR,
FIFO_OPEN_ERR
};
using namespace std;
#include "comm.hpp"
int main()
{
int fd = open(FIFO_FILE,O_WRONLY);
if(fd < 0)
{
perror("clinet open file fail...");
exit(FIFO_OPEN_ERR);
}
cout << "client open success" <<endl;
string line;
while(1)
{
cout << "Please Enter@ ";
getline(cin,line);
write(fd, line.c_str(), line.size());
}
close(fd);
cout << "客户端关闭啦!..." << endl;
return 0;
}
#include "comm.hpp"
using namespace std;
int main()
{
//创建一个管道
int n = mkfifo(FIFO_FILE, MODE);
if(n == -1)
{
perror("mkfifo");
exit(FIFO_CREATE_ERR);
}
//开始通信
int fd = open(FIFO_FILE,O_RDONLY);
if(fd < 0)
{
perror("open:");
exit(FIFO_OPEN_ERR);
}
cout <<"server open file sueccess" <<endl;
while(1)
{
char buffer[1024] = {0};
ssize_t x = read(fd, buffer, sizeof(buffer) - 1);
if(x > 0)
{
buffer[x] = 0;
cout << "服务器收到客户端发送的消息:"<< buffer <<endl;
}
else if(x == 0)
{
cout << "客户端关闭,服务器关闭" << endl;
break;
}
else
{
break;
}
}
close(fd);
//关闭信道
int m = unlink(FIFO_FILE);
if(m == -1)
{
perror("unlink:");
exit(FIFO_DELETE_ERR);
}
return 0;
}
如上代码所示,最终的运行结果为
当我们先打开服务端的时候,我们发现服务端创建了管道,但是并没有打印出server open file success,这说明open处阻塞了
当我们一旦打开了客户端,服务端和客户端几乎同时打开成功。这说明,命名管道文件需要写端和读端都打开的时候才可以,否则只打开其中一个会进入阻塞。我们可以理解为这是为了防止只打开读端,不打开写端的管道会出现读入0的情况。只打开写端,不打开读端会杀掉写端的进程
然后就可以通信了
最终我们我们关闭客户端的同时,由于关闭了写端,但是读端没有关闭,就会读入0,最终服务端的代码逻辑会检测到这个0,从而结束进程
同样的,如果我们先关闭了服务端,那么就会杀掉客户端的进程
4.修改
我们现在对上面的代码进行一下小小的修改,使代码变得更加优雅
如下所示,我们让创建管道和销毁管道变成一个类,这样的话,就不需要我们自己去手动控制了
#pragma once
#include <iostream>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <string>
#define FIFO_FILE "./myfifo"
#define MODE 0664
enum
{
FIFO_CREATE_ERR = 1,
FIFO_DELETE_ERR,
FIFO_OPEN_ERR
};
class Init
{
public:
Init()
{
//创建一个管道
int n = mkfifo(FIFO_FILE, MODE);
if(n == -1)
{
perror("mkfifo");
exit(FIFO_CREATE_ERR);
}
}
~Init()
{
//关闭信道
int m = unlink(FIFO_FILE);
if(m == -1)
{
perror("unlink:");
exit(FIFO_DELETE_ERR);
}
}
};
客户端还是不变的
using namespace std;
#include "comm.hpp"
int main()
{
int fd = open(FIFO_FILE,O_WRONLY);
if(fd < 0)
{
perror("clinet open file fail...");
exit(FIFO_OPEN_ERR);
}
cout << "client open success" <<endl;
string line;
while(1)
{
cout << "Please Enter@ ";
getline(cin,line);
write(fd, line.c_str(), line.size());
}
close(fd);
cout << "客户端关闭啦!..." << endl;
return 0;
}
下面是服务端,就可以通过一个变量的定义来控制前面的创建管道和关闭管道了
#include "comm.hpp"
using namespace std;
int main()
{
Init in;
//开始通信
int fd = open(FIFO_FILE,O_RDONLY);
if(fd < 0)
{
perror("open:");
exit(FIFO_OPEN_ERR);
}
cout <<"server open file sueccess" <<endl;
while(1)
{
char buffer[1024] = {0};
ssize_t x = read(fd, buffer, sizeof(buffer) - 1);
if(x > 0)
{
buffer[x] = 0;
cout << "服务器收到客户端发送的消息:"<< buffer <<endl;
}
else if(x == 0)
{
cout << "客户端关闭,服务器关闭" << endl;
break;
}
else
{
break;
}
}
close(fd);
return 0;
}