【消息队列项目】虚拟机管理实现

一.代码设计与编写

注意,我们这个虚拟机的代码都是放在下面的queue.hpp里面的

虚拟机模块整合了

  1. 交换机数据管理模块
  2. 队列数据管理模块
  3. 绑定信息管理模块
  4. 消息信息管理模块

四个数据管理模块,并依据数据间的关联关系提供联合操作支持。

一、虚拟机类成员

该类包含以下数据管理模块的句柄:

  • a. 交换机数据管理模块句柄
  • b. 队列数据管理模块句柄
  • c. 绑定数据管理模块句柄
  • d. 消息数据管理模块句柄

二、虚拟机功能操作

  • a. 声明交换机:若交换机已存在则返回成功,否则创建新的交换机。
  • b. 删除交换机:删除指定交换机,并同步清除与其相关的所有绑定信息。
  • c. 声明队列:若队列已存在则返回成功,否则创建新队列,并同时创建与之关联的消息管理对象。
  • d. 删除队列:删除指定队列,并同步清除相关绑定信息、消息管理对象以及该队列中的所有消息。
  • e. 绑定交换机与队列:建立交换机与队列之间的绑定关系。
  • f. 解绑交换机与队列:解除交换机与队列之间的绑定关系。
  • g. 获取交换机绑定信息:查询指定交换机所关联的所有绑定信息。
  • h. 新增消息:向指定队列中添加消息。
  • i. 获取队首消息:从指定队列中获取位于队首的消息。
  • j. 确认删除消息:对指定消息进行确认删除操作。

那么按照上面的思路,我们很快就能写出虚拟机的框架

cpp 复制代码
class VirtualHost
{
public:
    using ptr = std::shared_ptr<VirtualHost>;

    // 构造函数,初始化虚拟机名称和各数据管理模块
    VirtualHost(const std::string &hname, const std::string &basedir,
                const std::string &dbfile);

    // 声明交换机:若存在则直接返回成功,否则创建新交换机
    bool declareExchange(const std::string &name,
                         mymq::ExchangeType type,
                         bool durable,
                         bool auto_delete,
                         const std::unordered_map<std::string, std::string> &args);

    // 删除交换机:同时清理该交换机相关的所有绑定信息
    void deleteExchange(const std::string &name);

    // 检查指定名称的交换机是否存在
    bool existsExchange(const std::string &name);

    // 根据名称查询并返回交换机指针
    mymq::Exchange::ptr selectExchange(const std::string &ename);

    // 声明队列:若存在则直接返回成功,否则创建新队列并初始化其消息存储
    bool declareQueue(const std::string &qname,
                      bool qdurable,
                      bool qexclusive,
                      bool qauto_delete,
                      const std::unordered_map<std::string, std::string> &qargs);

    // 删除队列:同时删除队列的消息存储、绑定信息及队列自身
    void deleteQueue(const std::string &name);

    // 检查指定名称的队列是否存在
    bool existsQueue(const std::string &name);

    // 获取所有队列的映射表
    mymq::QueueMap allQueues();

    // 绑定交换机与队列:建立指定交换机与队列之间的路由关系
    bool bind(const std::string &ename, const std::string &qname, const std::string &key);

    // 解绑交换机与队列:解除指定交换机与队列之间的路由关系
    void unBind(const std::string &ename, const std::string &qname);

    // 获取指定交换机的所有绑定信息
    mymq::MsgQueueBindingMap exchangeBindings(const std::string &ename);

    // 检查指定交换机和队列之间是否存在绑定关系
    bool existsBinding(const std::string &ename, const std::string &qname);

    // 发布消息:向指定队列添加消息
    bool basicPublish(const std::string &qname,
                      mymq::BasicProperties *bp,
                      const std::string &body);

    // 消费消息:获取指定队列的队首消息
    mymq::MessagePtr basicConsume(const std::string &qname);

    // 消息确认删除:从指定队列中删除已确认的消息
    void basicAck(const std::string &qname, const std::string &msgid);

