当前位置: 首页 > news >正文

Linux 进程通信——基于责任链模式的消息队列

一.消息队列

1.消息队列细节简述

关于消息队列的原理和接口我们之前已经详细介绍过,在这里我们强调一些在操作系统中消息队列的实际细节。

• 消息队列提供了⼀个从⼀个进程向另外⼀个进程发送有类型块数据的⽅法

• 每个数据块都被认为是有⼀个类型,接收者进程接收的数据块可以有不同的类型值
• 消息队列也有管道⼀样的不⾜,就是每个消息的最⼤⻓度是有上限的(MSGMAX)
• 每个消息队列的总的字节数也是有上限的(MSGMNB),系统上消息队列的总数也有上限

(MSGMNI)的

2.消息队列的通信方式

基于上回讲解的消息队列,我们对它的通信方式进行进一步详解。

由之前讲解的操作系统内核组织不同IPC资源,我们知道对于消息队列,也有自己的msqid_ds,并且可以通过其中的ipc_perm拿到标识消息队列的唯一标志key。

struct msqid_ds {struct ipc_perm msg_perm;struct msg msg_first; / first message on queue,unused */struct msg msg_last; / last message in queue,unused */__kernel_time_t msg_stime; /* last msgsnd time */__kernel_time_t msg_rtime; /* last msgrcv time */__kernel_time_t msg_ctime; /* last change time */unsigned long msg_lcbytes; /* Reuse junk fields for 32 bit */unsigned long msg_lqbytes; /* ditto */unsigned short msg_cbytes; /* current number of bytes on queue */unsigned short msg_qnum; /* number of messages in queue */unsigned short msg_qbytes; /* max number of bytes on queue */__kernel_ipc_pid_t msg_lspid; /* pid of last msgsnd */__kernel_ipc_pid_t msg_lrpid; /* last receive pid */
}struct ipc_perm {key_t __key; /* Key supplied to xxxget(2) */uid_t uid; /* Effective UID of owner */gid_t gid; /* Effective GID of owner */uid_t cuid; /* Effective UID of creator */gid_t cgid; /* Effective GID of creator */unsigned short mode; /* Permissions */unsigned short __seq; /* Sequence number */
}

消息队列在内核中的组织形式如下:

3.接口简述

我们在这里就不再对接口进行详细讲解,除了收发消息之外其余接口都和其他IPC接口类似。

NAMEmsgrcv, msgsnd - System V message queue operationsSYNOPSIS#include <sys/types.h>#include <sys/ipc.h>#include <sys/msg.h>int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);

• msgid : 由 msgget 函数返回的消息队列标识码

• msgp:是⼀个指针,指针指向准备发送的消息
• msgsz:是msgp指向的消息⻓度,这个⻓度不含保存消息类型的那个long int⻓整型
• msgflg:控制着当前消息队列满或到达系统上限时将要发⽣⽣的事情, 0即可
( msgflg=IPC_NOWAIT 表⽰⽰队列满不等待,返回EAGAIN错误 )。

消息主体:struct msgbuf。其中mtype标识发送消息的主体是谁,mtext为消息正文,可以自定义长度。

struct msgbuf {long mtype; /* message type, must be > 0 */char mtext[1]; /* message data */
}

关于msgflg:

msgtype=0返回队列第⼀条信息

msgtype>0返回队列第⼀条类型等于msgtype的消息 
msgtype<0返回队列第⼀条类型⼩于等于msgtype绝对值的消息,并且是满⾜⾜条件的消息类型最⼩的消息
msgflg=IPC_NOWAIT,队列没有可读消息不等待,返回ENOMSG错误。msgflg=MSG_NOERROR,消息⼤⼩超过msgsz时被截断
msgtype>0且msgflg=MSG_EXCEPT,接收类型不等于msgtype的第⼀条消息

二.责任链模式

1.什么时责任链模式

责任链模式(Chain of Responsibility Pattern)是一种行为设计模式,它允许你将请求沿着处理者链进行发送。收到请求后,每个处理者均可对请求进行处理,或将其传递给链上的下一个处理者。

责任链模式的大致框架如下:

  1. 定义一个处理请求的抽象类或接口(Handler),其中包含一个指向下一个处理者的链接(next handler)和一个处理请求的方法。

  2. 具体处理者(Concrete Handler)实现处理请求的方法,并决定是否将请求沿着链传递。

