Linux SystemV 消息队列 + 责任链模式:实现客户端消息处理流水线

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

msgrcvmsgsnd 返回值是 -1 时,都表示出错返回

责任链模式

责任链模式介绍

  1. 责任链模式是一种行为设计模式
  2. 把对请求的若干个处理分别放到若干个节点中,每个节点都可以决定作出处理,或者不作出处理
  3. 使得请求者和处理者的耦合性降低,能够更加灵活的对请求进行处理

责任链模式示例

这里我们把 SystemV 消息队列和责任链模式结合起来,在基本CS结构的基础上,添加以下配置

  1. 在 Client 消息加上附加提示(例如 pid)
  2. 将一个 Client 的消息持久化到文件
  3. 将过大的文件压缩

这里只是为了展示思路,具体设计肯定不会通过这么粗暴的方式

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:
};
相关推荐
_codemonster1 小时前
系统分析师系列目录
java·网络·数据库
|_⊙1 小时前
Linux 深入理解文件(Ext2文件系统:下)
linux·服务器·数据库
cui_ruicheng1 小时前
Linux网络编程(一):网络基础与协议概念
linux·网络·操作系统
dualven_in_csdn1 小时前
【网络】ip转发
linux·服务器·网络
袁小皮皮不皮1 小时前
HCIP-BFD 学习笔记
运维·服务器·网络·笔记·网络协议·学习·智能路由器
xlq223221 小时前
54.序列化和反序列化
服务器·网络·网络协议·tcp/ip
恋奴娇1 小时前
ubuntu 25 gnome-screenshot 录屏启动失败 原因pipewire服务未启动
linux·运维·ubuntu
智者知已应修善业2 小时前
51单片机4按键控制共阳LED霓虹灯切换1整体闪烁2流水下3流水上4间隔闪烁】2023-10-27
c++·经验分享·笔记·算法·51单片机
JiaWen技术圈2 小时前
后端无状态鉴权 JWT 或 OAuth2 及其区别与实现
服务器·网络·网络协议