命名管道通信和共享内存通信
前言
进程通信是指在进程间传输数据(交换信息)。 进程通信根据交换信息量的多少和效率的高低,分为低级通信(只能传递状态和整数值)和高级通信(提高信号通信的效率,传递大量数据,减轻程序编制的复杂度)。其中高级进程通信分为三种方式:共享内存模式、消息传递模式、共享文件模式。
共享内存通讯是指在计算机的内存中开辟一块空间。使这个空间能够被两个进程看见,于是形成通讯。因为实在内存中操作,所以操作系统(OS)不需要和磁盘交换数据,于是效率会更高。
一、命名管道通讯
1. 简介
命名管道就是共享文件模式,也是本篇博客的中心。命名管道的通讯在于建立通讯用的文件,然后两个进程分别以读和写的方式打开文件。于是乎就能让一个进程读到另一个进程写的东西,从而形成通信。
有命名管道通信就会有匿名管道通信,但是匿名管道通信只能用于有血缘关系的进程。为了让服务器和客户端进行交互,我们需要建立命名通信管道。于是命名管道文件诞生了。
2. 实现原理
实现命名管道的通信的主要使用函数包括mkfifo、write、read、open。
2.1. mkfifo
这是我们第一次学习mkfifo函数,下面让我来介绍一下它的功能和使用方法。
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
mkfifo用来建立管道文件,它的形式如上所示。其中pathname表示的是文件所在的地址,如果只填写文件名称那么就会在当前文件夹创建文件。mode表示文件的操作权限,会改变文件是否能够读写,一般我们会将其设置为0666。
如果创建管道文件成功那么会返回0,如果出现错误会返回-1(错误码会被存储起来到errno中)。
需要注意的是该函数只能用来建立管道文件,不建议用这个函数建立其他性质的文件。
2.2. 其他文件操作函数
对于其他文件操作函数。open函数用来打开文件、write函数用于client进程向管道内写入数据、read用于server进程读取需要处理的内容。这些函数的使用方法可以参考之前的博客,链接如下:
访问文件和文件重定向_访问代码本地文件,重定向-CSDN博客文章浏览阅读1k次,点赞20次,收藏12次。主讲linux系统中打开文件的接口和对程序的输入输出重定向。_访问代码本地文件,重定向https://blog.csdn.net/2302_81342533/article/details/145203057
2.3. 实现逻辑
2.3.1. 共同部分
首先我们能够建立一个类用来封装我们所需要的功能:创建管道文件,删除管道文件、从管道文件中读取数据、向管道文件中写入数据等操作。在描述中,需要文件的地址和名称。建立文件的方式默认为0666,一次最多读取到的字节数默认为1023个。
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define FIFO_FILE "fifo"
#define EXIT_ERROR(error, error_mode) \
do{ \
std::cerr << error; \
exit(error_mode); \
}while(0)
class MyFifo
{
public:
// 初始化管道名称
MyFifo(const std::string&& name = FIFO_FILE)
:_filename(name)
{
_path = "./" + name;
_fifoname = "fifoname=" + _filename;
}
// 展示管道文件名称
bool SayFifoName()
{
std::cout << _fifoname << std::endl;
return true;
}
// 创建对应管道文件
bool Create()
{
umask(0);
int n = mkfifo(_path.c_str(), 0666);
if(n != 0)
{
EXIT_ERROR("mkdir_fifo_error", 1);
}
return true;
}
// 以读得到方式打开管道文件,并读取数据
int Read()
{
int readline = 0;
// 打开管道文件
int fd = open(_path.c_str(), O_RDONLY);
if(fd < 0)
{
EXIT_ERROR("open_fifo_error", 2);
}
// 读取数据
char buffer[1024];
while(true)
{
int n = read(fd, buffer, sizeof(buffer) - 1);
if(n > 0)
{
buffer[n] = 0;
readline++;
std::cout << "client say#" << buffer << std::endl;
}
else if(n < 0)
{
EXIT_ERROR("read_fifo_error", 3);
}
else
{
std::cout << "read is over\n";
break;
}
}
close(fd);
return readline;
}
// 向管道内写入数据
int Write()
{
int write_line = 0;
// 打开文件
int fd = open(_path.c_str(), O_WRONLY);
if(fd < 0)
{
EXIT_ERROR("open_fifo_error\n", 4);
}
// 写入数据
while(true)
{
std::cout << "Pleause Enter#";
std::string message;
getline(std::cin, message, '\n');
if(message == "close write")
{
std::cout << message << std::endl;
break;
}
int n = write(fd, message.c_str(), message.size());
if(n < 0)
{
EXIT_ERROR("write to fifo error\n", 5);
}
else if(n != message.size())
{
std::cerr << "write to fifo had lose byte\n";
}
else
{
write_line++;
}
}
close(fd);
return write_line;
}
bool Remake()
{
int n = unlink(_filename.c_str());
if(n == 0)
{
std::cout << "remove fifo success\n";
return true;
}
else
{
std::cout << "remove fifo failed\n";
return false;
}
}
~MyFifo()
{}
private:
std::string _path; // 管道文件地址
std::string _filename; // 文件名称
std::string _fifoname; // 管道名称
};
2.3.2. server(服务器部分)
服务器部分用来接收管道信息。
#include "comm.hpp"
int main()
{
MyFifo fifo;
// 创建管道
fifo.Create();
// 读取管道中的数据
fifo.Read();
// 删除管道文件
fifo.Remake();
return 0;
}
2.3.3. client(客户端部分)
客户端部分用来发送信息。
#include "comm.hpp"
int main()
{
MyFifo write_fifo;
// 向管道中写入数据
write_fifo.Write();
return 0;
}
3. 实际使用
因为有管道的存在,所以我们server端能够得到client端传来的数据。但我们现在没有对应的任务能够给server执行。所以我们就打印出来client传给server的数据。比如:
在原有基础上我增加了一些功能,例如用unlink删除管道文件和用“close write”关闭写端。通过增加这样的功能我们也能使客户端执行更负责的操作。但是现在的交流仍然是单向的,如果是双向的话也许我们就需要两个管道才行。
二、 共享内存通信
1. 简介
共享内存通信,就是指在内存中的两个进程都能够看到同一块区域,于是两个进程就能够进行交流了。这通常被用于两个没有血缘关系的进程,而且因为都是在内存中操作所以效率很高。缺点就是为了实现内存共享,需要约定一个方法使进程都能够找到同一块内存。只要能找到了共享内存就实现了。
2. 实现原理
在实现通信之前,我们需要将内存共享。那么我们将其分为两步,一步是创建,另一步是分享。创建内存需要用到两个函数ftok和shmget。接下来将介绍他们的形式和用法:
2.1. shmget
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
函数的作用是在内存中开辟size大小的内存,这就相当于玩游戏时创建房间一样。key代表房间的密码,size表示房间的大小(单位byte),shmflg表示创建房间的方法。其中shmflg有两种常用的命令——IPC_CREAT 、IPC_EXCL,前者表示创建,后者表示唯一。如果需要使用IPC_EXCL就需要和IPC_CREAT一起。
返回值如果>0则表示创建成功。如果为-1则表示创建失败,失败内容会被记录在errno中。
2.2. ftok
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
这个函数就是用于构建shmget中的key中默认的方法。pathname表示地址名,proj_id表示打开文件的文件方式。
如果成功将会返回>0。反之就会返回-1,错误码会被存到errno中。可以使用perror打印。
2.3. 实现逻辑
实现逻辑和命名通讯部分的实现逻辑相似,构建服务端和客户端,描述共享内存。将方法表示为创建内存和链接内存。
实现代码暂无。
作者结语
本节的内容就先到这里,下一节将会是进程池的实现。学习完进程池之后,也能将这一节的两个通讯方式进行池化。