目录
我们交换机的所有类,都存放在mqserver/exchange.hpp里面

好了,我们现在开始设计
一.交换机数据管理
1.定义交换机数据类
- a. 交换机名称
- b. 交换机类型:
- c. 是否持久化标志:指示交换机是否需要在服务器重启后继续存在
- d. 是否自动删除标志:当所有绑定队列都取消绑定时,是否自动删除该交换机
- f.其他参数
我们很快就能写出下面这个初版(注意是第一版,不代表最终效果)
cpp
// 1. 定义交换机数据结构体
// 该类表示系统中的交换机实体,包含交换机的所有配置属性
struct Exchange
{
using ptr = std::shared_ptr<Exchange>; // 智能指针别名,便于管理生命周期
std::string name; // 交换机唯一标识名称
mymq::ExchangeType type; // 交换机类型:DIRECT、FANOUT、TOPIC,注意mymq::ExchangeType是我们的.proto文件编译生成的类
bool durable; // 持久化标志:true-持久化,false-非持久化
bool auto_delete; // 自动删除标志:true-无绑定时自动删除
std::unordered_map<std::string, std::string> args; // 额外参数
// 默认构造函数
Exchange() {}
// 带参构造函数,初始化所有成员
Exchange(const std::string &ename, // 交换机名称
mymq::ExchangeType etype, // 交换机类型
bool edurable, // 交换机持久化标志
bool eauto_delete, // 交换机自动删除标志
std::unordered_map<std::string, std::string> &eargs) : name(ename), type(etype), durable(edurable),
auto_delete(eauto_delete), args(eargs)
{
}
// 我们传进去的字符串str_args的格式是:key1=value1&key2=value2&...
// 那么我们就在这个函数里面对这个str_args进行拆分,然后把拆分出来的信息填充到成员变量args里(他是一个unordered_map)
void setArgs(const std::string &str_args) {}
// 将成员变量args里面的信息序列化为格式化字符串
// 返回格式:key1=value1&key2=value2&...
std::string getArgs() {}
};
2.定义交换机数据持久化类(数据持久化的sqlite3数据库中)
- a. 创建/删除交换机数据表
- b. 新增交换机数据
- c. 删除交换机数据
- d. 查询所有交换机数据
- e. 查询指定交换机数据(根据名称)
我们很快就能写出下面这个初版(注意是第一版,不代表最终效果)
cpp
// 交换机映射表类型:以交换机名称为键,指向Exchange对象的智能指针为值
using ExchangeMap = std::unordered_map<std::string, Exchange::ptr>;
// 2. 交换机数据持久化管理类
// 负责将交换机数据存储到SQLite数据库,提供基本的CRUD操作
class ExchangeMapper
{
public:
// 构造函数,初始化数据库连接
// dbfile: SQLite数据库文件路径
ExchangeMapper(const std::string &dbfile) : _sql_helper(dbfile)
{
}
// 创建交换机数据表
void createTable()
{
}
// 删除交换机数据表
void removeTable()
{
}
// 插入新交换机数据
bool insert(Exchange::ptr &exp) // Exchange::ptr是一个std::shared_ptr<Exchange>
{
}
// 删除指定名称的交换机数据
void remove(const std::string &name)
{
}
//getOne:根据名称从数据库中查询一个交换机,并返回一个Exchange::ptr
//Exchange::ptr是一个std::shared_ptr<Exchange>
Exchange::ptr getOne(const std::string &name) {}
//getAll:从数据库中获取所有交换机,并返回一个ExchangeMap
// ExchangeMap是std::unordered_map<std::string, Exchange::ptr>
std::unordered_map<std::string, Exchange::ptr> getAll() {}
private:
SqliteHelper _sql_helper; // SQLite数据库操作辅助类实例
};
- 定义交换机数据管理类
- a. 声明交换机,并添加管理(存在则OK,不存在则创建)
- b. 删除交换机
- c. 获取指定交换机
- d. 销毁所有交换机数据'
我们很快就能写出下面这个初版(注意是第一版,不代表最终效果)
cpp
// 3. 交换机数据内存管理类
// 管理内存中的交换机数据,协调内存与持久化存储的同步
class ExchangeManager
{
public:
using ptr = std::shared_ptr<ExchangeManager>; // 智能指针别名
// 构造函数,初始化持久化映射器并恢复数据
// dbfile: 数据库文件路径
ExchangeManager(const std::string &dbfile) : _mapper(dbfile)
{
}
// 声明(创建或获取)交换机
bool declareExchange(const std::string &name,
mymq::ExchangeType type,
bool durable,
bool auto_delete,
std::unordered_map<std::string, std::string> args)
{
}
// 删除指定名称的交换机
void deleteExchange(const std::string &name)
{
}
// 获取指定名称的交换机对象
//Exchange::ptr是一个std::shared_ptr<Exchange>
Exchange::ptr selectExchange(const std::string &name)
{
}
// 判断指定名称的交换机是否存在
bool exists(const std::string &name)
{
}
// 获取当前内存中的交换机数量
size_t size()
{
}
// 清理所有交换机数据(从内存和数据库)
void clear()
{
}
private:
std::mutex _mutex; // 互斥锁,保证线程安全
ExchangeMapper _mapper; // 持久化管理器实例
ExchangeMap _exchanges; // 内存中的交换机映射表,注意ExchangeMap其实是std::unordered_map<std::string, Exchange::ptr>;
};
那么到这里我们的模板算是搭建出来了,不代表最终效果!!!
二.交换机数据类实现
说实话这个类是真的没有什么技术含量
cpp
// 1. 定义交换机数据结构体
// 该类表示系统中的交换机实体,包含交换机的所有配置属性
struct Exchange
{
using ptr = std::shared_ptr<Exchange>; // 智能指针别名,便于管理生命周期
std::string name; // 交换机唯一标识名称
mymq::ExchangeType type; // 交换机类型:DIRECT、FANOUT、TOPIC,注意mymq::ExchangeType是我们的.proto文件编译生成的类
bool durable; // 持久化标志:true-持久化,false-非持久化
bool auto_delete; // 自动删除标志:true-无绑定时自动删除
std::unordered_map<std::string, std::string> args; // 额外参数
// 默认构造函数
Exchange() {}
// 带参构造函数,初始化所有成员
Exchange(const std::string &ename, // 交换机名称
mymq::ExchangeType etype, // 交换机类型
bool edurable, // 交换机持久化标志
bool eauto_delete, // 交换机自动删除标志
std::unordered_map<std::string, std::string> &eargs) : name(ename), type(etype), durable(edurable),
auto_delete(eauto_delete), args(eargs)
{}
// 我们传进去的字符串str_args的格式是:key1=value1&key2=value2&...
// 那么我们就在这个函数里面对这个str_args进行拆分,然后把拆分出来的信息填充到成员变量args里(他是一个unordered_map)
void setArgs(const std::string &str_args)
{
std::vector<std::string> sub_args;
mymq::StringHelper::split(str_args, "&", sub_args); // 按'&'分割字符串,将分割的字串全部放到sub_args
// 此时每个字串的样子都是key1=value1
for (auto &str : sub_args) // 遍历每一个字串
{
size_t pos = str.find("="); // 查找键值分隔符
std::string key = str.substr(0, pos); // 提取键名
std::string val = str.substr(pos + 1); // 提取键值
args[key] = val; // 存储到map中
}
}
// 将成员变量args里面的信息序列化为格式化字符串
// 返回格式:key1=value1&key2=value2&...
std::string getArgs()
{
std::string result;
for (auto start = args.begin(); start != args.end(); ++start) // 遍历args变量里面的所有元素
{
result += start->first + "=" + start->second + "&"; // 组织字符串
}
return result;
}
};
我们很容易就写出完整的代码
不过这不是最终版本,只是代表我们作为一个普通人应该有的思路去写出的代码
三.交换机数据持久化类实现
stringstream类讲解
在C++中,stringstream是一个类,它允许你将字符串当作流来处理。这意味着你可以像使用cin和cout那样,从字符串中读取数据,或者将数据写入字符串。
它位于<sstream>头文件中。
主要用途:
-
字符串分割(通过空格、换行等分隔符)
-
数据类型转换:将数字字符串转换为数值类型,或者将数值类型转换为字符串。
-
字符串拼接:将不同类型的数据拼接成一个字符串。
基本操作:
-
创建一个stringstream对象:可以使用字符串初始化,也可以不初始化。
-
向stringstream对象写入数据:使用 << 操作符,就像向cout写入一样。
-
从stringstream对象读取数据:使用 >> 操作符,就像从cin读取一样。
-
获取stringstream中的字符串:使用.str()方法。
我们举一些简单的例子来说明
示例1:将数据组合成字符串
cpp
#include <iostream>
#include <sstream> // 需要包含这个头文件
#include <string>
int main() {
std::stringstream ss; // 创建 stringstream 对象
int age = 25;
std::string name = "张三";
double score = 89.5;
// 将不同类型的数据写入字符串流
ss << "姓名:" << name << ",年龄:" << age << ",分数:" << score;
// 获取组合后的字符串
std::string result = ss.str();
std::cout << result << std::endl;
// 输出:姓名:张三,年龄:25,分数:89.5
return 0;
}
示例2:从字符串提取数据
cpp
#include <iostream>
#include <sstream>
#include <string>
int main() {
std::string data = "张三 25 89.5";
std::stringstream ss(data); // 用字符串初始化
std::string name;
int age;
double score;
// 从流中提取数据,按空格分隔
ss >> name >> age >> score;
std::cout << "姓名:" << name << std::endl;
std::cout << "年龄:" << age << std::endl;
std::cout << "分数:" << score << std::endl;
return 0;
}
示例3:数据类型转换
cpp
#include <iostream>
#include <sstream>
#include <string>
int main() {
// 数字转字符串
std::stringstream ss1;
int num = 123;
ss1 << num; // 将整数写入流
std::string str_num = ss1.str(); // 获取字符串形式的数字
std::cout << "字符串:" << str_num << std::endl;
// 字符串转数字
std::string str = "456";
std::stringstream ss2(str);
int value;
ss2 >> value; // 从流中提取整数
std::cout << "数字:" << value << std::endl;
return 0;
}
常用成员函数
- str()
获取流中的字符串
可以带参数设置新字符串:ss.str("新内容")
- clear()
清除错误状态标志(如文件结束标志)
注意:不会清除内容
- 示例:重置 stringstream
cpp
#include <iostream>
#include <sstream>
int main() {
std::stringstream ss;
// 第一次使用
ss << "Hello";
std::cout << ss.str() << std::endl; // 输出:Hello
// 重置:清空内容并清除状态
ss.str(""); // 清空内容
ss.clear(); // 清除状态标志
// 再次使用
ss << "World";
std::cout << ss.str() << std::endl; // 输出:World
return 0;
}
实现
cpp
// 交换机映射表类型:以交换机名称为键,指向Exchange对象的智能指针为值
using ExchangeMap = std::unordered_map<std::string, Exchange::ptr>;
// 2. 交换机数据持久化管理类
// 负责将交换机数据存储到SQLite数据库,提供基本的CRUD操作
class ExchangeMapper
{
public:
// 构造函数,初始化数据库连接
// dbfile: SQLite数据库文件路径
ExchangeMapper(const std::string &dbfile) : _sql_helper(dbfile)
{
std::string path = mymq::FileHelper::parentDirectory(dbfile); // 我们先获取数据库文件的父目录
mymq::FileHelper::createDirectory(path); // 确保数据库目录存在
assert(_sql_helper.open()); // 打开数据库连接
createTable(); // 创建数据表
}
// 创建交换机数据表
void createTable()
{
std::string CREATE_TABLE =
"create table if not exists exchange_table("
"name varchar(32) primary key, " // 交换机名称,主键,最长32字符
"type int, " // 交换机类型,整数表示
"durable int, " // 持久化标志,0或1
"auto_delete int, " // 自动删除标志,0或1
"args varchar(128));"; // 额外参数,序列化字符串格式
bool ret = _sql_helper.exec(CREATE_TABLE, nullptr, nullptr);//执行SQL语句,SQL语句没有查询结果,不需要回调函数
if (ret == false)
{
DLOG("创建交换机数据库表失败!!"); // 记录错误日志
abort(); // 表创建失败,终止程序
}
}
// 删除交换机数据表
void removeTable()
{
std::string DROP_TABLE="drop table if exists exchange_table;";
bool ret = _sql_helper.exec(DROP_TABLE, nullptr, nullptr);
if (ret == false) {
DLOG("删除交换机数据库表失败!!");
abort(); // 表删除失败,终止程序
}
}
// 插入新交换机数据
bool insert(Exchange::ptr &exp) // Exchange::ptr是一个std::shared_ptr<Exchange>
{
//本质就是往exchange_table表里面插入一条数据
std::stringstream ss;
ss << "insert into exchange_table values(";
ss << "'" << exp->name << "', "; // 名称
ss << exp->type << ", "; // 类型
ss << exp->durable << ", "; // 持久化标志
ss << exp->auto_delete << ", "; // 自动删除标志
ss << "'" << exp->getArgs() << "');"; // 序列化参数
//这个时候ss就已经是一条SQL语句了,往exchange_table里面插入一条交换机的数据
return _sql_helper.exec(ss.str(), nullptr, nullptr);
}
// 删除指定名称的交换机数据
void remove(const std::string &name)
{
std::stringstream ss;
ss << "delete from exchange_table where name=";
ss << "'" << name << "';";
bool ret= _sql_helper.exec(ss.str(), nullptr, nullptr);
if (ret == false)
{
DLOG("删除交换机%s表失败!!",name);
abort(); // 交换机删除失败,终止程序
}
}
// 将数据库里面的exchange_table表里面的所有交换机数据加载到内存
// 返回值: 包含所有交换机的映射表
//注意ExchangeMap是std::unordered_map<std::string, Exchange::ptr>
//而这个Exchange::ptr是std::shared_ptr<Exchange>
ExchangeMap recovery()
{
ExchangeMap result;
// 查询所有字段
std::string sql = "select name, type, durable, auto_delete, args from exchange_table";
_sql_helper.exec(sql, selectCallback, &result); // 回调函数selectCallback处理查询结果,并且将result传递给了selectCallback
return result;
}
private:
// SQLite查询结果回调函数------注意:只有recovery()函数才会使用到这个回调函数,所以这个函数是为recovery()特制的
// arg: 用户自定义参数,此处为ExchangeMap指针,我们在上面传入了&result,把所有的数据都
// numcol: 返回的列数
// row: 当前行的每一列的数据
// fields: 记录了每一列的名称
static int selectCallback(void* arg,int numcol,char** row,char** fields) {
ExchangeMap *result = (ExchangeMap*)arg;
auto exp = std::make_shared<Exchange>(); // 创建新Exchange对象
exp->name = row[0]; // 名称
exp->type = (mymq::ExchangeType)std::stoi(row[1]); // 这里可是交换机类型:mymq::ExchangeType,这里进行了类型转换
exp->durable = (bool)std::stoi(row[2]); // 持久化标志
exp->auto_delete = (bool)std::stoi(row[3]); // 自动删除标志
if (row[4]) //说明存在其他参数
{
exp->setArgs(row[4]); // 解析参数字符串
}
//这里我们就把临时对象的数据添加进我们传进来的这个ExchangeMap里面
result->insert(std::make_pair(exp->name, exp)); // 插入到映射表:[交换机名称,交换机对象]
return 0;
}
mymq::SqliteHelper _sql_helper; // SQLite数据库操作辅助类实例
};
这个可能和我们最初的想法其实是有点不一样的,但是不要紧
四.交换机数据管理类实现
cpp
// 3. 交换机数据内存管理类
// 管理内存中的交换机数据,协调内存与持久化存储的同步
class ExchangeManager
{
public:
using ptr = std::shared_ptr<ExchangeManager>; // 智能指针别名
// 构造函数,初始化持久化映射器并恢复数据
// dbfile: 数据库文件路径
ExchangeManager(const std::string &dbfile) : _mapper(dbfile)
{
_exchanges = _mapper.recovery(); // 将数据库里面的exchange_table表里面的所有交换机数据加载到内存
}
// 声明(创建或获取)交换机
bool declareExchange(const std::string &name,//交换机名称
mymq::ExchangeType type,//交换机类型
bool durable,//持久化标志
bool auto_delete,//自动删除标志
std::unordered_map<std::string, std::string> args)
{
std::unique_lock<std::mutex> lock(_mutex); // 加锁保证线程安全
auto it = _exchanges.find(name);//交换机已经在内存中存在
if (it != _exchanges.end()) {
// 如果交换机已存在,直接返回成功
return true;
}
auto exp = std::make_shared<Exchange>(name, type, durable, auto_delete, args);//创建Exchange临时对象
if (durable == true) { //持久化标志为true
// 如果交换机需要持久化,先写入数据库
bool ret = _mapper.insert(exp);
if (ret == false)
{
return false; // 数据库写入失败
}
}
_exchanges.insert(std::make_pair(name, exp)); // 添加到内存映射表
return true;
}
// 删除指定名称的交换机
void deleteExchange(const std::string &name)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _exchanges.find(name);//查询交换机是不是存在
if (it == _exchanges.end()) { //没找到
return; // 交换机不存在,直接返回
}
if(it->second->durable == true) //持久化存储标志为true,才需要去数据库删除,否则只需要在内存中删除即可
{
_mapper.remove(name); // 从数据库删除持久化数据
}
_exchanges.erase(name); // 从内存映射表删除
}
// 获取指定名称的交换机对象
// Exchange::ptr是一个std::shared_ptr<Exchange>
Exchange::ptr selectExchange(const std::string &name)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _exchanges.find(name);//查询交换机是不是存在
if (it == _exchanges.end()) { //没找到
return Exchange::ptr(); // 返回空智能指针
}
return it->second;//返回交换机对象
}
// 判断指定名称的交换机是否存在
bool exists(const std::string &name)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _exchanges.find(name);
if (it == _exchanges.end()) {
return false;
}
return true;
}
// 获取当前内存中的交换机数量
size_t size()
{
std::unique_lock<std::mutex> lock(_mutex);
return _exchanges.size();
}
// 清理所有交换机数据(从内存和数据库)
void clear()
{
std::unique_lock<std::mutex> lock(_mutex);
_mapper.removeTable(); // 删除数据库表(包括所有数据)
_exchanges.clear(); // 清空内存映射表
}
private:
std::mutex _mutex; // 互斥锁,保证线程安全
ExchangeMapper _mapper; // 持久化管理器实例
ExchangeMap _exchanges; // 内存中的交换机映射表,注意ExchangeMap其实是std::unordered_map<std::string, Exchange::ptr>;
};
五.代码整合
我们把代码都放到mqserver/exchange.hpp里面

