文章目录
- 前言:
- 命名管道的原理:
- 代码编写:
前言:
在前面的一文,我们主要介绍了关于进程间的通信是如何实现的,以及引入了进程间通信的前提——“要让两个进程在OS中看到同一份资源”,而对于父子进程来说,子进程是通过继承父进程的struct files_struct
来看到同一个文件的缓冲区,因此那个对应的缓冲区就是匿名管道,它们都看到了缓冲区这么一个同一份资源。那现在如何使得两个独立的进程实现通信?
命名管道的原理:
我们先来回顾一下创建进程再从磁盘打开文件的过程,一下在之前讲解文件标识符的时候都有涉及到,如果忘记了可以看看我之前写过的博客。
现在我们创建进程B来讲讲俩进程要是想实现通信该怎么做:
在创建进程B的时候,由于进程具有独立性,进程B并不会像子进程那样继承进程A的struct files_struct
,而是会自己创建一个属于自己的struct files_struct
。
那还有必要再创建一个struct file
和对应的缓冲区吗?
——有必要创建struct file
但没必要创建新的缓冲区,因为两个进程实现了不一样的操作,我们想让进程B写入数据,而进程A要读数据。而对于缓冲区,着我们就没必要创建了,因为针对的是同一个文件,操作系统不做浪费时间和浪费空间的事情。
我们在处理匿名管道的时候我们并不会去关心匿名管道是不是会刷新到缓冲区,匿名管道是一种内存级别的通信机制,它允许在同一台计算机上的不同进程间进行高效的数据传输。这种通信方式强调数据的快速传递和共享,因此它直接在内存中创建缓冲区来存储和传输数据。磁盘的访问速度相对较慢,如果匿名管道的数据需要频繁地刷新到磁盘,那么将严重影响数据传输的效率。而内存级别的通信则能够显著提高数据传输的速度,因为它避免了磁盘访问的延迟。
而命名管道却有这个困扰,这是因为命名管道中是会有一个具体的路径名的,而匿名管道并没有文件系统的路径文件名,因此不会与磁盘文件有关联。但是其实命名管道其实也不会刷新至缓冲区,而是会出现在磁盘中然后是会以g开头的文件标记,这就能保证不会刷新到缓冲区。
输入指令:mkfifo myfifo
那么就能标记这个是个命名管道,以g开头的文件,所以不会刷新到缓冲区。
综上所述,我们这个磁盘文件所对应的内核级的缓冲区就是——命名管道
下面我们来运用一下命名管道:
代码编写:
现在我们想要实现两个独立的进程,一个是服务端(server)进程A,一个是客户端(client)B,服务端A负责接收数据,客户端B负责写数据。而对于命名管道的创建我们需要让服务端来进行管理,现在我们来介绍介绍关于命名管道的一些接口:
int mkfifo(const char *pathname, mode_t mode);
其中参数pathname表示的是你给命名管道起的名字,而参数mode表示你该命名管道的权限。int unlink(const char *pathname);
该接口时实现管道的释放的。
所以我们就可以通过封装和实现接口,编写下面一个简单的小项目,命名管道的使用。
还有就是我们一定要保证当服务端(server)读到0的时候,就代表这客户端(client)退出了,服务端(server)同时也要进行退出。当先退出server,根据管道的五大特性中,若是读端关闭,则写端也会自动关闭!
一定是要先打开 服务端(server)再打开客户端(client),不然client就会找不到管道文件,就直接返回了!
namedpipe.hpp:
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cerrno>
#include <fcntl.h>
const std::string pipe_path = "./myfifo";
#define DEFAULT_FD -1
#define Server 1
#define Client 2
#define READ O_RDONLY
#define WRITE O_WRONLY
class NamedPipe
{
private:
void OpenNamedPipe(int mode)
{
_fd = open(pipe_path.c_str(), mode);
if (_fd < 0)
{
perror("open");
exit(1);
}
std::cout << "NamedPipe 打开成功!" << std::endl;
}
public:
NamedPipe(std::string path, int user)
: _path(path), _user(user), _fd(DEFAULT_FD)
{
if (_user == Server)
{
int n = mkfifo(pipe_path.c_str(), 0666);
if (n < 0)
{
perror("mkfifo");
exit(1);
}
std::cout << "命名管道creat完毕!" << std::endl;
}
}
void OpenNamedPipeByRead()
{
OpenNamedPipe(READ);
}
void OpenNamedPipeByWrite()
{
OpenNamedPipe(WRITE);
}
int WriteInPipe()
{
std::string message;
std::cout << "Pleast Input> ";
std::getline(std::cin, message);
int n = write(_fd, message.c_str(), message.size());
if (n < 0)
{
perror("write");
exit(1);
}
return n;
}
int ReadFromPipe(std::string *out)
{
char file_buffer[1024];
int n = read(_fd, file_buffer, sizeof(file_buffer));
if (n > 0)
{
file_buffer[n] = 0;
*out = file_buffer;
}
return n;
}
~NamedPipe()
{
if (_user == Server)
{
int n = unlink(pipe_path.c_str());
if (n < 0)
{
perror("mkfifo");
exit(1);
}
std::cout << "命名管道free完毕" << std::endl;
}
}
private:
std::string _path;
int _user;
int _fd;
};
client.cpp:
#include "namedpipe.hpp"
int main()
{
NamedPipe mypipe(pipe_path, Client);
mypipe.OpenNamedPipeByWrite();
std::cout << "Hi I am Client" << std::endl;
while (true)
{
mypipe.WriteInPipe();
}
return 0;
}
server.cpp:
#include "namedpipe.hpp"
// 当前为服务端,负责创建命名管道和释放管道
int main()
{
NamedPipe mypipe(pipe_path, Server);
mypipe.OpenNamedPipeByRead();
std::cout << "Hi I am Server" << std::endl;
while (true)
{
std::string out;
int n = mypipe.ReadFromPipe(&out);
if (n > 0)
{
std::cout << "Client say> " << out << std::endl;
}
else if (n == 0)
{
std::cout << "client退出, my turn!" << std::endl;
break;
}
else
{
perror("read");
exit(1);
}
}
return 0;
}