SystemV 消息队列
消息队列
- 消息队列 是 Linux 提供的一种 进程间通信机制(IPC),用来进行不同进程之间的数据收发、异步传输
- 消息队列本质上是内核维护的一个消息链表,每个列表中,存放的都是 "有类型数据块"
- 和管道一样,消息队列也有着自己的缺点:
- "有类型数据块" 自身的大小是有限的,取决于对
struct mymsg的设计 - 消息队列中包含的"有类型数据块"的数量也是有限制的
- 内核中允许的最大消息队列数量也是有上限的
- "有类型数据块" 自身的大小是有限的,取决于对
- 消息队列的命令行操作
ipcs -q表示查看所有消息队列ipcrm -q msqid表示删除 id 为msqid的消息队列
系统调用接口
总的来说,SystemV 提供的三种 IPC 资源的使用方式、思路都是相似的。
msgget
cpp
int msgget(key_t key, int msgflg);
这里的 key 可以有 ftok 获得,作为一个消息队列的名字;msgflg 标志位的使用和文件以及信号量集的一致。
返回值是 msgmid,作为一个消息队列的唯一标识符
msgctl
cpp
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msgctl 用来完成对消息队列的控制操作。msqid,是消息队列的唯一标识符;cmd,表示具体要进行的操作,比如删除消息队列就是 IPC_RMID,此时第三个参数可以传空指针
msgsnd
cpp
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msgsnd,用来向一个消息队列中发送新消息,其中 msgp 需要我们提供一个 struct mymsg 结构体地址,这个结构体应具有以下内容:
cpp
struct mymsg {
long mtype; /* Message type. */
char mtext[1]; /* Message text. */
}
其中,mtype 用来帮助接受方区分消息;mtext 为消息正文,大小可以自己更改,设置大小即为"有类型数据块"的大小上限
msgsz 不是我们整个结构体的大小,而是我们定义的"有类型数据块"中 mtext 的大小
msgflg 可以用来区分是否阻塞等待。如果等于零,阻塞等待;否则即刻返回
msgrcv
c++
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int
msgflg);
msgrcv 表示从某个消息队列中接收消息,参数的意义与 msgsnd 相同,不过作为输出型参数。msgtyp 就是前面在 struct mymsg 中定义的 mtype
msgrcv 和 msgsnd 返回值是 -1 时,都表示出错返回
责任链模式
责任链模式介绍
- 责任链模式是一种行为设计模式
- 把对请求的若干个处理分别放到若干个节点中,每个节点都可以决定作出处理,或者不作出处理
- 使得请求者和处理者的耦合性降低,能够更加灵活的对请求进行处理
责任链模式示例
这里我们把 SystemV 消息队列和责任链模式结合起来,在基本CS结构的基础上,添加以下配置
- 在 Client 消息加上附加提示(例如 pid)
- 将一个 Client 的消息持久化到文件
- 将过大的文件压缩
这里只是为了展示思路,具体设计肯定不会通过这么粗暴的方式
cpp
#include <filesystem>
#include <memory>
#include <unistd.h>
#include <iostream>
#include <format>
#include <filesystem>
#include <fstream>
#define MAX_LINE 2
class HandlerText{
public:
virtual void Execute(const std::string& raw_string) = 0;
void Enable(){
_enable = true;
}
void Disable(){
_enable = false;
}
void SetNextHandler(std::shared_ptr<HandlerText> next_hdl){
_next_handler = next_hdl;
}
protected:
std::shared_ptr<HandlerText> _next_handler;
bool _enable; //当前节点是否允许处理
};
class HandlerTextFormat: public HandlerText{
public:
void Execute(const std::string& raw_string) override{
int pid = getpid();
std::string ret = std::format("[{}]: {}", pid, raw_string);
std::cout << ret << std::endl;
if(_next_handler){
_next_handler->Execuate(raw_string);
}
else{
std::cout << "责任链在TextFormat完毕" << std::endl;
}
}
};
class HandlerTextSave: public HandlerText{
public:
void Execute(const std::string& raw_string) override{
namespace fs = std::filesystem;
try{
fs::path p = "./tmp";
if(!fs::exists(p)){
fs::create_directories(p);
}
chdir("./tmp");
int pid = getpid();
std::ofstream out(std::to_string(pid), std::ios::app);
out << raw_string << '\n';
out.close();
chdir("./..");
}
catch(std::exception& e){
std::cout << e.what() << std::endl;
}
if(_next_handler){
_next_handler->Execuate(raw_string);
}
else{
std::cout << "责任链在HandlerTextSave完毕" << std::endl;
}
}
};
class HandlerTextBackupFile: public HandlerText{
public:
void Execute(const std::string& raw_string) override{
try{
chdir("./tmp");
int pid = getpid();
std::string filename = std::to_string(pid);
std::ifstream in(filename);
if(!in.is_open()){
std::cout << "open file fail: " << std::endl;;
return;
}
int cnt = 0;
std::string buff;
while(getline(in, buff)){ cnt += 1;}
in.close();
if(cnt >= MAX_LINE){
std::string tgz = std::to_string(pid) + ".tgz";
std::string raw_file = std::to_string(pid);
std::cout << std::format("tgz: {}, raw: {}", tgz, raw_file) << std::endl;
execlp("tar", "tar", "cvf", tgz.c_str(), raw_file.c_str(), nullptr);
std::cout << "tar log fail" << std::endl;
}
else{
std::cout << "lines in backup file: " << cnt << std::endl;
std::cout << "(无需压缩)" << std::endl;
std::cout << "(责任链完整结束)" << std::endl;
chdir("./..");
}
}
catch(const std::exception& e){
std::cout << e.what() << std::endl;
}
}
};
class ChainEntry{
public:
ChainEntry(const std::string& raw_file){
std::shared_ptr<HandlerTextFormat> formatter = std::make_shared<HandlerTextFormat>();
std::shared_ptr<HandlerTextSave> saver = std::make_shared<HandlerTextSave>();
std::shared_ptr<HandlerTextBackupFile> backer = std::make_shared<HandlerTextBackupFile>();
formatter->Enable();
saver->Enable();
backer->Enable();
formatter->SetNextHandler(saver);
saver->SetNextHandler(backer);
formatter->Execute(raw_file);
}
private:
};
如下是 CS 消息队列收发的基本结构
cpp
#include <sys/msg.h>
#include <iostream>
#include <sys/ipc.h>
#include <string>
#include <string.h>
#include "ChainOfResponsibility.hpp"
#define proj_id 0x77
#define filepath "/tmp"
#define SIZE 1024
#define CLIENT 1
#define BUILD_FLG (IPC_CREAT | IPC_EXCL | 0666)
#define GET_FLG 0
struct mymsg {
long mtype; /* Message type. */
char mtext[SIZE]; /* Message text. */
};
class MsgQueue{
public:
void GetKey(){
_k = ::ftok(filepath, proj_id);
if(_k == -1){
perror("GetKey fail: ");
return;
}
}
void GetMsgQueue(int flag){
_msqid = ::msgget(_k, flag);
if(_msqid < 0){
perror("GetMsgQueue fail: ");
return;
}
}
void Destroy(){
int ret = ::msgctl(_msqid, IPC_RMID, nullptr);
(void)ret;
}
void Send(const std::string& text, long type){
struct mymsg mm;
mm.mtype = type;
memset(mm.mtext, 0, text.size());
strncpy(mm.mtext, text.c_str(), text.size());
int ret = ::msgsnd(_msqid, &mm, sizeof(mm.mtext), 0);
if(ret == -1){
perror("msgsnd fail: ");
}
}
std::string& Recv(std::string& msg, long type){
struct mymsg mm;
mm.mtype = type;
int ret = ::msgrcv(_msqid, &mm, SIZE, type, 0);
if(ret == -1){
perror("msgrcv fail");
return msg;
}
mm.mtext[ret] = 0;
msg = mm.mtext;
ChainEntry ce(msg);
return msg;
}
private:
key_t _k;
int _msqid;
};
class Server: public MsgQueue{
public:
Server(int flag = GET_FLG){
GetKey();
GetMsgQueue(flag);
}
~Server(){
Destroy();
}
private:
};