我们往里面写入下面这些
cpp
#ifndef __M_EXCHANGE_H__
#define __M_EXCHANGE_H__
#include "../mqcommon/logger.hpp"
#include "../mqcommon/helper.hpp"
#include "../mqcommon/msg.pb.h"
#include <google/protobuf/map.h>
#include <iostream>
#include <unordered_map>
#include <mutex>
#include <memory>
namespace mymq
{
// 1. 定义交换机数据结构体
// 该类表示系统中的交换机实体,包含交换机的所有配置属性
struct Exchange
{
using ptr = std::shared_ptr<Exchange>; // 智能指针别名,便于管理生命周期
std::string name; // 交换机唯一标识名称
mymq::ExchangeType type; // 交换机类型:DIRECT、FANOUT、TOPIC,注意mymq::ExchangeType是我们的.proto文件编译生成的类
bool durable; // 持久化标志:true-持久化,false-非持久化
bool auto_delete; // 自动删除标志:true-无绑定时自动删除
std::unordered_map<std::string, std::string> args; // 额外参数
// 默认构造函数
Exchange() {}
// 带参构造函数,初始化所有成员
Exchange(const std::string &ename, // 交换机名称
mymq::ExchangeType etype, // 交换机类型
bool edurable, // 交换机持久化标志
bool eauto_delete, // 交换机自动删除标志
std::unordered_map<std::string, std::string> &eargs) : name(ename), type(etype), durable(edurable),
auto_delete(eauto_delete), args(eargs)
{
}
// 我们传进去的字符串str_args的格式是:key1=value1&key2=value2&...
// 那么我们就在这个函数里面对这个str_args进行拆分,然后把拆分出来的信息填充到成员变量args里(他是一个unordered_map)
void setArgs(const std::string &str_args)
{
std::vector<std::string> sub_args;
mymq::StringHelper::split(str_args, "&", sub_args); // 按'&'分割字符串,将分割的字串全部放到sub_args
// 此时每个字串的样子都是key1=value1
for (auto &str : sub_args) // 遍历每一个字串
{
size_t pos = str.find("="); // 查找键值分隔符
std::string key = str.substr(0, pos); // 提取键名
std::string val = str.substr(pos + 1); // 提取键值
args[key] = val; // 存储到map中
}
}
// 将成员变量args里面的信息序列化为格式化字符串
// 返回格式:key1=value1&key2=value2&...
std::string getArgs()
{
std::string result;
for (auto start = args.begin(); start != args.end(); ++start) // 遍历args变量里面的所有元素
{
result += start->first + "=" + start->second + "&"; // 组织字符串
}
return result;
}
};
// 交换机映射表类型:以交换机名称为键,指向Exchange对象的智能指针为值
using ExchangeMap = std::unordered_map<std::string, Exchange::ptr>;
// 2. 交换机数据持久化管理类
// 负责将交换机数据存储到SQLite数据库,提供基本的CRUD操作
class ExchangeMapper
{
public:
// 构造函数,初始化数据库连接
// dbfile: SQLite数据库文件路径
ExchangeMapper(const std::string &dbfile) : _sql_helper(dbfile)
{
std::string path = mymq::FileHelper::parentDirectory(dbfile); // 我们先获取数据库文件的父目录
mymq::FileHelper::createDirectory(path); // 确保数据库目录存在
assert(_sql_helper.open()); // 打开数据库连接
createTable(); // 创建数据表
}
// 创建交换机数据表
void createTable()
{
std::string CREATE_TABLE =
"create table if not exists exchange_table("
"name varchar(32) primary key, " // 交换机名称,主键,最长32字符
"type int, " // 交换机类型,整数表示
"durable int, " // 持久化标志,0或1
"auto_delete int, " // 自动删除标志,0或1
"args varchar(128));"; // 额外参数,序列化字符串格式
bool ret = _sql_helper.exec(CREATE_TABLE, nullptr, nullptr); // 执行SQL语句,SQL语句没有查询结果,不需要回调函数
if (ret == false)
{
DLOG("创建交换机数据库表失败!!"); // 记录错误日志
abort(); // 表创建失败,终止程序
}
}
// 删除交换机数据表
void removeTable()
{
std::string DROP_TABLE = "drop table if exists exchange_table;";
bool ret = _sql_helper.exec(DROP_TABLE, nullptr, nullptr);
if (ret == false)
{
DLOG("删除交换机数据库表失败!!");
abort(); // 表删除失败,终止程序
}
}
// 插入新交换机数据
bool insert(Exchange::ptr &exp) // Exchange::ptr是一个std::shared_ptr<Exchange>
{
// 本质就是往exchange_table表里面插入一条数据
std::stringstream ss;
ss << "insert into exchange_table values(";
ss << "'" << exp->name << "', "; // 名称
ss << exp->type << ", "; // 类型
ss << exp->durable << ", "; // 持久化标志
ss << exp->auto_delete << ", "; // 自动删除标志
ss << "'" << exp->getArgs() << "');"; // 序列化参数
// 这个时候ss就已经是一条SQL语句了,往exchange_table里面插入一条交换机的数据
return _sql_helper.exec(ss.str(), nullptr, nullptr);
}
// 删除指定名称的交换机数据
void remove(const std::string &name)
{
std::stringstream ss;
ss << "delete from exchange_table where name=";
ss << "'" << name << "';";
bool ret= _sql_helper.exec(ss.str(), nullptr, nullptr);
if (ret == false)
{
DLOG("删除交换机%s失败!!",name);
abort(); // 交换机删除失败,终止程序
}
}
// 将数据库里面的exchange_table表里面的所有交换机数据加载到内存
// 返回值: 包含所有交换机的映射表
// 注意ExchangeMap是std::unordered_map<std::string, Exchange::ptr>
// 而这个Exchange::ptr是std::shared_ptr<Exchange>
ExchangeMap recovery()
{
ExchangeMap result;
// 查询所有字段
std::string sql = "select name, type, durable, auto_delete, args from exchange_table";
_sql_helper.exec(sql, selectCallback, &result); // 回调函数selectCallback处理查询结果,并且将result传递给了selectCallback
return result;
}
private:
// SQLite查询结果回调函数------注意:只有recovery()函数才会使用到这个回调函数,所以这个函数是为recovery()特制的
// arg: 用户自定义参数,此处为ExchangeMap指针,我们在上面传入了&result,把所有的数据都
// numcol: 返回的列数
// row: 当前行的每一列的数据
// fields: 记录了每一列的名称
static int selectCallback(void *arg, int numcol, char **row, char **fields)
{
ExchangeMap *result = (ExchangeMap *)arg;
auto exp = std::make_shared<Exchange>(); // 创建新Exchange对象
exp->name = row[0]; // 名称
exp->type = (mymq::ExchangeType)std::stoi(row[1]); // 这里可是交换机类型:mymq::ExchangeType,这里进行了类型转换
exp->durable = (bool)std::stoi(row[2]); // 持久化标志
exp->auto_delete = (bool)std::stoi(row[3]); // 自动删除标志
if (row[4]) // 说明存在其他参数
{
exp->setArgs(row[4]); // 解析参数字符串
}
// 这里我们就把临时对象的数据添加进我们传进来的这个ExchangeMap里面
result->insert(std::make_pair(exp->name, exp)); // 插入到映射表:[交换机名称,交换机对象]
return 0;
}
mymq::SqliteHelper _sql_helper; // SQLite数据库操作辅助类实例
};
// 3. 交换机数据内存管理类
// 管理内存中的交换机数据,协调内存与持久化存储的同步
class ExchangeManager
{
public:
using ptr = std::shared_ptr<ExchangeManager>; // 智能指针别名
// 构造函数,初始化持久化映射器并恢复数据
// dbfile: 数据库文件路径
ExchangeManager(const std::string &dbfile) : _mapper(dbfile)
{
_exchanges = _mapper.recovery(); // 将数据库里面的exchange_table表里面的所有交换机数据加载到内存
}
// 声明(创建或获取)交换机
bool declareExchange(const std::string &name, // 交换机名称
mymq::ExchangeType type, // 交换机类型
bool durable, // 持久化标志
bool auto_delete, // 自动删除标志
std::unordered_map<std::string, std::string> args)
{
std::unique_lock<std::mutex> lock(_mutex); // 加锁保证线程安全
auto it = _exchanges.find(name); // 交换机已经在内存中存在
if (it != _exchanges.end())
{
// 如果交换机已存在,直接返回成功
return true;
}
auto exp = std::make_shared<Exchange>(name, type, durable, auto_delete, args); // 创建Exchange临时对象
if (durable == true)
{ // 持久化标志为true
// 如果交换机需要持久化,先写入数据库
bool ret = _mapper.insert(exp);
if (ret == false)
{
return false; // 数据库写入失败
}
}
_exchanges.insert(std::make_pair(name, exp)); // 添加到内存映射表
return true;
}
// 删除指定名称的交换机
void deleteExchange(const std::string &name)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _exchanges.find(name); // 查询交换机是不是存在
if (it == _exchanges.end())
{ // 没找到
return; // 交换机不存在,直接返回
}
if (it->second->durable == true) // 持久化存储标志为true,才需要去数据库删除,否则只需要在内存中删除即可
{
_mapper.remove(name); // 从数据库删除持久化数据
}
_exchanges.erase(name); // 从内存映射表删除
}
// 获取指定名称的交换机对象
// Exchange::ptr是一个std::shared_ptr<Exchange>
Exchange::ptr selectExchange(const std::string &name)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _exchanges.find(name); // 查询交换机是不是存在
if (it == _exchanges.end())
{ // 没找到
return Exchange::ptr(); // 返回空智能指针
}
return it->second; // 返回交换机对象
}
// 判断指定名称的交换机是否存在
bool exists(const std::string &name)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _exchanges.find(name);
if (it == _exchanges.end())
{
return false;
}
return true;
}
// 获取当前内存中的交换机数量
size_t size()
{
std::unique_lock<std::mutex> lock(_mutex);
return _exchanges.size();
}
// 清理所有交换机数据(从内存和数据库)
void clear()
{
std::unique_lock<std::mutex> lock(_mutex);
_mapper.removeTable(); // 删除数据库表(包括所有数据)
_exchanges.clear(); // 清空内存映射表
}
private:
std::mutex _mutex; // 互斥锁,保证线程安全
ExchangeMapper _mapper; // 持久化管理器实例
ExchangeMap _exchanges; // 内存中的交换机映射表,注意ExchangeMap其实是std::unordered_map<std::string, Exchange::ptr>;
};
}
#endif
六.交换机数据管理测试