    // 清空虚拟机:清理所有数据管理模块中的内容
    void clear();

private:
    std::string _host_name;     // 虚拟机名称
    mymq::ExchangeManager::ptr _emp;  // 交换机管理器指针
    mymq::MsgQueueManager::ptr _mqmp; // 队列管理器指针
    mymq::BindingManager::ptr _bmp;   // 绑定管理器指针
    mymq::MessageManager::ptr _mmp;   // 消息管理器指针
};

这样子,我们很快就能写出下面这个代码

cpp 复制代码
#ifndef __M_HOST_H__
#define __M_HOST_H__
#include "exchange.hpp"
#include "queue.hpp"
#include "bindings.hpp"
#include "message.hpp"

namespace mymq
{
    class VirtualHost
    {
    public:
        using ptr = std::shared_ptr<VirtualHost>;

        // 构造函数,初始化虚拟机名称和各数据管理模块
        VirtualHost(const std::string &hname, const std::string &basedir,
                    const std::string &dbfile) : _host_name(hname),                                      // 虚拟机名称
                                                 _emp(std::make_shared<mymq::ExchangeManager>(dbfile)),  // 交换机管理器
                                                 _mqmp(std::make_shared<mymq::MsgQueueManager>(dbfile)), // 队列管理器
                                                 _bmp(std::make_shared<mymq::BindingManager>(dbfile)),   // 绑定管理器
                                                 _mmp(std::make_shared<mymq::MessageManager>(basedir))   // 消息管理器
        {
            // 系统重启的时候才会去调用这个构造函数

            // 获取所有队列信息,并初始化每个队列的消息存储
            mymq::QueueMap qm = _mqmp->allQueues(); // 将数据库里面的queue_table表里面的数据全部加载到内存
            // 注意QueueMap是std::unordered_map<std::string, MsgQueue::ptr>;而MsgQueue::ptr是std::shared_ptr<MsgQueue>,
            // 也就是QueueMap记录了所有队列的信息
            for (auto &q : qm) // 遍历所有队列的信息
            {
                _mmp->initQueueMessage(q.first); // 将每个队列的所有信息都从文件里面加载到内存中
            }
        }

        // 声明交换机:若存在则直接返回成功,否则创建新交换机
        bool declareExchange(const std::string &name,
                             mymq::ExchangeType type,
                             bool durable,
                             bool auto_delete,
                             const std::unordered_map<std::string, std::string> &args)
        {
            return _emp->declareExchange(name, type, durable, auto_delete, args);
        }

        // 删除交换机:同时清理该交换机相关的所有绑定信息
        void deleteExchange(const std::string &name)
        {
            // 首先删除与该交换机相关的所有绑定
            _bmp->removeExchangeBindings(name);
            // 然后删除交换机本身
            return _emp->deleteExchange(name);
        }

        // 检查指定名称的交换机是否存在
        bool existsExchange(const std::string &name)
        {
            return _emp->exists(name);
        }

        // 根据名称查询并返回交换机指针
        mymq::Exchange::ptr selectExchange(const std::string &ename)
        {
            return _emp->selectExchange(ename);
        }

        // 声明队列:若存在则直接返回成功,否则创建新队列并初始化其消息存储
        bool declareQueue(const std::string &qname,
                          bool qdurable,
                          bool qexclusive,
                          bool qauto_delete,
                          const std::unordered_map<std::string, std::string> &qargs)
        {
            // 初始化队列的消息存储管理器
            _mmp->initQueueMessage(qname);
            // 创建队列
            return _mqmp->declareQueue(qname, qdurable, qexclusive, qauto_delete, qargs);
        }

        // 删除队列:同时删除队列的消息存储、绑定信息及队列自身
        void deleteQueue(const std::string &name)
        {
            // 删除队列的消息存储管理器
            _mmp->destroyQueueMessage(name);
            // 删除与该队列相关的所有绑定信息
            _bmp->removeMsgQueueBindings(name);
            // 删除队列本身
            return _mqmp->deleteQueue(name);
        }

        // 检查指定名称的队列是否存在
        bool existsQueue(const std::string &name)
        {
            return _mqmp->exists(name);
        }

        // 获取所有队列的映射表
        mymq::QueueMap allQueues()
        {
            return _mqmp->allQueues();
        }