责任链的优点在于可以对同一数据进行链式的处理,并且每个责任链结点的高内聚低耦合性较强,只要通过类似链表的操作增删方法结点即可完成操作。

2.基于责任链的消息队列

1、基类Handler

我们可以仿照上面的类图,先创建出一个基类Handler。我们只需要让后续的具体责任链结点继承基类,实现各自的Excute即可。并且为了让各子类实现链式结构,我们还需要定义一个指针。

class HandlerText
{
public:virtual void Excute(const std::string &text) = 0;void SetNext(std::shared_ptr<HandlerText> next){_next = next;}void Enable(){_enable = true;}void Disable(){_enable = false;}virtual ~HandlerText(){}protected:                              // protected需要被子类继承std::shared_ptr<HandlerText> _next; // 下一个责任链节点bool _enable = true;                // 是否启用该节点
};

2、责任链入口类HandlerEntry

接着就是责任链入口类,这个类主要用于将各个责任链结点(每个节点有自己的方法)构建并链接。现在我们有需求:client向server发送信息,其中信息:1.拼接时间和进程pid信息。2.server收到的内容保存到文件中。3.如果文件内容过大,要对文件内容切片保存在指定路径下的目录,打包保存,命令自定义。

因此很显然我们需要创建三个责任链结点继承基类,并实现各自的功能(Excute)。