接下来我们就借助这个GTest来对这个进行测试
测试插入数据,查询数据
cpp
#include "../../mqserver/exchange.hpp"
#include "../../mqcommon/logger.hpp"
#include "../../mqcommon/helper.hpp"
#include<gtest/gtest.h>
//ExchangeManager::ptr其实是std::shared_ptr<ExchangeManager>
mymq::ExchangeManager::ptr emp;
//全局事件
class ExchangeTest : public testing::Environment {
public:
// 在所有测试开始之前执行
virtual void SetUp() override {
emp=std::make_shared<mymq::ExchangeManager>("./data/test.db");//创建数据库文件,并做好初始化
}
// 在所有测试结束之后执行
virtual void TearDown() override {
emp->clear();//清理所有交换机数据
}
};
//插入数据测试用例
TEST(exchange_test,insert_test)
{
//往数据库表里面插入几条条交换机的数据
std::unordered_map<std::string,std::string> map={{"k1","v1"},{"k2","v2"}};
emp->declareExchange("exchange1",mymq::ExchangeType::DIRECT,true,false,map);
emp->declareExchange("exchange2",mymq::ExchangeType::DIRECT,true,false,map);
emp->declareExchange("exchange3",mymq::ExchangeType::DIRECT,true,false,map);
emp->declareExchange("exchange4",mymq::ExchangeType::DIRECT,true,false,map);
ASSERT_EQ(emp->size(),4);//判断当前数据库内的数据是不是有4条
}
//查询数据测试用例
TEST(exchange_test,select_test)
{
//查询exchange3的信息
mymq::Exchange::ptr exp=emp->selectExchange("exchange3");
ASSERT_EQ(exp->name,"exchange3");
ASSERT_EQ(exp->durable,true);
ASSERT_EQ(exp->auto_delete,false);
ASSERT_EQ(exp->type,mymq::ExchangeType::DIRECT);
ASSERT_EQ(exp->getArgs(),std::string("k2=v2&k1=v1&"));
}
int main(int argc, char *argv[]) {
testing::InitGoogleTest(&argc, argv);
testing::AddGlobalTestEnvironment(new ExchangeTest()); // 注册全局环境
return RUN_ALL_TESTS();
}