        // 绑定交换机与队列:建立指定交换机与队列之间的路由关系
        bool bind(const std::string &ename, const std::string &qname, const std::string &key)
        {
            // 检查交换机是否存在
            mymq::Exchange::ptr ep = _emp->selectExchange(ename);
            if (ep.get() == nullptr) // 交换机不存在
            {
                DLOG("进行队列绑定失败,交换机%s不存在!", ename.c_str());
                return false;
            }
            // 检查队列是否存在
            mymq::MsgQueue::ptr mqp = _mqmp->selectQueue(qname);
            if (mqp.get() == nullptr) // 队列不存在
            {
                DLOG("进行队列绑定失败,队列%s不存在!", qname.c_str());
                return false;
            }
            // 创建绑定,持久化属性由交换机和队列的持久化标志共同决定
            return _bmp->bind(ename, qname, key, ep->durable && mqp->durable);
        }

        // 解绑交换机与队列:解除指定交换机与队列之间的路由关系
        void unBind(const std::string &ename, const std::string &qname)
        {
            return _bmp->unBind(ename, qname);
        }

        // 获取指定交换机的所有绑定信息
        mymq::MsgQueueBindingMap exchangeBindings(const std::string &ename)
        {
            return _bmp->getExchangeBindings(ename);
        }

        // 检查指定交换机和队列之间是否存在绑定关系
        bool existsBinding(const std::string &ename, const std::string &qname)
        {
            return _bmp->exists(ename, qname);
        }

        // 发布消息:向指定队列添加消息
        bool basicPublish(const std::string &qname,
                          mymq::BasicProperties *bp,
                          const std::string &body)
        {
            // 检查队列是否存在
            mymq::MsgQueue::ptr mqp = _mqmp->selectQueue(qname);
            if (mqp.get() == nullptr)
            {
                DLOG("发布消息失败,队列%s不存在!", qname.c_str());
                return false;
            }
            // 插入消息,持久化属性由队列的持久化标志决定
            return _mmp->insert(qname, bp, body, mqp->durable);
        }

        // 消费消息:获取指定队列的队首消息
        mymq::MessagePtr basicConsume(const std::string &qname)
        {
            return _mmp->front(qname);
        }

        // 消息确认删除:从指定队列中删除已确认的消息
        void basicAck(const std::string &qname, const std::string &msgid)
        {
            return _mmp->ack(qname, msgid);
        }

        // 清空虚拟机:清理所有数据管理模块中的内容
        void clear()
        {
            _emp->clear();  // 清空交换机数据
            _mqmp->clear(); // 清空队列数据
            _bmp->clear();  // 清空绑定数据
            _mmp->clear();  // 清空消息数据
        }

    private:
        std::string _host_name;           // 虚拟机名称
        mymq::ExchangeManager::ptr _emp;  // 交换机管理器指针
        mymq::MsgQueueManager::ptr _mqmp; // 队列管理器指针
        mymq::BindingManager::ptr _bmp;   // 绑定管理器指针
        mymq::MessageManager::ptr _mmp;   // 消息管理器指针
    };
}
#endif

二.代码测试

创建交换机,队列,绑定,消息测试

我们很快就能写出下面这个测试

cpp 复制代码
#include "../../mqserver/host.hpp"
#include "../../mqcommon/logger.hpp"
#include "../../mqcommon/helper.hpp"
#include <gtest/gtest.h>

