项目文件结构
bash
bitmq/
|-- mqdemo
|-- mqclient
|-- mqcommon
|-- mqserver
|-- mqtest
|-- mqthird
- mqdemo:编写一些功能用例时所在的目录
- mqcommon: 公共模块代码(线程池,数据库访问,文件访问,日志打印,pb相关,以及其他的一些琐碎功能模块代码)
- mqclient: 客户端模块代码
- mqserver: 服务器模块代码
- mqtest: 单元测试
- mqthird: 用到的第三方库存放目录
日志打印工具实现
cpp
#include <iostream>
#include <ctime>
#include <cstdio>
// 日志级别
#define DBG_LEVEL 0
#define INF_LEVEL 1
#define ERR_LEVEL 2
#define DEFAULT_LEVEL DBG_LEVEL
// 颜色宏定义
#define COLOR_NONE "\033[0m"
#define COLOR_DBG "\033[33m" // 黄色
#define COLOR_INF "\033[32m" // 绿色
#define COLOR_ERR "\033[31m" // 红色
#define LOG(lev_color, lev_str, level, format,...) \
do{ \
if(level >= DEFAULT_LEVEL){ \
time_t t = time(nullptr); \
struct tm *ptm = localtime(&t); \
char time_str[32]; \
strftime(time_str,31,"%H:%M:%S",ptm); \
printf("[%s%s%s][%s][%s:%d]\t" format "\n",\
lev_color, lev_str, COLOR_NONE ,time_str, __FILE__, __LINE__, ##__VA_ARGS__); \
} \
}while(0)
#define DLOG(format,...) LOG(COLOR_DBG, "DBG", DBG_LEVEL, format,##__VA_ARGS__)
#define ILOG(format,...) LOG(COLOR_INF, "INF", INF_LEVEL, format,##__VA_ARGS__)
#define ELOG(format,...) LOG(COLOR_ERR, "ERR", ERR_LEVEL, format,##__VA_ARGS__)
int main(){
DLOG("调试日志 hello");
ILOG("普通日志 num = %d", 666);
ELOG("错误日志 出错啦");
return 0;
}
运行结果:

很好,那么我们就可以封装成头文件形式:
cpp
#ifndef __M_LOG_H__
#define __M_LOG_H__
#include <iostream>
#include <ctime>
#include <cstdio>
// 日志级别
#define DBG_LEVEL 0
#define INF_LEVEL 1
#define ERR_LEVEL 2
#define DEFAULT_LEVEL DBG_LEVEL
// 颜色宏定义
#define COLOR_NONE "\033[0m"
#define COLOR_DBG "\033[33m" // 黄色
#define COLOR_INF "\033[32m" // 绿色
#define COLOR_ERR "\033[31m" // 红色
#define LOG(lev_color, lev_str, level, format,...) \
do{ \
if(level >= DEFAULT_LEVEL){ \
time_t t = time(nullptr); \
struct tm *ptm = localtime(&t); \
char time_str[32]; \
strftime(time_str,31,"%H:%M:%S",ptm); \
printf("[%s%s%s][%s][%s:%d]\t" format "\n",\
lev_color, lev_str, COLOR_NONE ,time_str, __FILE__, __LINE__, ##__VA_ARGS__); \
} \
}while(0)
#define DLOG(format,...) LOG(COLOR_DBG, "DBG", DBG_LEVEL, format,##__VA_ARGS__)
#define ILOG(format,...) LOG(COLOR_INF, "INF", INF_LEVEL, format,##__VA_ARGS__)
#define ELOG(format,...) LOG(COLOR_ERR, "ERR", ERR_LEVEL, format,##__VA_ARGS__)
#endif
工具类实现
sqlite 基础操作类
根据之前的sqlite3基础使用里封装的代码,稍加改造即可:
cpp
class SqliteHelper{
public:
typedef int(*SqliteCallback)(void*,int,char**,char**);
SqliteHelper(const std::string &dbfile) :_dbfile(dbfile),_handler(nullptr){}
bool open(int safe_level = SQLITE_OPEN_FULLMUTEX){
int ret = sqlite3_open_v2(_dbfile.c_str(),&_handler,SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | safe_level,nullptr);
if(ret != SQLITE_OK){
ELOG("创建/打开sqlite数据库失败:%s",sqlite3_errmsg(_handler));
return false;
}
return true;
}
bool exec(const std::string &sql,SqliteCallback cb,void*arg){
int ret = sqlite3_exec(_handler,sql.c_str(),cb,arg,nullptr);
if(ret != SQLITE_OK){
ELOG("%s 执行语句失败: %s",sql.c_str(),sqlite3_errmsg(_handler));
return false;
}
return true;
}
bool close(){
if(_handler) {
sqlite3_close_v2(_handler);
return true;
}
return false;
}
private:
std::string _dbfile;
sqlite3 *_handler;
};
字符串操作类
实际上我们只需要实现一个字符串分割的功能,根据传入的字符串和分割字符串,将字符串分割成子字符串然后放进vector中:
cpp
#include <iostream>
#include <string>
#include <vector>
class StrHelper{
public:
static size_t split(const std::string &str, const std::string &sep, std::vector<std::string> &result){
size_t pos,idx = 0,len = str.size(),seplen = sep.size();
while(idx < len){
pos = str.find(sep,idx);
if(pos == std::string::npos){
result.emplace_back(str.substr(idx));
break;
}else if(pos == idx){
idx += seplen;
continue;
}else{
result.emplace_back(str.substr(idx,pos-idx));
idx = pos + seplen;
}
}
return result.size();
}
};
int main(){
std::string str = "...news...music.#.pop...";
std::vector<std::string> arry;
int n = StrHelper::split(str,".",arry);
for(auto &s:arry){
std::cout<<s<<std::endl;
}
return 0;
}
运行结果:
bash
news
music
#
pop
UUID 生成器类
UUID(Universally Unique Identifier),即通用唯一识别码 ,由 32 位 16 进制数字组成。
标准格式以连字符分为五段,采用 8-4-4-4-12 的结构,示例:
550e8400-e29b-41d4-a716-446655440000
本项目 UUID 生成规则:
- 采用 16 字节数组 生成 32 位 16 进制字符串
- 前 8 字节:生成随机数字
- 后 8 字节:使用有序序号
- 组合后确保全局唯一,同时可通过序号区分数据顺序
具体实现:
cpp
#include <iostream>
#include <random>
#include <sstream>
#include <iomanip>
#include <atomic>
static std::string uuid() {
static std::mt19937_64 generator(std::random_device{}()); // random_device机器随机数,效率低,当作种子,生成伪随机数
std::uniform_int_distribution<int> dist(0, 255);
std::stringstream ss;
ss << std::hex << std::setfill('0');
for (int i = 0; i < 8; ++i) {
ss << std::setw(2) << dist(generator);
if (i == 3 || i == 5 || i == 7) {
ss << '-';
}
}
static std::atomic<size_t> seq(1);
size_t num = seq.fetch_add(1);
for (int i = 7; i >= 0; --i) {
ss << std::setw(2) << ((num >> (i * 8)) & 0xFF);
if (i == 6) ss << "-";
}
return ss.str();
}
int main(){
for(int i = 0; i < 20; ++i){
std::cout << uuid() << std::endl;
}
return 0;
}
输出结果:
bash
4b0dfed8-c0b2-1432-0000-000000000001
d8f17d63-5976-daea-0000-000000000002
e48feafa-4353-e05c-0000-000000000003
3eda3cab-7f53-bd7e-0000-000000000004
d50c80cd-4738-d73d-0000-000000000005
f26f8f65-7b66-1492-0000-000000000006
a708727f-51d7-85ee-0000-000000000007
ca8caead-223d-ce69-0000-000000000008
07ebec5f-9a70-94fa-0000-000000000009
e657db76-cb5a-08a2-0000-00000000000a
e5105c92-0ccd-a9ff-0000-00000000000b
b93b9fda-315a-e177-0000-00000000000c
873dc6a0-0f77-2df3-0000-00000000000d
c81ec758-6104-9d36-0000-00000000000e
5d7f240e-92b8-9d02-0000-00000000000f
ee1bfde5-6d40-d539-0000-000000000010
734d0028-f3e5-e448-0000-000000000011
1f870f3b-b430-08c8-0000-000000000012
946df344-3de2-bacb-0000-000000000013
2579d0a0-dcd6-550e-0000-000000000014
文件基础操作类
我们需要实现一些文件的基础操作:
a. 文件是否存在判断
b. 文件大小获取
c. 文件读 / 写
d. 文件创建 / 删除
e. 目录创建 / 删除
cpp
class FileHelper{
public:
FileHelper(const std::string& filename):_filename(filename){}
bool exists(){
struct stat st;
return (stat(_filename.c_str(),&st) == 0);
}
size_t size(){
struct stat st;
int ret = stat(_filename.c_str(),&st);
if(ret < 0){
return 0;
}else{
return st.st_size;
}
}
bool read(char *body, size_t offset, size_t len){
// 1.打开文件
std::ifstream ifs(_filename,std::ios::binary | std::ios::in);
if(ifs.is_open() == false){
ELOG("%s 文件打开失败!",_filename.c_str());
return false;
}
// 2.跳转文件指定位置
ifs.seekg(offset,std::ios::beg);
// 3.读取文件数据
ifs.read(body,len);
if(ifs.good() == false){
ELOG("%s 文件读取失败!!",_filename.c_str());
ifs.close();
return false;
}
// 4.关闭文件
ifs.close();
return true;
}
bool read(std::string &body){
// 获取文件大小,根据文件大小调整body空间
size_t fsize = this->size();
body.resize(fsize);
return read(&body[0],0,fsize);
}
bool write(const char* body, size_t offset,size_t len){
// 1.打开文件
std::fstream fs(_filename,std::ios::binary | std::ios::in | std::ios::out);
if(fs.is_open() == false){
ELOG("%s 文件打开失败!",_filename.c_str());
return false;
}
// 2.跳转文件指定位置
fs.seekp(offset,std::ios::beg);
// 3.写入数据
fs.write(body,len);
if(fs.good() == false){
ELOG("%s 写入文件数据失败!!",_filename.c_str());
fs.close();
return false;
}
// 4.关闭文件
fs.close();
return true;
}
bool write(const std::string &body){
return write(&body[0],0,body.size());
}
bool rename(const std::string &nname){
return (::rename(_filename.c_str(),nname.c_str()) == 0);
}
static std::string parentDirectory(const std::string &filename){
size_t pos = filename.find_last_of("/");
if(pos == std::string::npos){
return "./";
}
std::string path = filename.substr(0,pos + 1);
return path;
}
static bool createFile(const std::string &filename){
std::fstream ofs(filename,std::ios::binary | std::ios::out);
if(ofs.is_open() == false){
ELOG("%s 文件打开失败!",filename.c_str());
return false;
}
ofs.close();
return true;
}
static bool removeFile(const std::string &filename){
return (::remove(filename.c_str()) == 0);
}
static bool createDirectory(const std::string &path){
size_t pos,idx = 0,len = path.size();
while(idx < len){
pos = path.find("/",idx);
if(pos == std::string::npos){
return (mkdir(path.c_str(),0775) == 0);
}
std::string subpath = path.substr(0,pos);
int ret = mkdir(subpath.c_str(),0775);
if(ret != 0 && errno != EEXIST){
ELOG("创建目录 %s 失败:%s",subpath.c_str(),strerror(errno));
return false;
}
idx = pos + 1;
}
return true;
}
static bool removeDirectory(const std::string &path){
std::string cmd = "rm -rf " + path;
return (system(cmd.c_str()) != -1);
}
private:
std::string _filename;
};
编写Proto文件
在自研轻量 MQ 项目开发中,采用 Protobuf3 定义统一数据协议,规范消息序列化格式,适配网络传输与本地持久化场景,保证多模块数据交互统一。
协议文件指定 proto3 语法与专属包名 Fy_mq,隔离命名空间,避免命名冲突。
结合消息队列核心能力,定义两组枚举类型。交换机类型包含直连、广播、主题三种主流路由模式,并设置默认未知类型做异常兜底。投递模式区分非持久化与持久化消息,用来控制磁盘落地策略,保障消息可靠性。
通过 BasicProperties 结构体封装消息基础属性,包含唯一消息ID、投递模式与路由键,为交换机转发、消息管理提供核心参数。
顶层设计嵌套式消息结构,内部 Payload 承载消息属性、内容与有效性标识。其中有效性字段采用字符串格式,依托天然优势保证跨平台解析兼容。额外增加文件偏移与消息长度字段,适配持久化文件读写,实现快速定位与数据校验。
该协议结构分层清晰,兼顾业务投递与存储底层需求,借助 protoc 编译生成 C++ 代码,可快速完成消息序列化与反序列化,简化开发,提升项目整体规范性。
protobuf
syntax = "proto3";
package Fy_mq;
enum ExchangeType {
UNKNOWTYPE = 0;
DIRECT = 1;
FANOUT = 2;
TOPIC = 3;
};
enum DeliverMode{
UNKNOWMODE = 0;
UNDURABLE = 1;
DURABLE = 2;
};
message BasicProperties{
string id = 1;
DeliverMode deliver_mode = 2;
string routing_key = 3;
};
message Message{
message Payload{
BasicProperties properties = 1;
string body = 2;
string valid = 3;
};
Payload payload = 1;
uint32 offset = 2;
uint32 length = 3;
};
交换机数据管理
交换机模块是自研轻量 MQ 中路由分发的核心核心模块 ,整体采用三层分层设计 :交换机实体结构、SQLite 持久化映射层、线程安全内存管理层。模块依托 Protobuf 枚举类型 规范路由类型,结合本地数据库落地存储,实现交换机实例的内存高效管理、断电持久化恢复、多线程并发安全三大核心能力。
交换机实体结构封装
通过结构体抽象交换机完整属性,统一承载运行时核心数据,包含基础属性 与扩展参数两大模块。
- 基础属性:交换机名称、路由类型、持久化标记、自动删除标记
- 扩展参数:采用
google::protobuf::Map键值对容器存储,适配跨平台数据交互需求
提供参数序列化与反序列化 接口,将扩展参数按照 key=val&key=val 规则拼接与解析,同时对非法格式字符串做日志告警与容错跳过,避免非法数据导致程序异常。
cpp
struct Exchange{
using ptr = std::shared_ptr<Exchange>;
//1.交换机名称
std::string name;
//2.交换机类型
ExchangeType type;
//3.交换机持久化标记
bool durable;
//4.是否自动删除标记
bool auto_delete;
//5.其他参数
google::protobuf::Map<std::string,std::string> args;
Exchange(){}
Exchange(const std::string &ename,
ExchangeType etype,
bool edurable,
bool eauto_delete,
const google::protobuf::Map<std::string,std::string> &eargs)
:name(ename),type(etype),durable(edurable),auto_delete(eauto_delete),args(eargs){}
//args存储键值对,在存储数据库的时候,会组织一个格式字符串进行存储 key=val&key=val....
//内部解析str_args字符串,将内容存储到成员中
void SetArgs(const std::string &str_args){
std::vector<std::string> sub_args;
StrHelper::split(str_args,"&",sub_args);
for(auto &str : sub_args){
size_t pos = str.find("=");
if( pos == std::string::npos){
ELOG("交换机解析格式非法:%s",str.c_str());
continue;//跳过非法格式
}
std::string key = str.substr(0,pos);
std::string value = str.substr(pos+1);
args[key] = value;
}
}
std::string getArgs(){
std::string result;
for(auto &it:args){
result += it.first + "=" + it.second + "&";
}
return result;
}
};
交换机持久化映射层
ExchangeMapper 作为数据库交互专属层 ,完全隔离底层 SQL 操作,专注实现交换机数据的落地存储与重启恢复。
构造时自动完成目录检测创建、数据库打开、数据表初始化,保障环境可用性;封装建表、删表、插入、删除、数据恢复全套操作。
- 数据表以交换机名称为主键,保证实例全局唯一
- 数值类型字段统一转为整型入库,适配 SQLite 弱类型存储特性
- 查询回调中增加空指针边界判断,杜绝数据库 NULL 字段引发崩溃
- 服务启动时执行
recovery方法,自动加载磁盘持久化数据至内存,实现数据持久化闭环
cpp
using ExchangeMap = std::unordered_map<std::string,Exchange::ptr>;//内存管理用
class ExchangeMapper{
public:
ExchangeMapper(const std::string &dbfile):_sql_helper(dbfile){
std::string path = FileHelper::parentDirectory(dbfile);
FileHelper::createDirectory(path);
assert(_sql_helper.open());
createTable();
}
void createTable(){
#define CREATE_TABLE "create table if not exists exchange_table(\
name varchar(32) primary key,\
type int,\
durable int,\
auto_delete int,\
args varchar(128));"
bool ret = _sql_helper.exec(CREATE_TABLE,nullptr,nullptr);
if(ret == false){
DLOG("创建交换机数据库表失败!");
abort();
}
}
void removeTable(){
#define 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){
std::stringstream ss;
ss << "insert into exchange_table (name, type, durable, auto_delete, args) values(";
ss << "'" << exp->name << "',";
ss << "'" << (int)exp->type << "',";
ss << "'" << (int)exp->durable << "',";
ss << "'" << (int)exp->auto_delete << "',";
ss << "'" << exp->getArgs() << "');";
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 << "';";
_sql_helper.exec(ss.str(),nullptr,nullptr);
}
ExchangeMap recovery(){
ExchangeMap result;
std::string sql = "select name,type,durable,auto_delete,args from exchange_table;";
_sql_helper.exec(sql,selectCallback,&result);
return result;
}
private:
static int selectCallback(void *arg,int numcol,char** row,char** fields){
if (!row[0] || !row[1] || !row[2] || !row[3]) return 0;
ExchangeMap *result = (ExchangeMap*)arg;
auto exp = std::make_shared<Exchange>();
exp->name = row[0];
exp->type = (Fy_mq::ExchangeType)std::stoi(row[1]);
exp->durable = (bool)std::stoi(row[2]);
exp->auto_delete = (bool)std::stoi(row[3]);
if(row[4]) exp->SetArgs(row[4]);
result->insert(std::make_pair(exp->name,exp));
return 0;
}
private:
SqliteHelper _sql_helper;
};
交换机内存管理层
ExchangeManager 是业务上层直接调用的管理入口 ,核心负责:多线程安全控制、内存容器管理、内存与数据库联动。
- 并发安全 :所有增删查改接口全部加
std::unique_lock锁保护,杜绝多线程资源竞争问题 - 幂等声明 :声明交换机时优先检索内存容器,已存在直接返回,避免重复创建
- 持久化联动 :仅对
durable标记为 true 的交换机执行数据库写入与删除,区分临时/持久化实例 - 资源管理 :基于
std::shared_ptr智能指针托管生命周期,内存自动回收 - 调试适配 :
clear方法为开发调试定制,支持一键清空内存+删除数据表,方便本地测试
cpp
class ExchangeManager{
public:
using ptr = std::shared_ptr<ExchangeManager>;
ExchangeManager(const std::string &dbfile): _mapper(dbfile){
_exchanges = _mapper.recovery();
}
//声明交换机
bool declareExchange(const std::string&name,
ExchangeType type,bool durable,bool auto_delete,
const google::protobuf::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);
if(durable == 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) _mapper.remove(name);
_exchanges.erase(name);
}
//获取指定交换机
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;
}
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;
};
测试代码
cpp
#include "../mqserver/mq_exchange.hpp"
#include <gtest/gtest.h>
Fy_mq::ExchangeManager::ptr emp;
class ExchangeTest : public testing::Environment{
public:
virtual void SetUp() override{
emp = std::make_shared<Fy_mq::ExchangeManager>("./data/meta.db");
}
virtual void TearDown() override{
emp->clear();
DLOG("最后的清理!!\n");
}
};
TEST(exchange_test, insert_test) {
google::protobuf::Map<std::string, std::string> map;
map["k1"] = "v1";
map["k2"] = "v2";
emp->declareExchange("exchange1", Fy_mq::ExchangeType::DIRECT, true, false, map);
emp->declareExchange("exchange2", Fy_mq::ExchangeType::DIRECT, true, false, map);
emp->declareExchange("exchange3", Fy_mq::ExchangeType::DIRECT, true, false, map);
emp->declareExchange("exchange4", Fy_mq::ExchangeType::DIRECT, true, false, map);
ASSERT_EQ(emp->size(), 4);
}
TEST(exchange_test, remove_test) {
emp->deleteExchange("exchange3");
Fy_mq::Exchange::ptr exp = emp->selectExchange("exchange3");
ASSERT_EQ(exp.get(), nullptr);
ASSERT_EQ(emp->exists("exchange3"), false);
}
TEST(exchange_test, select_test) {
ASSERT_EQ(emp->exists("exchange1"), true);
ASSERT_EQ(emp->exists("exchange2"), true);
ASSERT_EQ(emp->exists("exchange3"), false);
ASSERT_EQ(emp->exists("exchange4"), true);
Fy_mq::Exchange::ptr exp = emp->selectExchange("exchange2");
ASSERT_NE(exp.get(), nullptr);
ASSERT_EQ(exp->name, "exchange2");
ASSERT_EQ(exp->durable, true);
ASSERT_EQ(exp->auto_delete, false);
ASSERT_EQ(exp->type, Fy_mq::ExchangeType::DIRECT);
ASSERT_EQ(exp->args["k1"], "v1");
ASSERT_EQ(exp->args["k2"], "v2");
}
int main(int argc,char*argv[]){
testing::InitGoogleTest(&argc,argv);
testing::AddGlobalTestEnvironment(new ExchangeTest);
return RUN_ALL_TESTS();
}
运行结果:

模块整体设计亮点
- 职责解耦:数据结构、持久化操作、业务管理三层完全拆分,低耦合易维护
- 容错性强:参数解析、数据库查询、SQL 执行全链路异常拦截与日志提示
- 工程化规范:智能指针托管、互斥锁并发保护、枚举约束业务类型,适配后端服务开发标准
- 可扩展性强:扩展参数容器预留自定义配置能力,可后期快速拓展交换机附加功能