测试是一点问题都没有。


也可以看到,整个测试执行完之后,数据库里面什么表都没有
删除测试
cpp
#include "../../mqserver/exchange.hpp"
#include "../../mqcommon/logger.hpp"
#include "../../mqcommon/helper.hpp"
#include<gtest/gtest.h>
//ExchangeManager::ptr其实是std::shared_ptr<ExchangeManager>
mymq::ExchangeManager::ptr emp;
//全局事件
class ExchangeTest : public testing::Environment {
public:
// 在所有测试开始之前执行
virtual void SetUp() override {
emp=std::make_shared<mymq::ExchangeManager>("./data/test.db");//创建数据库文件,并做好初始化
//往里面插入一条数据
std::unordered_map<std::string,std::string> map={{"k1","v1"},{"k2","v2"}};
emp->declareExchange("exchange1",mymq::ExchangeType::DIRECT,true,false,map);
}
// 在所有测试结束之后执行
virtual void TearDown() override {
emp->clear();//清理所有交换机数据
}
};
//删除数据测试用例
TEST(exchange_test,delete_test)
{
emp->deleteExchange("exchange1");
//查询exchange3的信息
mymq::Exchange::ptr exp=emp->selectExchange("exchange1");
ASSERT_EQ(exp,nullptr);
}
int main(int argc, char *argv[]) {
testing::InitGoogleTest(&argc, argv);
testing::AddGlobalTestEnvironment(new ExchangeTest()); // 注册全局环境
return RUN_ALL_TESTS();
}