class VirtualHostManagerTest : public testing::Test
{
protected:
    // 测试夹具的SetUp,在每个测试用例开始前执行
    void SetUp() override
    {
        // 创建消息管理器,指定数据存储目录
        host = std::make_shared<mymq::VirtualHost>("host1", "./data/host1/mes/", "./data/host1/host1.db");
        
        // 声明交换机
        std::unordered_map<std::string, std::string> args;
        host->declareExchange("exchange1", mymq::ExchangeType::DIRECT, true, false, args);
        host->declareExchange("exchange2", mymq::ExchangeType::DIRECT, true, false, args);
        host->declareExchange("exchange3", mymq::ExchangeType::DIRECT, true, false, args);

        // 声明队列
        std::unordered_map<std::string, std::string> qargs;
        host->declareQueue("queue1", true, false, false, qargs);
        host->declareQueue("queue2", true, false, false, qargs);
        host->declareQueue("queue3", true, false, false, qargs);

        // 绑定交换机和队列
        host->bind("exchange1", "queue1", "news.music.#");
        host->bind("exchange1", "queue2", "news.music.#");
        host->bind("exchange1", "queue3", "news.music.#");

        host->bind("exchange2", "queue1", "news.music.#");
        host->bind("exchange2", "queue2", "news.music.#");
        host->bind("exchange2", "queue3", "news.music.#");

        host->bind("exchange3", "queue1", "news.music.#");
        host->bind("exchange3", "queue2", "news.music.#");
        host->bind("exchange3", "queue3", "news.music.#");

        // 发布消息
        host->basicPublish("queue1", nullptr, "Hello World-1");
        host->basicPublish("queue1", nullptr, "Hello World-2");
        host->basicPublish("queue1", nullptr, "Hello World-3");

        host->basicPublish("queue2", nullptr, "Hello World-1");
        host->basicPublish("queue2", nullptr, "Hello World-2");
        host->basicPublish("queue2", nullptr, "Hello World-3");

        host->basicPublish("queue3", nullptr, "Hello World-1");
        host->basicPublish("queue3", nullptr, "Hello World-2");
        host->basicPublish("queue3", nullptr, "Hello World-3");
    }

    // 测试夹具的TearDown,在每个测试用例结束后执行
    void TearDown() override
    {
        host->clear();
        host.reset();  // 释放共享指针
    }

    // 共享的测试资源
    mymq::VirtualHost::ptr host;
};

TEST_F(VirtualHostManagerTest, InitTest)
{
    // 验证交换机的存在
    EXPECT_TRUE(host->existsExchange("exchange1"));
    EXPECT_TRUE(host->existsExchange("exchange2"));
    EXPECT_TRUE(host->existsExchange("exchange3"));

    // 验证队列的存在
    EXPECT_TRUE(host->existsQueue("queue1"));
    EXPECT_TRUE(host->existsQueue("queue2"));
    EXPECT_TRUE(host->existsQueue("queue3"));

    // 验证绑定的存在
    EXPECT_TRUE(host->existsBinding("exchange1", "queue1"));
    EXPECT_TRUE(host->existsBinding("exchange1", "queue2"));
    EXPECT_TRUE(host->existsBinding("exchange1", "queue3"));

    EXPECT_TRUE(host->existsBinding("exchange2", "queue1"));
    EXPECT_TRUE(host->existsBinding("exchange2", "queue2"));
    EXPECT_TRUE(host->existsBinding("exchange2", "queue3"));

    EXPECT_TRUE(host->existsBinding("exchange3", "queue1"));
    EXPECT_TRUE(host->existsBinding("exchange3", "queue2"));
    EXPECT_TRUE(host->existsBinding("exchange3", "queue3"));

    // 验证消息消费
    mymq::MessagePtr msg1 = host->basicConsume("queue1");
    ASSERT_EQ(msg1->payload().body(), std::string("Hello World-1"));
    
    mymq::MessagePtr msg2 = host->basicConsume("queue1");
    ASSERT_EQ(msg2->payload().body(), std::string("Hello World-2"));
    
    mymq::MessagePtr msg3 = host->basicConsume("queue1");
    ASSERT_EQ(msg3->payload().body(), std::string("Hello World-3"));
    
    mymq::MessagePtr msg4 = host->basicConsume("queue1");
    ASSERT_EQ(msg4.get(), nullptr);
}


