一.代码设计与编写
注意,我们这个虚拟机的代码都是放在下面的queue.hpp里面的

虚拟机模块整合了
- 交换机数据管理模块
- 队列数据管理模块
- 绑定信息管理模块
- 消息信息管理模块
四个数据管理模块,并依据数据间的关联关系提供联合操作支持。
一、虚拟机类成员
该类包含以下数据管理模块的句柄:
- 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

完美,一点错误都没有