恢复数据测试
cpp
#include "../../mqserver/exchange.hpp"
#include "../../mqcommon/logger.hpp"
#include "../../mqcommon/helper.hpp"
#include<gtest/gtest.h>
//ExchangeManager::ptr其实是std::shared_ptr<ExchangeManager>
mymq::ExchangeManager::ptr emp;
//全局事件
class ExchangeTest : public testing::Environment {
public:
// 在所有测试开始之前执行
virtual void SetUp() override {
emp=std::make_shared<mymq::ExchangeManager>("./data/test.db");//创建数据库文件,并做好初始化
}
};
//插入数据测试用例
TEST(exchange_test,insert_test)
{
//往数据库表里面插入几条条交换机的数据
std::unordered_map<std::string,std::string> map={{"k1","v1"},{"k2","v2"}};
emp->declareExchange("exchange1",mymq::ExchangeType::DIRECT,true,false,map);
emp->declareExchange("exchange2",mymq::ExchangeType::DIRECT,true,false,map);
emp->declareExchange("exchange3",mymq::ExchangeType::DIRECT,true,false,map);
emp->declareExchange("exchange4",mymq::ExchangeType::DIRECT,true,false,map);
ASSERT_EQ(emp->size(),4);//判断当前数据库内的数据是不是有4条
}
//查询数据测试用例
TEST(exchange_test,select_test)
{
//查询exchange3的信息
mymq::Exchange::ptr exp=emp->selectExchange("exchange3");
ASSERT_EQ(exp->name,"exchange3");
ASSERT_EQ(exp->durable,true);
ASSERT_EQ(exp->auto_delete,false);
ASSERT_EQ(exp->type,mymq::ExchangeType::DIRECT);
ASSERT_EQ(exp->getArgs(),std::string("k2=v2&k1=v1&"));
}
int main(int argc, char *argv[]) {
testing::InitGoogleTest(&argc, argv);
testing::AddGlobalTestEnvironment(new ExchangeTest()); // 注册全局环境
return RUN_ALL_TESTS();
}