int main(int argc, char *argv[])
{
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

编译的代码很简单

cpp 复制代码
g++ test.cpp ../../mqcommon/msg.pb.cc -lgtest -lprotobuf -lsqlite3 -lpthread

删除交换机,队列测试

很简单就能写出下面这个代码

cpp 复制代码
#include "../../mqserver/host.hpp"
#include "../../mqcommon/logger.hpp"
#include "../../mqcommon/helper.hpp"
#include <gtest/gtest.h>

class VirtualHostManagerTest : public testing::Test
{
protected:
    // 测试夹具的SetUp,在每个测试用例开始前执行
    void SetUp() override
    {
        // 创建消息管理器,指定数据存储目录
        host = std::make_shared<mymq::VirtualHost>("host1", "./data/host1/mes/", "./data/host1/host1.db");
        
        // 声明交换机
        std::unordered_map<std::string, std::string> args;
        host->declareExchange("exchange1", mymq::ExchangeType::DIRECT, true, false, args);
        host->declareExchange("exchange2", mymq::ExchangeType::DIRECT, true, false, args);
        host->declareExchange("exchange3", mymq::ExchangeType::DIRECT, true, false, args);

        // 声明队列
        std::unordered_map<std::string, std::string> qargs;
        host->declareQueue("queue1", true, false, false, qargs);
        host->declareQueue("queue2", true, false, false, qargs);
        host->declareQueue("queue3", true, false, false, qargs);

        // 绑定交换机和队列
        host->bind("exchange1", "queue1", "news.music.#");
        host->bind("exchange1", "queue2", "news.music.#");
        host->bind("exchange1", "queue3", "news.music.#");

        host->bind("exchange2", "queue1", "news.music.#");
        host->bind("exchange2", "queue2", "news.music.#");
        host->bind("exchange2", "queue3", "news.music.#");

        host->bind("exchange3", "queue1", "news.music.#");
        host->bind("exchange3", "queue2", "news.music.#");
        host->bind("exchange3", "queue3", "news.music.#");

        // 发布消息
        host->basicPublish("queue1", nullptr, "Hello World-1");
        host->basicPublish("queue1", nullptr, "Hello World-2");
        host->basicPublish("queue1", nullptr, "Hello World-3");

        host->basicPublish("queue2", nullptr, "Hello World-1");
        host->basicPublish("queue2", nullptr, "Hello World-2");
        host->basicPublish("queue2", nullptr, "Hello World-3");

        host->basicPublish("queue3", nullptr, "Hello World-1");
        host->basicPublish("queue3", nullptr, "Hello World-2");
        host->basicPublish("queue3", nullptr, "Hello World-3");
    }

    // 测试夹具的TearDown,在每个测试用例结束后执行
    void TearDown() override
    {
        host->clear();
        host.reset();  // 释放共享指针
    }

    // 共享的测试资源
    mymq::VirtualHost::ptr host;
};

TEST_F(VirtualHostManagerTest, remove_exchange)
{
    //删除交换机
    host->deleteExchange("exchange1");
    // 验证绑定的存在
    EXPECT_EQ(host->existsBinding("exchange1", "queue1"),false);
    EXPECT_EQ(host->existsBinding("exchange1", "queue2"),false);
    EXPECT_EQ(host->existsBinding("exchange1", "queue3"),false);
}

TEST_F(VirtualHostManagerTest, remove_queue)
{
    //删除队列
    host->deleteQueue("queue1");
    // 验证绑定的存在
    EXPECT_EQ(host->existsBinding("exchange1", "queue1"),false);
    EXPECT_EQ(host->existsBinding("exchange2", "queue1"),false);
    EXPECT_EQ(host->existsBinding("exchange3", "queue1"),false);
    //验证消息的存在
    //队列都没有了,消息就没有存在的必要了
    mymq::MessagePtr msg4 = host->basicConsume("queue1");
    ASSERT_EQ(msg4.get(), nullptr);
}


int main(int argc, char *argv[])
{
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

我们编译运行一下

cpp 复制代码
g++ test.cpp ../../mqcommon/msg.pb.cc -lgtest -lprotobuf -lsqlite3 -lpthread

确认消息测试

cpp 复制代码
#include "../../mqserver/host.hpp"
#include "../../mqcommon/logger.hpp"
#include "../../mqcommon/helper.hpp"
#include <gtest/gtest.h>

class VirtualHostManagerTest : public testing::Test
{
protected:
    // 测试夹具的SetUp,在每个测试用例开始前执行
    void SetUp() override
    {
        // 创建消息管理器,指定数据存储目录
        host = std::make_shared<mymq::VirtualHost>("host1", "./data/host1/mes/", "./data/host1/host1.db");
        
        // 声明交换机
        std::unordered_map<std::string, std::string> args;
        host->declareExchange("exchange1", mymq::ExchangeType::DIRECT, true, false, args);
        host->declareExchange("exchange2", mymq::ExchangeType::DIRECT, true, false, args);
        host->declareExchange("exchange3", mymq::ExchangeType::DIRECT, true, false, args);

        // 声明队列
        std::unordered_map<std::string, std::string> qargs;
        host->declareQueue("queue1", true, false, false, qargs);
        host->declareQueue("queue2", true, false, false, qargs);
        host->declareQueue("queue3", true, false, false, qargs);

        // 绑定交换机和队列
        host->bind("exchange1", "queue1", "news.music.#");
        host->bind("exchange1", "queue2", "news.music.#");
        host->bind("exchange1", "queue3", "news.music.#");

        host->bind("exchange2", "queue1", "news.music.#");
        host->bind("exchange2", "queue2", "news.music.#");
        host->bind("exchange2", "queue3", "news.music.#");

        host->bind("exchange3", "queue1", "news.music.#");
        host->bind("exchange3", "queue2", "news.music.#");
        host->bind("exchange3", "queue3", "news.music.#");

        // 发布消息
        host->basicPublish("queue1", nullptr, "Hello World-1");
        host->basicPublish("queue1", nullptr, "Hello World-2");
        host->basicPublish("queue1", nullptr, "Hello World-3");

        host->basicPublish("queue2", nullptr, "Hello World-1");
        host->basicPublish("queue2", nullptr, "Hello World-2");
        host->basicPublish("queue2", nullptr, "Hello World-3");

        host->basicPublish("queue3", nullptr, "Hello World-1");
        host->basicPublish("queue3", nullptr, "Hello World-2");
        host->basicPublish("queue3", nullptr, "Hello World-3");
    }

    // 测试夹具的TearDown,在每个测试用例结束后执行
    void TearDown() override
    {
        host->clear();
        host.reset();  // 释放共享指针
    }

    // 共享的测试资源
    mymq::VirtualHost::ptr host;
};

TEST_F(VirtualHostManagerTest, ack_test)
{
    mymq::MessagePtr msg1 = host->basicConsume("queue1");
    ASSERT_EQ(msg1->payload().body(),std::string("Hello World-1"));
    host->basicAck(std::string("queue1"),msg1->payload().properties().id());

    mymq::MessagePtr msg2 = host->basicConsume("queue1");
    ASSERT_EQ(msg2->payload().body(),std::string("Hello World-2"));
    host->basicAck(std::string("queue1"),msg2->payload().properties().id());

    mymq::MessagePtr msg3 = host->basicConsume("queue1");
    ASSERT_EQ(msg3->payload().body(),std::string("Hello World-3"));
    host->basicAck(std::string("queue1"),msg3->payload().properties().id());
}


int main(int argc, char *argv[])
{
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

编译运行

cpp 复制代码
g++ test.cpp ../../mqcommon/msg.pb.cc -lgtest -lprotobuf -lsqlite3 -lpthread

完美,一点错误都没有

相关推荐
老王熬夜敲代码3 小时前
网络中数据传输的具体过程
linux·网络·笔记
汤愈韬4 小时前
TK_网络基础和常见攻击(笔记)
网络·笔记
北邮刘老师5 小时前
【智能体互联协议解析】需要“智能体名字系统”(ANS)吗?
网络·人工智能·大模型·智能体·智能体互联网
照海19Gin6 小时前
【企业网架构实验解析】三层组网与 AC+AP 无线部署的协议逻辑(eNSP 仿真实践)
网络
Lilixxs6 小时前
wireshark LLDP 协议抓包获取 M580 PLC 网口地址
网络·测试工具·wireshark·plc·施耐德·lldp·m580
vortex56 小时前
Linux下局域网IPv6地址发现与扫描
网络
北邮刘老师7 小时前
【智能体互联协议解析】身份码-智能体的身份证号
网络·人工智能·大模型·智能体·智能体互联网
日更嵌入式的打工仔8 小时前
Ethercat COE 笔记
网络·笔记·ethercat
UVM_ERROR9 小时前
UVM实战:RDMA Host侧激励开发全流程问题排查与解决
服务器·网络·数据库