class HandlerEntry
{
public:HandlerEntry(){// 构造责任链节点_format = std::make_shared<HandlerTextFormat>();_save = std::make_shared<HandlerTextSaveFile>();_backup = std::make_shared<HandlerTextBackup>();// 设置责任链节点处理顺序_format->SetNext(_save);_save->SetNext(_backup);}void EnableHandler(bool isformat, bool issave, bool isbackup){isformat ? _format->Enable() : _format->Disable();issave   ? _save->Enable() : _save->Disable();isbackup ? _backup->Enable() : _backup->Disable();}void Run(const std::string &text){_format->Excute(text);}~HandlerEntry(){}private:std::shared_ptr<HandlerText> _format;std::shared_ptr<HandlerText> _save;std::shared_ptr<HandlerText> _backup;
};

3、具体的责任链结点类

class HandlerTextFormat : public HandlerText
{
public:void Excute(const std::string &text) override{std::string format_result = text + "\n";if (_enable){// 该节点被开启,对文本进行格式化处理std::stringstream ss;ss << time(nullptr) << "-" << getpid() << "-" << text << "\n";format_result = ss.str();std::cout << "step 1: 格式化消息: " << text << " 结果: " << format_result << std::endl;}if (_next){_next->Excute(format_result); // 将处理结果,表现在text内部,传递给下一个节点}else{std::cout << "到达责任链处理结尾,完成责任链处理" << std::endl;}}
};// 文件的基本信息: 文件路径,文件名称
std::string defaultfilepath = "./tmp/";
std::string defaultfilename = "test.log";// 对文本进行文件保存
class HandlerTextSaveFile : public HandlerText
{
public:HandlerTextSaveFile(const std::string &filepath = defaultfilepath,const std::string &filename = defaultfilename): _filepath(filepath), _filename(filename){// 形成默认的目录名, filesystemif (std::filesystem::exists(_filepath))return;try{std::filesystem::create_directories(_filepath);}catch (std::filesystem::filesystem_error const &e){std::cerr << e.what() << '\n';}}void Excute(const std::string &text) override{if (_enable){// 保存到文件中std::string file = _filepath + _filename;std::ofstream ofs(file, std::ios::app);if (!ofs.is_open()){std::cerr << "open file error: " << file << std::endl;return;}ofs << text;ofs.close();std::cout << "step 2: 保存消息: " << text << " 到文件: " << file << std::endl;}if (_next){_next->Excute(text); // 将处理结果,表现在text内部,传递给下一个节点}else{std::cout << "到达责任链处理结尾,完成责任链处理" << std::endl;}}private:std::string _filepath;std::string _filename;
};const int defaultmaxline = 5; // 最大行数, 这里比较短,方便测试// 对文件内容长度进行检查,如果长度过长,对文件内容进行打包备份
class HandlerTextBackup : public HandlerText
{
public:HandlerTextBackup(const std::string &filepath = defaultfilepath,const std::string &filename = defaultfilename,const int &maxline = defaultmaxline): _filepath(filepath), _filename(filename), _maxline(maxline){}void Excute(const std::string &text) override{if (_enable){// 该节点被开启,对文件进行检查,如果超范围,我们就要切片,并且进行打包备份std::string file = _filepath + _filename;std::cout << "Step 3: 检查文件: " << file << " 大小是否超范围" << std::endl;if (IsOutOfRange(file)){// 如果超了范围,进行切片备份std::cout << "目标文件超范围,进行切片备份" << file << std::endl;Backup(file);}}if (_next){_next->Excute(text); // 将处理结果,表现在text内部,传递给下一个节点}else{std::cout << "到达责任链处理结尾,完成责任链处理" << std::endl;}}private:bool IsOutOfRange(const std::string &file){std::ifstream ifs(file);if (!ifs.is_open()){std::cerr << "open file eeor: " << file << std::endl;return false;}int lines = 0;std::string line;while (std::getline(ifs, line)){lines++;}ifs.close();return lines > _maxline;}void Backup(const std::string &file){// 1589234234std::string suffix = std::to_string(time(nullptr));// "./tmp/test.txt" -> "./tmp/test.txt.1589234234"std::string backup_file = file + "." + suffix; // 备份文件名// 只需要文件名, 不需要路径, test.txt.1589234234std::string src_file = _filename + "." + suffix;// test.txt.1589234234.tgzstd::string tar_file = src_file + ".tgz";// 切片备份并打包pid_t pid = fork();if (pid == 0){// child// 1. 先对文件进行重名,Linux上,对文件名进行重命名是原子的.// "./tmp/test.txt" -> "./tmp/test.txt.1589234234"// 2. 我们想让子进程进行数据备份std::filesystem::rename(file, backup_file);std::cout << "step 4: 备份文件: " << file << " 到文件: " << backup_file << std::endl;// 3. 对备份文件进行打包,打包成为.tgz, 需要使用exec*系统调用// 3.1 对备份文件进行打包.tgz// "./tmp/test.txt" -> "./tmp/test.txt.1589234234" -> "./tmp/test.txt.1589234234.tgz"// 3.1.1 更改工作路径std::filesystem::current_path(_filepath);// 3.1.2 调用tar命令进行打包execlp("tar", "tar", "-czf", tar_file.c_str(), src_file.c_str(), nullptr);exit(1); // exec*系统调用失败,返回1}// parentint status;pid_t rid = waitpid(pid, &status, 0);if (rid > 0){if (WIFEXITED(status) && WEXITSTATUS(status) == 0){// 打包成功,删除源文件std::filesystem::remove(backup_file);std::cout << "step 5: 删除备份文件: " << backup_file << std::endl;}}}private:std::string _filepath;std::string _filename;int _maxline; // 最大行数
};

3.消息队列MsgQueue

消息队列类中,我们只需要定义消息块类型msgbuf和消息队列的创建,初始化,销毁,发送和接收消息方法即可。然后创造客户端类Client和服务器类Server,分别继承MsgQueue并定义创建消息队列端(服务端)和获取消息队列端(客户端)即可。

#ifndef MSGQUEUE_HPP
#define MSGQUEUE_HPP#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>#define PATHNAME "/tmp"
#define PROJID 0x123const int default_fd = -1;
const int defualt_size = 1024;#define GET_MSGQUEUE (IPC_CREAT)
#define CREATE_MSGQUEUE (IPC_CREAT | IPC_EXCL | 0666)class MsgQueue 
{// 有类型数据块struct msgbuf {long mtype;char mtext[defualt_size];};public:MsgQueue(): _msgfd(default_fd) {}// 创建消息队列void Create(int flag){// 获取唯一的键值key_t key = ftok(PATHNAME, PROJID);if(key == -1){std::cerr << "ftok error" << std::endl;exit(1);}// 按照16进制打印keystd::cout << "key: " << std::hex << key << std::endl;// 创建消息队列 --- 暂定// IPC_CREAT:创建消息队列,如果不存在,创建之,否则,获取它// IPC_CREAT | IPC_EXCL(不单独使用): 若消息队列已存在,则返回错误,否则创建之。新的消息队列_msgfd = msgget(key, flag);if(_msgfd == -1){std::cerr << "msgget error" << std::endl;exit(2);}std::cout << "msgqueue created: " << _msgfd << std::endl;}// 发送消息void Send(int type, const std::string& text){struct msgbuf msg;memset(&msg, 0, sizeof(msg));msg.mtype = type;memcpy(msg.mtext, text.c_str(), text.size());// 问题:不能填写成为sizeof(msg)int n = msgsnd(_msgfd, &msg, sizeof(msg.mtext), 0);if(n == -1){std::cerr << "msgsnd error" << std::endl;return;}}// 接受消息,参数设置成输出型参数void Recv(int type, std::string& text){struct msgbuf msg;int n = msgrcv(_msgfd, &msg, sizeof(msg.mtext), type, 0);if(n == -1){std::cerr << "msgrcv error" << std::endl;return;}msg.mtext[n] = '\0';text = msg.mtext;}// 获取消息队列中的属性void GetAttr(){struct msqid_ds outbuffer;int n = msgctl(_msgfd, IPC_STAT, &outbuffer);if(n == -1){std::cerr << "msgctl error" << std::endl;return;}std::cout << "outbuffer.msg_perm.__key: " << std::hex << outbuffer.msg_perm.__key << std::endl;}// 删除消息队列void Destroy(){int n = msgctl(_msgfd, IPC_RMID, 0);if(n == -1){std::cerr << "msgctl error" << std::endl;return;}std::cout << "msgqueue destroyed" << std::endl;}        ~MsgQueue() {}private:int _msgfd;
};// 我们需要定义消息类型
#define MSG_TYPE_CLIENT 1
#define MSG_TYPE_SERVER 2class Server : public MsgQueue
{public:Server(){MsgQueue::Create(CREATE_MSGQUEUE);std::cout << "server create msgqueue done" << std::endl;MsgQueue::GetAttr();}~Server() {MsgQueue::Destroy();}
};class Client : public MsgQueue
{public:Client(){MsgQueue::Create(GET_MSGQUEUE);std::cout << "client get msgqueue done" << std::endl;}~Client() {}
};#endif // MSGQUEUE_HPP

1、客户端Client

客户端只负责获取消息队列,并发送消息。

#include "MsgQueue.hpp"int main() 
{Client client;while(true){// 只让client发送消息std::string input;std::cout << "Please input message: ";std::getline(std::cin, input);client.Send(MSG_TYPE_CLIENT, input);if(input == "exit"){break;}}return 0;
}

2、服务端Server

服务端负责创建消息队列,并接收来自客户端的消息,并引入责任链模式对文本进行处理。

#include "MsgQueue.hpp"
#include "ChainOfResponsibility.hpp"int main()
{std::string text;Server server;HandlerEntry he;he.EnableHandler(true, true, true);while (true){// 如果消息队列为空,阻塞等待server.Recv(MSG_TYPE_CLIENT, text);std::cout << "Received: " << text << std::endl;if(text == "exit"){break;}// 加工处理数据,采用责任链模式he.Run(text);}return 0;
}

效果如下:

http://www.dtcms.com/a/470104.html

相关文章:

  • 手机能做网站吗北京软件开发公司哪家专业
  • 物流网站的建设免费推广平台排行榜
  • iOS一直讲的单元格优化
  • 信阳网站seo用手机怎么做网站
  • 【Canvas技法】 卡通版太阳的三种画法
  • 词根学习笔记 | Agri系列
  • PHP解决跨域请求问题的两种实用方法
  • 语音大模型自监督训练思路
  • 辉芒微单片机FT60E12X,Touch 系列8位单片机MCU简要分析
  • w3c网站代码标准规范自己做的网站怎么接入网页游戏
  • 一篇文章详细解析 IPv4地址
  • 主办单位性质与网站名称不符绍兴专业做网站公司
  • C++ 简介
  • ValueTuple 详解
  • 框架--MyBatis
  • 1.C++基础(上)
  • cursor一些简单的使用心得官方的建议
  • 新能源汽车公司如何落地 ASPICE
  • 网站建设中的注册和登录页面网站开发技术期末考试题
  • react hooks
  • 建立数据分析与决策体系
  • 昂瑞微:全链条创新引领中国“芯”突围
  • Js逆向最新boss直聘__zp_stoken__-某boss逆向
  • Oracle ORA-01653 错误检查以及解决笔记
  • wordpress 做购物网站购买主机可以做网站吗
  • C#合并产品价格对比实战
  • 链表OJ(十六)146. 模拟LRU 缓存 双向链表+哈希
  • 旧物新生:一款回收小程序如何让环保成为举手之劳
  • seo网站优化服务去哪个网站找题目给孩子做
  • MATLAB实现对角加载波束形成算法