我们去数据库看看

数据都还在啊!!
那么我们把这个插入数据的测试删除掉,看看后面是不是能加载进来
cpp
#include "../../mqserver/exchange.hpp"
#include "../../mqcommon/logger.hpp"
#include "../../mqcommon/helper.hpp"
#include<gtest/gtest.h>
//ExchangeManager::ptr其实是std::shared_ptr<ExchangeManager>
mymq::ExchangeManager::ptr emp;
//全局事件
class ExchangeTest : public testing::Environment {
public:
// 在所有测试开始之前执行
virtual void SetUp() override {
emp=std::make_shared<mymq::ExchangeManager>("./data/test.db");//创建数据库文件,并做好初始化
}
};
//查询数据测试用例
TEST(exchange_test,select_test)
{
//查询exchange3的信息
mymq::Exchange::ptr exp=emp->selectExchange("exchange3");
ASSERT_EQ(exp->name,"exchange3");
ASSERT_EQ(exp->durable,true);
ASSERT_EQ(exp->auto_delete,false);
ASSERT_EQ(exp->type,mymq::ExchangeType::DIRECT);
ASSERT_EQ(exp->getArgs(),std::string("k2=v2&k1=v1&"));
}
int main(int argc, char *argv[]) {
testing::InitGoogleTest(&argc, argv);
testing::AddGlobalTestEnvironment(new ExchangeTest()); // 注册全局环境
return RUN_ALL_TESTS();
}

没有一点问题,这就说明加载到了历史数据。