项目扩展五:交互式:command-line interface版本的实现

项目扩展五:command-line interface版本的实现

  • 一、CLI交互的设计
    • 1.为何要设计这个CLI交互
    • 2.具体设计
      • 1.启动服务
      • 2.选择信道
      • 3.选择虚拟机
      • 4.正式业务
        • 注意:
          • 1.消费者与生产者跟信道的关系
          • 2.消息处理回调函数的问题
          • 3.消息确认的问题
      • 5.其他功能
        • 1.打印功能
        • 2.查看消息功能
        • 3.其他
      • 6.手册
  • 二.ThreadChannel结构体的实现
    • 1.生产消费模型
    • 2.关联的消费者和生产者
    • 3.线程函数
      • 1.回调呢?
      • 2.主从线程如何同步的
    • 4.退出函数
    • 5.完整代码
  • 三、VirtualHostElem
  • 四、GlobalResource
    • 1.基础成员
    • 2.恢复历史数据
    • 3.网络通信
      • 1.proto文件修改
      • 2.服务器方面的修改
        • 1.serializeToString
        • 2.虚拟机模块修改
        • 3.信道模块
        • 4.broker服务器模块
      • 3.客户端模块的修改
        • 1.信道模块
          • 1.回调函数
          • 2.信道模块
          • 3.连接模块
    • 4.climq.cc针对恢复历史数据的修改
      • 1.保证同步
      • 2.四个回调函数
      • 3.构造函数
  • 五、正则表达式解决字符串匹配与查找问题
    • 1.正则表达式简单介绍
    • 2.C++11 regex库简单介绍
    • 3.C++11 原始字符串字面量简单介绍
    • 4.示例
      • 1.匹配` man `字符串
      • 2.匹配` use channel_name = 信道名 `字符串
      • 3. (direct|fanout|topic)
    • 5.完善
  • 六、具体功能的实现
    • 1.命令相关
      • 1.man
      • 2.clear
      • 3.quit
    • 2.信道相关
      • 1.使用信道
      • 2.取消对信道的使用
    • 3.虚拟机相关
      • 1.声明虚拟机
      • 2.删除虚拟机
      • 3.使用虚拟机
      • 4.取消对虚拟机的使用
      • 5.显示所有虚拟机信息
      • 6.打印指定虚拟机信息
    • 4.交换机相关
      • 1.声明交换机
      • 2.删除交换机
      • 3.显示所有交换机信息
      • 4.显示指定交换机信息
    • 5.队列相关
      • 1.声明队列
      • 2.删除队列
      • 3.显示所有队列信息
      • 4.显示指定队列信息
    • 6.绑定相关
      • 1.绑定队列
      • 2.解绑队列
    • 7.订阅相关
      • 1.消费处理回调函数
      • 2.生产确认应答函数
      • 3.消费者订阅
      • 4.消费者取消订阅
      • 5.生产者关联
      • 6.生产者取消关联
    • 8.消息的发布与拉取
      • 1.发布消息
      • 2.拉取消息
      • 3.查看指定消费者消费过的消息
      • 4.查看指定生产者发布过的消息
  • 七、完整代码
  • 八、演示

CLI(command-line interface)命令行界面,类似于我们常用的shell就是采用CLI进行交互的
与之相对的有GUI(图形交互界面,比如我们常用的Windows和手机)

一、CLI交互的设计

1.为何要设计这个CLI交互

RabbitMQ官方支持图形化界面进行操作,诸如创建虚拟机,交换机,发布消息等等操作

而这个图形化界面的实现,后端代码我们好写,但是也要写前端代码,所以我们就不用图形化界面了

而是仿照mysql类似的方法,实现一个CLI命令行界面

2.具体设计

使用步骤:

  1. 启动服务
  2. 使用信道
  3. 使用虚拟机
  4. 在该虚拟机上进行操作
    在这里插入图片描述

一个从线程维护一个信道,从线程负责提供具体服务
主线程负责解释命令行指令,分发任务,打印输出消息

我们选择让主线程打印输出消息是因为:
主线程需要串行化执行所有指令,跟用户交互时,用户输入一个指令,就需要给用户反馈执行情况

1.启动服务

用户启动climq服务时,需要传入想要创建的信道数量num和信道前缀名channel_name_prefix
然后我们创建num个信道,信道名分别为channel_name_prefix1到channel_name_prefixnum

因此这里需要用命令行参数:

// ./climq channel_num channel_name_prefix
int main(int argc, char *argv[])
{if (argc != 3){cout << "Usage: " << argv[0] << " channel_num channel_name_prefix\n";return 1;}return 0;
}

2.选择信道

在这一期间,用户无法执行任何除了选择信道之外的业务操作,因为所有的业务都要由信道提供

3.选择虚拟机

在这一期间,只允许创建虚拟机,选择虚拟机,删除虚拟机,取消对当前信道的选择这些操作

4.正式业务

创建虚拟机,删除虚拟机,取消对当前虚拟机的使用,创建交换机,删除交换机,创建队列,删除队列,绑定队列,解绑队列

订阅队列,取消订阅,关联生产者,取消关联
发布消息和拉取消息

注意:
1.消费者与生产者跟信道的关系

依然跟我们之前所规定的一样:一个信道最多只能关联一个消费者和一个生产者,我们在这里也就不打破这个规定了

2.消息处理回调函数的问题

在这里我们就直接cout打印出来了,不支持用户自定义消费处理回调函数了,想要自定义消费处理回调函数请使用C++代码来进行访问
而不是我们提供的climq工具进行访问

3.消息确认的问题

我们在消费处理回调函数当中就进行ACK了,因此消息无需用户自行ACK,所以消息ACK的功能我们就不提供了

5.其他功能

1.打印功能

RabbitMQ图形化界面当中支持查看虚拟机,交换机,队列等等信息,因此我们的climq也支持打印虚拟机,交换机,队列和绑定信息

2.查看消息功能

我们的climq支持查看指定消费者所消费的所有消息,指定生产者所发布的所有消息

3.其他

为了防止用户在使用时忘记具体指令,我们给一个使用手册,用户输入man即可查看该手册

用户输入回车就是换行,输入clear就是清屏

6.手册

climq用户使用手册一、步骤说明1. 使用信道:- 命令:use channel_name=信道名称
2. 取消使用信道:- 命令:disuse channel
3. 具体命令:(一)虚拟机相关1. 声明虚拟机:- 命令:< create virtual_host=虚拟机名称 >
2. 删除虚拟机:- 命令:< drop virtual_host=虚拟机名称 >
3. 使用虚拟机:- 命令:< use virtual_host=虚拟机名称 >
4. 取消虚拟机的使用:- 命令:< disuse virtual_host >
5. 查看虚拟机信息:- 命令:< show virtual_host_info=虚拟机名称 >
6. 查看所有虚拟机:- 命令:< show all_virtual_host >(二)交换机相关1. 声明交换机:- 命令:< create exchange_name=交换机名称 type=[direct/fanout/topic] durable=[true/false] auto_delete=[true/false] >
2. 删除交换机:- 命令:< drop exchange_name=交换机名称 >
3. 查看交换机信息:- 命令:< show exchange_info=交换机名称 >
4. 查看所有交换机:- 命令:< show all_exchange_info >(三)队列相关1. 声明队列:- 命令:< create queue_name=队列名 durable=[true/false] auto_delete=[true/false] exclusive=[true/false] >
2. 删除队列:- 命令:< drop queue_name=队列名称 >
3. 查看队列信息:- 命令:< show queue_info=队列名称 >
4. 查看所有队列:- 命令:< show all_queue_info >(四)队列绑定与解绑相关1. 绑定队列:- 命令:< bind queue_name=队列名 to exchange_name=交换机名称 with binding_key=绑定键 >
2. 解绑队列:- 命令:< unbind queue_name=队列名 to exchange_name=交换机名称 >
3. 查看所有绑定信息:- 命令:< show all_binding_info >(五)队列订阅与取消订阅相关1. 订阅队列:- 命令:< consume queue_name=队列名 consumer_tag=消费者标识 auto_ack=[true/false] >- 注意: 一个信道只支持关联一个消费者!!
2. 取消订阅队列:- 命令:< cancel this_consume >
3. 关联生产者- 命令:< produce producer_tag=生产者标识 >- 注意: 一个信道只支持关联一个生产者
4. 取消生产者的关联- 命令:< cancel this_produce >(六)消息发布与拉取相关
1. 发布消息:- 命令:< publish message exchange_name=交换机名称 delivery_mode=[durable|undurable] mechanism=消息发布方式[push/pull/both] routing_key=路由键 body=消息主体 >
2. 拉取消息:- 命令:< pull message >(七)查看已接收和已发布消息相关1. 查看已收到的消息:- 命令:< show consume_message consumer_tag=消费者标识 >
2. 查看已发布的消息:- 命令:< show publish_message producer_tag=生产者标识 >
3. 退出:- 命令:quit
4. 清屏:- 命令: clear
5. 换行:- 命令: 回车

二.ThreadChannel结构体的实现

在这里插入图片描述

1.生产消费模型

我们要为每个线程都维护一个任务队列+一个输入队列
主线程将任务打包后放到任务队列
从线程执行完任务之后,将执行情况放入输出队列

这么一个双向的生产消费模型,因此我们需要两个queue,两个互斥锁,两个条件变量
因此,下面这些成员都是需要的

thread worker;queue<ChannelCallback> task_queue;
mutex task_mtx;
condition_variable task_cond;queue<string> info_queue;
mutex info_mtx;
condition_variable info_cond;

那么这个ChannelCallback要搞成什么类型的函数对象呢?
因为所有的业务参数都不尽相同,因此就需要主线程将所有的参数都绑定一下,而信道在从线程内部自行创建的,所以信道这个参数必须由从线程自行传入

因此:
using ChannelCallback = function<void(const Channel::ptr &cp)>;

2.关联的消费者和生产者

一个信道在任意时刻最多只能关联一个生产者和一个消费者

Consumer::ptr _consumer;
Productor::ptr _productor;

但是因为消费者和生产者的关联关系可以解绑,所以一个信道可以关联很多消费者(只需要保证在同一时刻只关联了一个消费者即可)

又因为我们支持打印指定消费者所消费的消息和指定生产者发布的消息
因此,我们还需要:

unordered_map<string, vector<string>> _consume_message_map;  // key: consumer_tag  value: 消费的消息body
unordered_map<string, vector<string>> _producer_message_map; // key: productor_tag  value: 发布的消息body

3.线程函数

线程只需要获取信道池当中的信道,死循环从任务队列当中拿并执行任务,最后归还信道即可

1.回调呢?

到底谁执行回调函数?
回调在连接层面上注册的,是由主线程注册的,因此回调函数On…Callback是由主线程执行的
在这里插入图片描述
但是因为我们把调用对应具体消费处理回调函数的任务打包到一起抛入了线程池当中,因此具体用户注册的回调函数是由异步工作线程执行的

因此从线程无需考虑回调的问题,只需要专心在条件变量那里等,拿任务,执行任务即可

至于将输出消息放到输出队列当中,这个步骤完全可以放入具体任务当中

2.主从线程如何同步的

无非就两种情况:
从线程拿任务时,主线程还没有放任务,等到主线程放任务时,一定会通知从线程,此时从线程便不会错过通知,没任何问题

主线程放完任务通知从线程时,从线程还没开始拿任务,从线程错过通知
但是因为主线程刚刚放完任务,所以任务队列必定不为空

此时等待条件不满足,从线程无需等待,直接拿任务去执行即可

推而广之,多生产多消费,单生产多消费,多生产单消费也没任何问题
因为错过通知则无需等待(因为只要通知,就代表等待条件不满足了)

4.退出函数

当用户退出时,需要清理资源,需要关闭所有信道并退出所有线程
因此,我们需要给主线程提供一个shutdown接口用来清理该ThreadChannel的所有资源

其实就是搞一个bool _isrunning用来标识当前是否需要继续运行
然后置为false并且notify_all两个条件变量即可

注意: shutdown是由主线程调用的,所以无需担心主线程先于从线程退出导致的未定义行为

5.完整代码

struct ThreadChannel
{using ChannelCallback = function<void(const Channel::ptr &cp)>;void push_task(ChannelCallback cb){unique_lock<mutex> ulock(task_mtx);task_queue.push(cb);task_cond.notify_one();}void pop_task(const Channel::ptr &cp){ChannelCallback task;{unique_lock<mutex> ulock(task_mtx);task_cond.wait(ulock, [this]() -> bool{ return !task_queue.empty() || !_isrunning; });if (!_isrunning)return;task = task_queue.front();task_queue.pop();}// 代码健壮性if (task == nullptr)return;task(cp);}void push_info(const std::string &info){unique_lock<mutex> ulock(info_mtx);info_queue.push(info);info_cond.notify_one();}string pop_info(){unique_lock<mutex> ulock(info_mtx);info_cond.wait(ulock, [this]() -> bool{ return !info_queue.empty() || !_isrunning; });if (!_isrunning)return "";string info = info_queue.front();info_queue.pop();return info;}// 停止服务void shutdown(){if (_isrunning){_isrunning = false;task_cond.notify_all();info_cond.notify_all();worker.join();}}thread worker;queue<ChannelCallback> task_queue;mutex task_mtx;condition_variable task_cond;queue<string> info_queue;mutex info_mtx;condition_variable info_cond;Consumer::ptr _consumer;Productor::ptr _productor;bool _isrunning = true;unordered_map<string, vector<string>> _consumer_message_map; // key: consumer_tag  value: 消费的消息bodyunordered_map<string, vector<string>> _producer_message_map; // key: productor_tag  value: 发布的消息body
};

线程函数:

struct GlobalResource
{void thread_routine(const string &channel_name, const Connection::ptr &conn){Channel::ptr cp = conn->getChannel();ThreadChannel &tchannel = _channel_map[channel_name];while (tchannel._isrunning){tchannel.pop_task(cp);}conn->returnChannel(cp);}
};

三、VirtualHostElem

虚拟机是进行资源隔离和整合的一个中间层,只有使用当前虚拟机的用户才能查看当前虚拟机的资源,不能查看其他虚拟机的资源

因此,我们把交换机数据,队列数据,绑定信息数据全都放到VirtualHostElem当中

组织方法就跟我们再写服务器时对交换机,队列,绑定信息的组织方法一样

这里虚拟机,交换机,队列和绑定信息 我们统一搞成string的格式:
因此,需要给他们提供serializeToString的函数

虚拟机:
std::string serializeToString()
{return "dbfile: " + _dbfile + " basedir: " + _basedir;
}交换机:
std::string serializeToString()
{return "type: " + ExchangeType_Name(type) + " durable: " + (durable ? " true " : " false ") + " auto_delete: " + (auto_delete ? " true" : " false");
}队列:
std::string serializeToString()
{return std::string() + "durable: " + (durable ? " true " : " false ") + " auto_delete: " + (auto_delete ? " true " : " false ") + " exclusive: " + (exclusive ? " true" : " false");
}这里因为给了三目表达式,会导致编译器在编译阶段无法成功准确确认表达式的类型,因而operator+不知如何调用
所以我们需要给一个string()来引导编译器成功编译上面交换机无需加string()的原因是ExchangeType_Name的返回值就是string类型绑定信息:
std::string serializeToString()
{return "exchange: " + exchange_name + ", queue: " + queue_name + " binding_key: " + binding_key;
}
struct VirtualHostElem
{// 删除指定交换机的所有绑定信息void removeExchangeBindings(const string &exchange_name){_binding_map.erase(exchange_name);}// 删除指定队列的所有绑定信息void removeQueueBindings(const string &queue_name){for (auto &kv : _binding_map){kv.second.erase(queue_name);}}string vhost_info;unordered_map<string, string> _exchange_map;unordered_map<string, string> _queue_map;unordered_map<string, unordered_map<string, string>> _binding_map; // exchange_name -> queue_name -> binding_info
};

四、GlobalResource

1.基础成员

因为我们势必需要封装出很多函数,而函数全都散开的话,互相调用时就需要考虑定义的前后顺序,甚至需要使用前置声明

所以我们把这些函数统一放到一个类当中,这样就可以随意调用了
这个类:GlobalResource

而这个类需要什么成员呢?

首先他需要一个建立信道名跟ThreadChannel联系的哈希表
和一个建立虚拟机名称和虚拟机资源的哈希表

unordered_map<string, ThreadChannel> _channel_map;
unordered_map<string, VirtualHostElem> _vhost_map;

其次,他需要知道并保存当前正在使用的信道跟虚拟机的名称

string _channel_name;
bool _channel_ok = false;string _vhost_name;
bool _vhost_ok = false;

为了能够退出死循环,需要给一个bool running = true;

2.恢复历史数据

既然我们要把虚拟机,交换机,队列,绑定信息都提供打印功能,而且这些数据都是支持持久化的
因此我们需要在程序启动时就完成客户端历史数据的恢复

所以我们的客户端需要能够查找所有虚拟机信息,指定虚拟机当中所有交换机,队列和绑定信息

因此我们就需要新增网络通信协议,并且实现响应接口

3.网络通信

1.proto文件修改

// 获取所有虚拟机信息
message GetAllVirtualHostInfoRequest
{string req_id = 1;string channel_id = 2;
}// 获取所有交换机信息
message GetAllExchangeInfoRequest
{string req_id = 1;string channel_id = 2;string vhost_name = 3;
}// 获取所有队列信息
message GetAllMsgQueueInfoRequest
{string req_id = 1;string channel_id = 2;string vhost_name = 3;
}// 获取所有绑定信息
message GetAllBindingInfoRequest
{string req_id = 1;string channel_id = 2;string vhost_name = 3;
}// 4. 获取所有虚拟机的响应
message GetAllVirtualHostInfoResponse
{string channel_id = 1;map<string,string> info = 2;
}// 5. 获取所有交换机的响应
message GetAllExchangeInfoResponse
{string channel_id = 1;string vhost_name = 2;map<string,string> info = 3;
}// 6. 获取所有队列的响应
message GetAllMsgQueueInfoResponse
{string channel_id = 1;string vhost_name = 2;map<string,string> info = 3;
}// 7. 获取所有绑定信息的响应
message GetAllBindingInfoResponse
{// 因为protobuf不支持这么嵌套定义map   :   map<string,map<string,string>>// 所以我们这么定义message InnerInfo{map<string,string> inner_info = 1; }string channel_id = 1;string vhost_name = 2;map<string,InnerInfo> info = 3;
}

2.服务器方面的修改

首先,我们要先为虚拟机,交换机,队列,绑定信息各自添加相应的serializeToString函数

1.serializeToString
虚拟机:
std::string serializeToString()
{return "dbfile: " + _dbfile + " basedir: " + _basedir;
}交换机:
std::string serializeToString()
{return "type: " + ExchangeType_Name(type) + " durable: " + (durable ? " true " : " false ") + " auto_delete: " + (auto_delete ? " true" : " false");
}队列:
std::string serializeToString()
{return std::string() + "durable: " + (durable ? " true " : " false ") + " auto_delete: " + (auto_delete ? " true " : " false ") + " exclusive: " + (exclusive ? " true" : " false");
}绑定信息:
std::string serializeToString()
{return "exchange: " + exchange_name + ", queue: " + queue_name + " binding_key: " + binding_key;
}
2.虚拟机模块修改

虚拟机模块的任务依然就是接口复用即可,无需考虑其他的,因为他是业务模块,无需考虑用户(网络模块需要考虑的)

虚拟机模块:

ExchangeMap getAllExchangeInfo()
{return _emp->getAllExchanges();
}MsgQueueMap getAllMsgQueueInfo()
{return _mqmp->getAllMsgQueue();
}BindingMap getAllBindingInfo()
{return _bmp->getAllBindings();
}std::string serializeToString()
{return "dbfile: " + _dbfile + " basedir: " + _basedir;
}

虚拟机管理模块:

// 返回所有虚拟机信息
VirtualHostMap getAllVirtualHostInfo()
{std::unique_lock<std::mutex> ulock(_mutex);return _vhmap;
}ExchangeMap getAllExchangeInfo(const std::string& vname)
{std::ostringstream oss;oss << "获取指定虚拟机当中所有交换机信息失败,因为虚拟机不存在, 虚拟机名称: " << vname << "\n";VirtualHost::ptr vhp = getVirtualHost(vname, oss);if (vhp.get() == nullptr){return ExchangeMap();}return vhp->getAllExchangeInfo();
}MsgQueueMap getAllMsgQueueInfo(const std::string& vname)
{std::ostringstream oss;oss << "获取指定虚拟机当中所有队列信息失败,因为虚拟机不存在, 虚拟机名称: " << vname << "\n";VirtualHost::ptr vhp = getVirtualHost(vname, oss);if (vhp.get() == nullptr){return MsgQueueMap();}return vhp->getAllMsgQueueInfo();
}BindingMap getAllBindingInfo(const std::string& vname)
{std::ostringstream oss;oss << "获取指定虚拟机当中所有绑定信息失败,因为虚拟机不存在, 虚拟机名称: " << vname << "\n";VirtualHost::ptr vhp = getVirtualHost(vname, oss);if (vhp.get() == nullptr){return BindingMap();}return vhp->getAllBindingInfo();
}
3.信道模块

信道模块属于网络模块,他的任务就是拿到req,复用接口,构造resp并返回

在这里需要根据用户的需求,调用交换机,队列,绑定信息的serializeToString函数来进行序列化

using GetAllVirtualHostInfoRequestPtr = std::shared_ptr<GetAllVirtualHostInfoRequest>;
using GetAllExchangeInfoRequestPtr = std::shared_ptr<GetAllExchangeInfoRequest>;
using GetAllMsgQueueInfoRequestPtr = std::shared_ptr<GetAllMsgQueueInfoRequest>;
using GetAllBindingInfoRequestPtr = std::shared_ptr<GetAllBindingInfoRequest>;
void getAllVirtualHostInfo(const GetAllVirtualHostInfoRequestPtr &req)
{// 拿到结果,构造结果,返回响应GetAllVirtualHostInfoResponse resp;resp.set_channel_id(req->channel_id());std::unordered_map<std::string, VirtualHost::ptr> umap = _vhost_manager_ptr->getAllVirtualHostInfo();google::protobuf::Map<std::string, std::string> gmap;for (auto &kv : umap){std::string info = kv.second->serializeToString();gmap[kv.first] = info;}resp.mutable_info()->swap(gmap);_codec->send(_conn, resp);basicResponse(req->req_id(), req->channel_id(), true);
}void getAllExchangeInfo(const GetAllExchangeInfoRequestPtr &req)
{// 拿到结果,构造结果,返回响应GetAllExchangeInfoResponse resp;resp.set_channel_id(req->channel_id());resp.set_vhost_name(req->vhost_name());google::protobuf::Map<std::string, std::string> gmap;ExchangeMap emap = _vhost_manager_ptr->getAllExchangeInfo(req->vhost_name());for (auto &kv : emap){gmap[kv.first] = kv.second->serializeToString();}resp.mutable_info()->swap(gmap);_codec->send(_conn, resp);basicResponse(req->req_id(), req->channel_id(), true);
}void getAllMsgQueueInfo(const GetAllMsgQueueInfoRequestPtr &req)
{// 拿到结果,构造结果,返回响应GetAllMsgQueueInfoResponse resp;resp.set_channel_id(req->channel_id());resp.set_vhost_name(req->vhost_name());google::protobuf::Map<std::string, std::string> gmap;MsgQueueMap mqmp = _vhost_manager_ptr->getAllMsgQueueInfo(req->vhost_name());for (auto &kv : mqmp){gmap[kv.first] = kv.second->serializeToString();}resp.mutable_info()->swap(gmap);_codec->send(_conn, resp);basicResponse(req->req_id(), req->channel_id(), true);
}void getAllBindingInfo(const GetAllBindingInfoRequestPtr &req)
{// 拿到结果,构造结果,返回响应GetAllBindingInfoResponse resp;resp.set_channel_id(req->channel_id());resp.set_vhost_name(req->vhost_name());google::protobuf::Map<std::string, GetAllBindingInfoResponse::InnerInfo> gmap;BindingMap bmp = _vhost_manager_ptr->getAllBindingInfo(req->vhost_name());for (auto &kv : bmp){GetAllBindingInfoResponse::InnerInfo &info = gmap[kv.first];for (auto &elem : kv.second){info.mutable_inner_info()->insert({elem.first, elem.second->serializeToString()});}}resp.mutable_info()->swap(gmap);_codec->send(_conn, resp);basicResponse(req->req_id(), req->channel_id(), true);
}
4.broker服务器模块

对于broker服务器来说,他需要在ProtobufDispatcher事件派发器当中注册针对相应req的回调函数

将对应收到的req调用信道提供的函数即可

// 9. 获取所有虚拟机信息
void OnGetAllVirtualHostInfo(const muduo::net::TcpConnectionPtr &conn, const GetAllVirtualHostInfoRequestPtr &req, muduo::Timestamp)
{// 1. 先看有无该连接Connection::ptr myconn = _connection_manager_ptr->getConnecion(conn);if (myconn.get() == nullptr){default_info("获取所有虚拟机信息时,没有找到连接对应的Connection对象");return;}// 2. 获取信道Channel::ptr mychannel = myconn->getChannel(req->channel_id());if (mychannel.get() == nullptr){default_info("获取所有虚拟机信息失败,因为获取信道失败");return;}// 3. 复用mychannel->getAllVirtualHostInfo(req);
}// 10. 获取所有交换机信息
void OnGetAllExchangeInfo(const muduo::net::TcpConnectionPtr &conn, const GetAllExchangeInfoRequestPtr &req, muduo::Timestamp)
{// 1. 先看有无该连接Connection::ptr myconn = _connection_manager_ptr->getConnecion(conn);if (myconn.get() == nullptr){default_info("获取指定虚拟机对应的所有交换机信息时,没有找到连接对应的Connection对象");return;}// 2. 获取信道Channel::ptr mychannel = myconn->getChannel(req->channel_id());if (mychannel.get() == nullptr){default_info("获取指定虚拟机对应的所有交换机信息失败,因为获取信道失败");return;}// 3. 复用mychannel->getAllExchangeInfo(req);
}// 11. 获取所有队列信息
void OnGetAllMsgQueueInfo(const muduo::net::TcpConnectionPtr &conn, const GetAllMsgQueueInfoRequestPtr &req, muduo::Timestamp)
{// 1. 先看有无该连接Connection::ptr myconn = _connection_manager_ptr->getConnecion(conn);if (myconn.get() == nullptr){default_info("获取指定虚拟机对应的所有队列信息时,没有找到连接对应的Connection对象");return;}// 2. 获取信道Channel::ptr mychannel = myconn->getChannel(req->channel_id());if (mychannel.get() == nullptr){default_info("获取指定虚拟机对应的所有队列信息失败,因为获取信道失败");return;}// 3. 复用mychannel->getAllMsgQueueInfo(req);
}// 12. 获取所有绑定信息
void OnGetAllBindingInfo(const muduo::net::TcpConnectionPtr &conn, const GetAllBindingInfoRequestPtr &req, muduo::Timestamp)
{// 1. 先看有无该连接Connection::ptr myconn = _connection_manager_ptr->getConnecion(conn);if (myconn.get() == nullptr){default_info("获取指定虚拟机对应的所有绑定信息时,没有找到连接对应的Connection对象");return;}// 2. 获取信道Channel::ptr mychannel = myconn->getChannel(req->channel_id());if (mychannel.get() == nullptr){default_info("获取指定虚拟机对应的所有绑定信息失败,因为获取信道失败");return;}// 3. 复用mychannel->getAllBindingInfo(req);
}

3.客户端模块的修改

对于客户端来说,它只需要修改网络模块即可,因为对应的服务都是由服务器提供的

因此只需要修改信道模块和连接模块即可

1.信道模块
1.回调函数

为了让用户能够真正拿到相应的数据,我们客户端需要将对应的resp返回给用户,因此我们就可以通过要求用户注册一个回调函数来实现解耦

类型:using getVirtualHostInfoCallback = std::function<void(const GetAllVirtualHostInfoResponsePtr &resp)>;
using getExchangeInfoCallback = std::function<void(const GetAllExchangeInfoResponsePtr &resp)>;
using getMsgQueueInfoCallback = std::function<void(const GetAllMsgQueueInfoResponsePtr &resp)>;
using getBindingInfoCallback = std::function<void(const GetAllBindingInfoResponsePtr &resp)>;成员:getVirtualHostInfoCallback _vhost_cb;
getExchangeInfoCallback _exchange_cb;
getMsgQueueInfoCallback _queue_cb;
getBindingInfoCallback _binding_cb;

因为连接模块收到resp之后,需要调用信道模块封装好的函数,进而才能调用用户注册的回调
所以信道模块需要将相应回调保存好

2.信道模块

信道模块具体函数的任务是:

  1. 构建req并发送
  2. 等待基础响应
    (我们要保证climq当中调用完相应的恢复函数并返回之后,历史数据的确已经成功恢复了,所以这里我们给上基础响应,尽管恢复交换机,队列和绑定信息三者之间并无强制前后顺序,可以并发)
    [注意: 恢复虚拟机必须要在交换机…之前,因为后者的调用需要依赖前者恢复的数据]
void getAllVirtualHostInfo(getVirtualHostInfoCallback callback)
{GetAllVirtualHostInfoRequest req;req.set_channel_id(_channel_id);std::string rid = UUIDHelper::uuid();req.set_req_id(rid);_codec->send(_conn, req);_vhost_cb = callback;waitResponse(rid);
}void getAllExchangeInfo(const std::string &vname, getExchangeInfoCallback callback)
{GetAllExchangeInfoRequest req;req.set_channel_id(_channel_id);std::string rid = UUIDHelper::uuid();req.set_req_id(rid);req.set_vhost_name(vname);_codec->send(_conn, req);_exchange_cb = callback;waitResponse(rid);
}void getAllMsgQueueInfo(const std::string &vname, getMsgQueueInfoCallback callback)
{GetAllMsgQueueInfoRequest req;req.set_channel_id(_channel_id);std::string rid = UUIDHelper::uuid();req.set_req_id(rid);req.set_vhost_name(vname);_codec->send(_conn, req);_queue_cb = callback;waitResponse(rid);
}void getAllBindingInfo(const std::string &vname, getBindingInfoCallback callback)
{GetAllBindingInfoRequest req;req.set_channel_id(_channel_id);std::string rid = UUIDHelper::uuid();req.set_req_id(rid);req.set_vhost_name(vname);_codec->send(_conn, req);_binding_cb = callback;waitResponse(rid);
}

对外提供给连接模块的函数:

void returnVostInfo(const GetAllVirtualHostInfoResponsePtr &resp)
{_vhost_cb(resp);
}void returnExchangeInfo(const GetAllExchangeInfoResponsePtr &resp)
{_exchange_cb(resp);
}void returnMsgQueueInfo(const GetAllMsgQueueInfoResponsePtr &resp)
{_queue_cb(resp);
}void returnBindingInfo(const GetAllBindingInfoResponsePtr &resp)
{_binding_cb(resp);
}
3.连接模块

在ProtobufDispatcher上注册相应resp的回调函数即可

找到信道,然后封装任务抛入线程池

void OnGetAllVirtualHostResponse(const muduo::net::TcpConnectionPtr &conn, const GetAllVirtualHostInfoResponsePtr &resp, muduo::Timestamp)
{// 1.找到信道Channel::ptr cp = _channel_manager->getChannel(resp->channel_id());if (cp.get() == nullptr){default_info("调用消费者的消费处理回调函数失败,因为未找到该信道, 信道ID: %s",resp->channel_id().c_str());return;}// 2. 包装任务抛入线程池_worker->_pool.put([cp, resp]{ cp->returnVostInfo(resp); });
}void OnGetAllExchangeResponse(const muduo::net::TcpConnectionPtr &conn, const GetAllExchangeInfoResponsePtr &resp, muduo::Timestamp)
{// 1.找到信道Channel::ptr cp = _channel_manager->getChannel(resp->channel_id());if (cp.get() == nullptr){default_info("调用消费者的消费处理回调函数失败,因为未找到该信道, 信道ID: %s",resp->channel_id().c_str());return;}// 2. 包装任务抛入线程池_worker->_pool.put([cp, resp]{ cp->returnExchangeInfo(resp); });
}void OnGetAllMsgQueueResponse(const muduo::net::TcpConnectionPtr &conn, const GetAllMsgQueueInfoResponsePtr &resp, muduo::Timestamp)
{// 1.找到信道Channel::ptr cp = _channel_manager->getChannel(resp->channel_id());if (cp.get() == nullptr){default_info("调用消费者的消费处理回调函数失败,因为未找到该信道, 信道ID: %s",resp->channel_id().c_str());return;}// 2. 包装任务抛入线程池_worker->_pool.put([cp, resp]{ cp->returnMsgQueueInfo(resp); });
}void OnGetAllBindingResponse(const muduo::net::TcpConnectionPtr &conn, const GetAllBindingInfoResponsePtr &resp, muduo::Timestamp)
{// 1.找到信道Channel::ptr cp = _channel_manager->getChannel(resp->channel_id());if (cp.get() == nullptr){default_info("调用消费者的消费处理回调函数失败,因为未找到该信道, 信道ID: %s",resp->channel_id().c_str());return;}// 2. 包装任务抛入线程池_worker->_pool.put([cp, resp]{ cp->returnBindingInfo(resp); });
}

4.climq.cc针对恢复历史数据的修改

1.保证同步

我们要保证两个同步:

  1. 恢复完虚拟机数据之后,才能往下去恢复交换机,队列,绑定信息的数据
  2. 恢复完交换机,队列,绑定信息数据之后才能对外提供服务

第一个同步只需要一个条件,用条件变量即可
而第二个同步需要三个条件,需要用muduo库的CountDownLatch才行,因为他就是用来解决这一类问题的

所以,我们增加几个成员:
为了防止错过通知和伪唤醒,我们需要给一个bool变量

muduo::CountDownLatch init_latch;
mutex init_mtx;
condition_variable init_cond;
bool init_ok = false;

我们就给init_latch初始化为4了,在每个函数当中都调用一次countDown

2.四个回调函数

void recover_vhost(const GetAllVirtualHostInfoResponsePtr &resp)
{unique_lock<mutex> ulock(init_mtx);for (auto &kv : resp->info()){_vhost_map[kv.first].vhost_info = kv.second;}init_latch.countDown();init_ok = true;init_cond.notify_one();
}void recover_exchange(const GetAllExchangeInfoResponsePtr &resp)
{for (auto &kv : resp->info()){_vhost_map[resp->vhost_name()]._exchange_map[kv.first] = kv.second;}init_latch.countDown();
}void recover_queue(const GetAllMsgQueueInfoResponsePtr &resp)
{for (auto &kv : resp->info()){_vhost_map[resp->vhost_name()]._queue_map[kv.first] = kv.second;}init_latch.countDown();
}void recover_binding(const GetAllBindingInfoResponsePtr &resp)
{for (auto &kv : resp->info()){auto &bmap = _vhost_map[resp->vhost_name()]._binding_map;for (auto &elem : kv.second.inner_info()){bmap[kv.first][elem.first] = elem.second; // kv.first是交换机名称  elem.first是队列名称}}init_latch.countDown();
}

3.构造函数

GlobalResource(int channel_num, const string &channel_name_prefix) : init_latch(4)
{// 1. 建立连接Connection::ptr myconn = std::make_shared<Connection>("127.0.0.1", 8889, std::make_shared<AsyncWorker>());// 2. 创建多线程for (int i = 1; i <= channel_num; i++){string channel_name = channel_name_prefix + to_string(i);_channel_map[channel_name].worker = thread(std::bind(&GlobalResource::thread_routine, this, channel_name, myconn));}// 3. 恢复历史数据(虚拟机,交换机,绑定信息)[从服务器下手了...]// 要有一个单独的channelChannel::ptr cp = myconn->getChannel();cp->getAllVirtualHostInfo(std::bind(&GlobalResource::recover_vhost, this, std::placeholders::_1));{unique_lock<mutex> ulock(init_mtx);init_cond.wait(ulock, [this]() -> bool{ return init_ok; });}for (auto &kv : _vhost_map){cp->getAllExchangeInfo(kv.first, std::bind(&GlobalResource::recover_exchange, this, std::placeholders::_1));cp->getAllMsgQueueInfo(kv.first, std::bind(&GlobalResource::recover_queue, this, std::placeholders::_1));cp->getAllBindingInfo(kv.first, std::bind(&GlobalResource::recover_binding, this, std::placeholders::_1));}init_latch.wait();myconn->returnChannel(cp);
}

五、正则表达式解决字符串匹配与查找问题

为了提高用户体验,我们规定: 用户输入的指令可以在每个单词之间使用无数个空格
而且我们的匹配是一种选择性的模式匹配,因此可以使用regex库(正则表达式)

1.正则表达式简单介绍

正则表达式(Regular Expression,简称Regex或RE)是一种文本模式
包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为“元字符”)。
正则表达式使用单个字符串来描述、匹配一系列符合某个句法规则的字符串。
通常被用来检索、替换那些符合某个模式(规则)的文本。基本概念1. 普通字符:大多数字符,包括所有大写和小写字母、所有数字、所有标点符号和一些其他符号,都是普通字符。它们在正则表达式中只匹配它们自己。2. 元字符:正则表达式中的特殊字符,不表示它们自身的字面意义,而是用于表达某种特殊含义。例如,. 表示任意单个字符(除了换行符),* 表示匹配前面的子表达式零次或多次。3. 主要用途
(1) 数据验证:确保字符串符合特定的格式(如电子邮件地址、电话号码等)。
(2) 搜索:在文本中查找符合模式的字符串。
(3) 替换:在文本中查找符合模式的字符串,并将其替换为其他文本。4. 基本元字符
.:匹配除换行符 \n 之外的任何单个字符。
^:匹配输入字符串的开始位置。
$:匹配输入字符串的结束位置。
*:匹配前面的子表达式零次或多次。
+:匹配前面的子表达式一次或多次。
?:匹配前面的子表达式零次或一次。
{n}:n 是一个非负整数。匹配确定的 n 次。
{n,}:n 是一个非负整数。至少匹配 n 次。
{n,m}:m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。
[xyz]:字符集合。匹配所包含的任意一个字符。
[^xyz]:负值字符集合。匹配未包含的任意字符。
\:转义字符,用于匹配包括元字符在内的特殊字符。
\s:匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。
\S:匹配任何非空白字符。等价于 [^ \f\n\r\t\v]
():捕获组,将捕获组中的内容作为一个整体进行匹配,而且支持提取捕获组的内容
|:逻辑或,匹配其中任意一个对象(字符或者单词)\f:换页符
\n:换行符
\r:回车符
\t:水平制表符
\v:垂直制表符

注意:正则表达式是区分大小写的

我们只需要使用:
+
*
\s
\S
()
|
这六个

2.C++11 regex库简单介绍

C++中的regex库是C++11标准引入的一部分,用于支持正则表达式的操作。正则表达式是一种强大的文本处理工具,它允许开发者使用一种非常灵活的方式来搜索、替换或分割字符串。以下是C++ regex库的简单介绍:一、基本组成
头文件:<regex>,包含了使用正则表达式所需的所有组件。
主要类:
std::regex:表示一个正则表达式对象,用于匹配文本。
std::smatch(或std::match_results):用于保存匹配结果,可以看作是一个子匹配(sub_match)的数组。
std::sregex_iterator:迭代器,用于遍历字符串中所有与正则表达式匹配的子串。二、主要功能1. 匹配:
std::regex_match:检查整个字符串是否与正则表达式完全匹配。如果匹配,返回true;否则返回false2. 搜索:
std::regex_search:在字符串中搜索与正则表达式匹配的部分。如果找到匹配项,返回true,并将匹配结果保存在std::smatch对象中;否则返回false3. 替换:
std::regex_replace:替换字符串中与正则表达式匹配的部分。它接受一个源字符串、一个正则表达式和一个替换字符串作为参数,并返回替换后的新字符串。4. 遍历匹配项:
使用std::sregex_iterator可以遍历字符串中所有与正则表达式匹配的子串。这通常用于处理包含多个匹配项的字符串。

要进行字符串的匹配,我们使用regex_match函数

注意:smatch对象的0号下标的内容是进行匹配的字符串

3.C++11 原始字符串字面量简单介绍

因为我们需要用\s \S 所以势必就需要进行二次转义,可是这样极其不优雅,所以我们采用C++11的原始字符串字面量

原始字符串字面量的语法使用前缀R(大写),后跟一个括号内的定界符(delimiter)
这个定界符可以是任何括号字符对((), {}, [])以及定界符内的可选字符序列
(但括号内的字符不能是换行符或字符串内的其他转义序列)。
定界符用于界定原始字符串的开头和结尾。允许开发者以一种更加直观和方便的方式来编写包含转义序列(如\n等)的字符串。示例:假设你有一个文件路径,它包含了许多反斜杠(\),
在C++中你需要将它们都转义为\\。使用原始字符串字面量,你可以这样写:// 使用原始字符串字面量
string filePath = R"(C:\Users\Example\Documents\file.txt)";
cout << filePath << endl; 
// 输出: C:\Users\Example\Documents\file.txt// 如果没有使用原始字符串,你需要这样写
string filePathEscaped = "C:\\Users\\Example\\Documents\\file.txt";
cout << filePathEscaped << endl; 
// 输出: C:\Users\Example\Documents\file.txt注意:- 原始字符串字面量中的换行符会被保留在字符串中。
- 定界符(括号内的字符)不能出现在原始字符串中,除非它们被再次用作定界符来结束字符串。
- 原始字符串字面量提供了编写包含复杂转义序列(如正则表达式、文件路径等)的字符串的便捷方式。
- 实际上,你可以根据需要选择任何合适的括号对作为定界符。

4.示例

为了进行功能上的限制,我们将命令行解析函数分为三个函数:

  1. 选择信道之前的命令行解析函数
  2. 选择虚拟机之前的命令行解析函数
  3. 选择虚拟机之后的命令行解析函数

主循环函数通过_channel_ok和_vhost_ok来判断每次执行哪个函数来解析命令

1.匹配 man 字符串

前面可以有任意个空格,后面也可以有任意个空格:

regex(R"(\s*man\s*)");

*让\s可以匹配0次或任意次

2.匹配 use channel_name = 信道名 字符串

use前面可以有任意个空格,use和channel_name之间至少有一个空格
channel_name和=之间可以有任意空格,=和信道名之间可以有任意个空格,信道名之后可以有任意个空格

regex(R"#(\s*use\s+channel_name\s*=\s*(\S+)\s*)#")

+让\s可以匹配1次或任意次
\S是非空格序列,因为我们不支持信道名当中包含空格,就像是MySQL不支持表名当中包含空格一样

这里为何要给#呢?
不给#也可以,只不过不给#的话,因为()太多了,会导致眼花
所以给#来提高观感的清晰度

注意:给什么符号作为定界符都可以,只要这个符号不会出现在正则表达式当中即可

3. (direct|fanout|topic)

下面匹配声明交换机这个指令:

 create exchange_name=交换机名称 type=[direct/fanout/topic] durable=[true/false] auto_delete=[true/false] 

在这里就可以用到|来进行选择匹配:

regex(R"#(\s*create\s+exchange_name\s*=\s*(\S+)\s+type\s*=\s*(direct|fanout|topic)\s+durable\s*=\s*(true|false)\s+auto_delete\s*=\s*(true|false)\s*)#")

单词之前需要至少有一个空格,开头和结尾可以有任意个空格,等号两边可以有任意个空格

看着很长,但是大家自己敲一敲写一写,你会发现很简单,因为只用到了这么几个通配符而已

5.完善

经过了上面的实例之后,其他的匹配也跟上面如初一辄,大家可以跟着第一大点最后的手册来自己把所有的命令都匹配了
在这里插入图片描述
在这里插入图片描述

一、命令相关:查看命令手册     		regex(R"(\s*man\s*)"))
清屏			   		regex(R"(\s*clear\s*)"))
退出			 		regex(R"(\s*quit\s*)"))二、信道相关:选择信道 				regex(R"#(\s*use\s+channel_name\s*=\s*(\S+)\s*)#"))
取消对信道的使用           regex(R"#(\s*disuse\s+channel_name\s*=\s*(\S+)\s*)#"))三、虚拟机相关:创建虚拟机         	regex(R"#(\s*create\s+virtual_host\s*=\s*(\S+)\s*)#"))
删除虚拟机			regex(R"#(\s*drop\s+virtual_host\s*=\s*(\S+)\s*)#"))
使用虚拟机			regex(R"#(\s*use\s+virtual_host\s*=\s*(\S+)\s*)#"))
显示所有虚拟机		regex(R"#(\s*show\s+virtual_host_info\s*=\s*(\S+)\s*)#"))
显示指定虚拟机信息		regex(R"#(\s*show\s+virtual_host_info\s*=\s*(\S+)\s*)#"))四、交换机相关创建交换机			regex(R"#(\s*create\s+exchange_name\s*=\s*(\S+)\s+type\s*=\s*(direct|fanout|topic)\s+durable\s*=\s*(true|false)\s+auto_delete\s*=\s*(true|false)\s*)#"))
删除交换机			regex(R"#(\s*drop\s+exchange_name\s*=\s*(\S+)\s*)#"))
显示所有交换机		regex(R"(\s*show\s+all_exchange_info\s*)"))
显示指定交换机信息		regex(R"#(\s*show\s+exchange_info\s*=\s*(\S+)\s*)#"))五、队列相关创建队列				regex(R"#(\s*create\s+queue_name\s*=\s*(\S+)\s+durable\s*=\s*(true|false)\s+auto_delete\s*=\s*(true|false)\s+exclusive\s*=\s*(true|false)\s*)#"))
删除队列				regex(R"#(\s*drop\s+queue_name\s*=\s*(\S+)\s*)#"))
显示所有队列			regex(R"(\s*show\s+all_queue_info\s*)"))
显示指定队列信息		regex(R"#(\s*show\s+queue_info\s*=\s*(\S+)\s*)#"))六、绑定相关绑定队列				regex(R"#(\s*bind\s+queue_name\s*=\s*(\S+)\s+to\s+exchange_name\s*=\s*(\S+)\s+with\s+binding_key\s*=\s*(\S+)\s*)#"))
解绑队列				regex(R"#(\s*unbind\s+queue_name\s*=\s*(\S+)\s+to\s+exchange_name\s*=\s*(\S+)\s*)#"))
显示所有绑定信息		regex(R"(\s*show\s+all_binding_info\s*)"))七、订阅相关订阅队列				regex(R"#(\s*consume\s+queue_name\s*=\s*(\S+)\s+consumer_tag\s*=\s*(\S+)\s+auto_ack\s*=\s*(\S+)\s*)#"))
取消订阅				regex(R"(\s*cancel\s+this_consume\s*)"))
【无需给消费者标识,因为一个信道只能关联一个消费者,因此直接取消即可】生产者关联信道		regex(R"#(\s*produce\s+producer_tag\s*=\s*(\S+)\s*)#"))
生产者取消关联		regex(R"#(\s*cancel\s+this_produce\s*)#"))八、消息的发布与拉取发布消息:			regex(R"#(\s*publish\s+message\s+exchange_name\s*=\s*(\S+)\s+delivery_mode\s*=\s*(durable|undurable)\s+mechanism\s*=\s*(push|pull|both)\s+routing_key\s*=\s*(\S+)\s+body\s*=\s*(.*))#"))【发布消息也无需给生产者标识,因为一个信道只能关联一个生产者者,因此直接发布即可】拉取消息:regex(R"(\s*pull\s+message\s*)"))【拉取消息也无需给消费者标识,因为一个信道只能关联一个消费者,因此直接拉取即可】显示指定消费者所消费过的所有消息:regex(R"#(\s*show\s+consume_message\s+consumer_tag\s*=\s*(\S+)\s*)#"))显示指定生产者所发布过的所有消息:regex(R"#(\s*show\s+publish_message\s+producer_tag\s*=\s*(\S+)\s*)#"))

六、具体功能的实现

1.命令相关

1.man

就是显示命令手册,难道要一行一行cout?太LOW了
难道要存文件里面,然后文件读写?太LOW了

直接用一下cat不就行了吗~
因此,我们把命令手册的内容存到manual.txt文件当中,
用system函数来执行系统命令cat:

// 打印使用手册
void man()
{system("cat ./manual.txt");
}

2.clear

跟上面一样,我们用system函数来执行clear系统命令即可

// 清屏
void clear_screen()
{system("clear");
}

3.quit

用户输入quit之后,我们要将所有线程函数退出,然后退出主线程
其实就是让主线程遍历线程函数哈希表,调用每个ThreadChannel提供的shutdown函数即可

// 退出函数
void quit()
{for (auto &kv : _channel_map){kv.second.shutdown();}running = false;
}

2.信道相关

1.使用信道

步骤:

  1. 判断当前是否正在使用信道
  2. 查询该信道是否存在
  3. 使用信道
void useChannel(const string &channel_name)
{// 0. 判断是否正在使用信道if (_channel_ok == true){cout << "使用信道失败,因为当前正在使用信道, 当前信道名称: " << _channel_name << " 请先disuse当前信道,然后使用其他信道\n";}// 1. 查询该信道是否存在else if (_channel_map.count(channel_name) == 0){cout << "选择信道失败, 因为该信道不存在, 请重新选择\n";}else{cout << "选择信道成功, 信道名: " << channel_name << "\n";_channel_name = channel_name;_channel_ok = true;}
}

2.取消对信道的使用

步骤:

  1. 检查当前是否正在使用信道
  2. 取消对信道的使用

注意:
取消对信道的使用时,我们也要取消对虚拟机的使用
因为我们要保证:只有先使用信道,才能使用虚拟机

void disuseChannel()
{if (_channel_ok){_channel_name.clear();_channel_ok = false;// 将虚拟机使用也置空_vhost_ok = false;_vhost_name.clear();cout << "取消信道使用成功, 虚拟机也已一并取消使用\n";}else{cout << "当前尚未使用任何信道,无法disuse\n";}
}

3.虚拟机相关

下面就是真正需要从线程去执行任务的模块了
核心就是根据参数打包任务,抛入任务队列,然后去等待并打印结果

注意:在打包的任务当中
需要这样做:

  1. 调用相应的业务接口,拿到返回值(bool)
  2. 将bool的执行结果结合具体业务形成string的执行结果
  3. 根据bool的执行结果调整相应数据结构当中的数据(增删查改)
  4. 将string的执行结果抛入info队列

pushInfo和waitInfo我们封装为了函数:

// 将执行结果放入info_queue当中
void pushInfo(const std::string info)
{ThreadChannel &tchannel = _channel_map[_channel_name];tchannel.push_info(info);
}
// 拿取执行结果,给用户进行打印
void waitInfo()
{ThreadChannel &tchannel = _channel_map[_channel_name];cout << "执行结果:" << tchannel.pop_info();
}

秉持这个思路,我们就可以写出所有的业务代码:

1.声明虚拟机

  1. 查看该虚拟机是否存在
  2. 打包任务抛入任务队列
  3. 等待并打印结果

func:
4. 补充参数
5. 调用相应业务接口
6. 将bool执行情况结合业务转为string
7. 将string执行情况抛入info队列

维护数据结构:
如果return_result为true,则在_vhost_map新增虚拟机,并且填充其vhost_info 字段

我们会发现,除了我们维护数据结构的具体策略不一样,其他都是一样的
因此后面的业务接口,我就只介绍如何维护数据结构了

// 虚拟机相关
void declareVirtualHost(const string &vhost_name)
{// 0. 看该虚拟机是否存在if (_vhost_map.count(vhost_name) > 0){cout << "声明虚拟机成功,虚拟机名称:" << vhost_name << "\n";return;}// 1. 打包任务,交由信道执行auto func = [vhost_name, this](const Channel::ptr &cp){// 1. 根据虚拟机名称拿到 数据库名称和消息目录string vhost_dbfile = "./" + vhost_name + "/resource.db";string vhost_basedir = "./" + vhost_name + "/message";// 2. 声明虚拟机bool ok = cp->declareVirtualHost(vhost_name, vhost_dbfile, vhost_basedir);// 3. 根据执行情况, 将执行结果放入info_queue当中string info;if (ok){info = "声明虚拟机成功,虚拟机名称:" + vhost_name + "\n";// 新增虚拟机_vhost_map[vhost_name].vhost_info = "dbfile: " + vhost_dbfile + " basedir: " + vhost_basedir;}elseinfo = "声明虚拟机失败,虚拟机名称:" + vhost_name + "\n";pushInfo(info);};// 2. 将任务抛入任务队列ThreadChannel &tchannel = _channel_map[_channel_name];tchannel.push_task(func);// 3. 等待并打印结果waitInfo();
}

2.删除虚拟机

如果被删除的虚拟机是当前正在使用的虚拟机,则需要取消对当前虚拟机的使用

不要忘了在_vhost_map当中删除虚拟机

void eraseVirtualHost(const string &vhost_name)
{// 1. 查询该虚拟机是否存在if (_vhost_map.count(vhost_name) == 0){cout << "删除虚拟机失败,因为该虚拟机 " << vhost_name << " 并不存在\n";return;}// 2. 打包任务抛入任务队列auto func = [vhost_name, this](const Channel::ptr &cp){// 1. 调用函数获取执行情况bool ret = cp->eraseVirtualHost(vhost_name);// 2. 根据执行情况, 将执行结果放入info_queue当中string info;if (ret){info = "删除虚拟机成功, 虚拟机名称: " + vhost_name + "\n";// 判断被删除虚拟机是否是当前所使用的虚拟机if (vhost_name == _vhost_name){// 取消对该虚拟机的使用_vhost_name.clear();_vhost_ok = false;info += "因为被删除虚拟机为当前所使用虚拟机, 所以虚拟机使用情况重置, 请重新选择虚拟机\n";}// 删除该虚拟机的记录_vhost_map.erase(vhost_name);}else{info = "删除虚拟机失败,虚拟机名称: " + vhost_name + "\n";}pushInfo(info);};ThreadChannel &tchannel = _channel_map[_channel_name];tchannel.push_task(func);// 4. 等待并打印执行结果waitInfo();
}

3.使用虚拟机

跟使用信道如出一辙,这里就不赘述了

void useVirtualHost(const string &vhost_name)
{// 0. 判断是否正在使用虚拟机if (_vhost_ok == true){cout << "使用虚拟机失败,因为当前正在使用虚拟机, 当前虚拟机名称: " << _vhost_name << " 请先disuse当前虚拟机,然后使用其他虚拟机\n";}// 1. 查询该虚拟机是否存在else if (_vhost_map.count(vhost_name)){_vhost_name = vhost_name;_vhost_ok = true;cout << "使用虚拟机成功,虚拟机名称:" << vhost_name << "\n";}else{cout << "使用虚拟机失败,因为该虚拟机不存在,虚拟机名称:" << vhost_name << "\n";}
}

4.取消对虚拟机的使用

跟取消对信道的使用差不多,这里就不赘述了

void disuseVirtualHost()
{if (_vhost_ok){_vhost_name.clear();_vhost_ok = false;cout << "取消虚拟机使用成功\n";}else{cout << "当前尚未使用任何虚拟机,无法disuse\n";}
}

5.显示所有虚拟机信息

虚拟机自身信息就是vhost_info,打印即可

void showAllVirtualHost()
{for (auto &kv : _vhost_map){cout << kv.first << " : " << kv.second.vhost_info << "\n";}
}

6.打印指定虚拟机信息

void showVirtualHost(const string &vhost_name)
{auto iter = _vhost_map.find(vhost_name);if (iter == _vhost_map.end()){cout << "该虚拟机不存在\n";}else{cout << iter->first << " : " << iter->second.vhost_info << "\n";}
}

4.交换机相关

1.声明交换机

在_vhost_map当中的_exchange_map哈希表增加该交换机信息

void declareExchange(const string &exchange_name, ExchangeType type, bool durable, bool auto_delete)
{// 0. 看该交换机是否存在if (_vhost_map[_vhost_name]._exchange_map.count(exchange_name) > 0){cout << "声明交换机成功, 交换机名称: " << exchange_name << "\n";return;}// 1. 打包任务auto func = [exchange_name, type, durable, auto_delete, this](const Channel::ptr &cp){// 1. 调用方法bool ret = cp->declareExchange(_vhost_name, exchange_name, type, durable, auto_delete, {});// 2. 根据执行结果拿到执行情况string info;if (ret){info = "声明交换机成功, 交换机名称: " + exchange_name + "\n";string desc = "type: " + ExchangeType_Name(type) + " durable: " + (durable ? " true " : " false ") + " auto_delete: " + (auto_delete ? " true" : " false");_vhost_map[_vhost_name]._exchange_map.emplace(exchange_name, move(desc));}else{info = "声明交换机失败, 交换机名称: " + exchange_name + "\n";}// 3. 放入info_queue当中pushInfo(info);};// 2. 抛入task_queueThreadChannel &tchannel = _channel_map[_channel_name];tchannel.push_task(func);// 3. 取结果并打印waitInfo();
}

2.删除交换机

在_vhost_map的_exchange_map当中删除该交换机
注意:删除交换机时千万不要忘了删除该交换机关联的绑定信息

void eraseExchange(const string &exchange_name)
{// 0. 查看该交换机是否存在if (_vhost_map[_vhost_name]._exchange_map.count(exchange_name) == 0){cout << "删除交换机失败, 因为该交换机不存在, 交换机名称: " << exchange_name << "\n";return;}// 1. 打包任务auto func = [exchange_name, this](const Channel::ptr &cp){// 1. 调用接口bool ret = cp->eraseExchange(_vhost_name, exchange_name);// 2. 记录执行情况string info;if (ret){info = "删除交换机成功, 交换机名称: " + exchange_name + "\n";_vhost_map[_vhost_name]._exchange_map.erase(exchange_name);_vhost_map[_vhost_name].removeExchangeBindings(exchange_name);}else{info = "删除交换机失败, 交换机名称: " + exchange_name + "\n";}// 3. 将执行情况放到info_queue当中pushInfo(info);};// 2.把任务抛入task_queueThreadChannel &tchannel = _channel_map[_channel_name];tchannel.push_task(func);// 3. 等待并打印结果waitInfo();
}

3.显示所有交换机信息

交换机信息在_vhost_map的_exchange_map当中
直接打印该kv的second即可,里面就是交换机序列化成的string对象

void showAllExchange()
{for (auto &kv : _vhost_map[_vhost_name]._exchange_map){cout << kv.first << " : " << kv.second << "\n";}
}

4.显示指定交换机信息

如果用户就是只要某个交换机的信息,那么他可以调用该指令

void showExchange(const string &exchange_name)
{auto iter = _vhost_map[_vhost_name]._exchange_map.find(exchange_name);if (iter == _vhost_map[_vhost_name]._exchange_map.end()){cout << "该交换机不存在\n";}else{cout << iter->first << " : " << iter->second << "\n";}
}

5.队列相关

跟交换机模块雷同:

1.声明队列

在_vhost_map当中的_queue_map增加该队列信息

// 队列相关
void declareMsgQueue(const string &queue_name, bool durable, bool auto_delete, bool exclusive)
{// 1. 查找是否有该队列if (_vhost_map[_vhost_name]._queue_map.count(queue_name) > 0){cout << "声明队列成功, 队列名: " << queue_name << "\n";return;}// 2. 打包任务auto func = [this, queue_name, durable, auto_delete, exclusive](const Channel::ptr &cp){// 1 .调用接口bool ret = cp->declareMsgQueue(_vhost_name, queue_name, durable, exclusive, auto_delete, {});// 2. 拿到执行情况string info;if (ret){info = "声明队列成功, 队列名: " + queue_name + "\n";// 因为问号表达式的类型无法在编译时准确确定,因此operator+不会推导,导致编译错误// 需要手动在其中加上一个明确的string对象,相当于告诉编译器该怎么推导string desc = string() + "durable: " + (durable ? " true " : " false ") + " auto_delete: " + (auto_delete ? " true " : " false ") + " exclusive: " + (exclusive ? " true" : " false");_vhost_map[_vhost_name]._queue_map.emplace(queue_name, move(desc));}else{info = "声明队列失败, 队列名: " + queue_name + "\n";}// 3. 把info放入info_queuepushInfo(info);};// 3. 把任务抛入线程池ThreadChannel &tchannel = _channel_map[_channel_name];tchannel.push_task(func);// 4. 等待打印任务waitInfo();
}

2.删除队列

不要忘了删除该队列关联的绑定信息

按理来说,删除队列时需要删除订阅该队列的消费者

但是因为:

  1. 我们提供了查看指定消费者所消费过的所有信息的功能,因此删除消费者就很坑
  2. 队列和消费者之间是多对多的关系,不能盲目删除消费者

因此,我们不删任何消费者

void eraseMsgQueue(const string &queue_name)
{// 1. 查找是否存在if (_vhost_map[_vhost_name]._queue_map.count(queue_name) == 0){cout << "删除队列失败, 因为该队列不存在, 队列名: " << queue_name << "\n";return;}// 2. 打包任务auto func = [queue_name, this](const Channel::ptr &cp){bool ret = cp->eraseMsgQueue(_vhost_name, queue_name);string info;if (ret){info = "删除队列成功, 队列名: " + queue_name + "\n";_vhost_map[_vhost_name]._queue_map.erase(queue_name);_vhost_map[_vhost_name].removeQueueBindings(queue_name);}else{info = "删除队列失败, 队列名: " + queue_name + "\n";}pushInfo(info);};ThreadChannel &tchannel = _channel_map[_channel_name];tchannel.push_task(func);waitInfo();
}

3.显示所有队列信息

void showAllMsgQueue()
{for (auto &kv : _vhost_map[_vhost_name]._queue_map){cout << kv.first << " : " << kv.second << "\n";}
}

4.显示指定队列信息

void showMsgQueue(const string &queue_name)
{auto iter = _vhost_map[_vhost_name]._queue_map.find(queue_name);if (iter == _vhost_map[_vhost_name]._queue_map.end()){cout << "该队列不存在\n";}else{cout << iter->first << " : " << iter->second << "\n";}
}

6.绑定相关

1.绑定队列

在_vhost_map的_binding_map当中添加绑定信息

void bind(const string &queue_name, const string &exchange_name, const string &binding_key)
{// 0. 查找是否存在auto eiter = _vhost_map[_vhost_name]._binding_map.find(exchange_name);if (eiter != _vhost_map[_vhost_name]._binding_map.end()){unordered_map<string, string> &queue_binding_map = eiter->second;if (queue_binding_map.count(queue_name) > 0){cout << "绑定成功, 交换机: " << exchange_name << ", 队列: " << queue_name << " 绑定信息: " << binding_key << "\n";return;}}// 1. 打包任务auto func = [queue_name, exchange_name, binding_key, this](const Channel::ptr &cp){bool ret = cp->bind(_vhost_name, exchange_name, queue_name, binding_key);string info;if (ret){info = "exchange: " + exchange_name + ", queue: " + queue_name + " binding_key: " + binding_key + "\n";_vhost_map[_vhost_name]._binding_map[exchange_name].insert({queue_name, info});pushInfo("绑定成功, " + info);}else{info = "绑定失败, 交换机: " + exchange_name + ", 队列: " + queue_name + " 绑定信息: " + binding_key + "\n";pushInfo(info);}};ThreadChannel &tchannel = _channel_map[_channel_name];tchannel.push_task(func);waitInfo();
}

2.解绑队列

在_vhost_map的_binding_map当中删除绑定信息

void unBind(const string &queue_name, const string &exchange_name)
{// 0. 查找是否存在auto eiter = _vhost_map[_vhost_name]._binding_map.find(exchange_name);if (eiter == _vhost_map[_vhost_name]._binding_map.end())return;unordered_map<string, string> &queue_binding_map = eiter->second;if (queue_binding_map.count(queue_name) == 0){cout << "解绑失败,因为绑定信息不存在: 交换机: " << exchange_name << ", 队列: " << queue_name << "\n";return;}// 1. 打包任务auto func = [queue_name, exchange_name, this](const Channel::ptr &cp){bool ret = cp->unBind(_vhost_name, exchange_name, queue_name);string info;if (ret){info = "解绑成功, 交换机: " + exchange_name + ", 队列: " + queue_name + "\n";_vhost_map[_vhost_name]._binding_map[exchange_name].erase(queue_name);}else{info = "解绑失败, 交换机: " + exchange_name + ", 队列: " + queue_name + "\n";}pushInfo(info);};ThreadChannel &tchannel = _channel_map[_channel_name];tchannel.push_task(func);waitInfo();
}

7.订阅相关

1.消费处理回调函数

打印+存放+ACK

void ConsumerCallback(const Channel::ptr &cp, const string &consumer_tag, const BasicProperities *bp, const string &body)
{cout << consumer_tag << " 消费了消息: " << body << "\n";// 存放该消费了的消息_channel_map[_channel_name]._consumer_message_map[consumer_tag].push_back(body);if (bp != nullptr){cp->BasicAck(bp->msg_id());}
}

这里就算是对应消费者设置了auto_ack,那么我们再ACK一次也无妨
为了进行统一我们就不再把消费者的auto_ack传过来了

2.生产确认应答函数

打印+存放+ACK

void ProducerCallback(const Channel::ptr &cp, const BasicPublishResponsePtr &resp)
{ostringstream oss;oss << resp->productor_tag() << " 发布消息: " << resp->body() << (resp->ok() ? "成功, " : "失败, ") << resp->status_str() << "\n";cout << oss.str();_channel_map[_channel_name]._producer_message_map[resp->productor_tag()].push_back(oss.str());
}

3.消费者订阅

在_channel_map的_consumer_message_map当中新增该消费者
设置_channel_map当中的consumer为当前信道所关联的消费者

需要给mqclient当中的channel新增两个get函数:

注意:判断的时候不要从_consumer_message_map当中判断消费者是否存在,因为消费者可以重复声明,因此可以存在也可以不存在
而应该判断consumer是否为空,是否是当前消费者

Consumer::ptr getConsumer()
{std::unique_lock<std::mutex> ulock(_mutex);return _consumer;
}Productor::ptr getProductor()
{std::unique_lock<std::mutex> ulock(_mutex);return _productor;
}
void basicConsume(const string &queue_name, const string &consumer_tag, bool auto_ack)
{ThreadChannel &tchannel = _channel_map[_channel_name];Consumer::ptr consumer = tchannel._consumer;if (consumer.get() != nullptr && consumer->_consumer_tag != consumer_tag){cout << "订阅队列失败,因为该信道已经关联了一个消费者:" << consumer->_consumer_tag << "\n";return;}else if(consumer.get() != nullptr && consumer->_consumer_tag == consumer_tag){cout << "当前消费者已订阅,无需重复订阅" << consumer->_consumer_tag << "\n";return;}// 打包任务auto func = [queue_name, consumer_tag, auto_ack, this](const Channel::ptr &cp){bool ret = cp->BasicConsume(_vhost_name, consumer_tag, queue_name, std::bind(&GlobalResource::ConsumerCallback, this, cp, placeholders::_1, placeholders::_2, placeholders::_3), auto_ack);string info;if (ret){info = "订阅队列成功, 队列名: " + queue_name + ", 消费者标识: " + consumer_tag + "\n";_channel_map[_channel_name]._consumer_message_map.insert({consumer_tag, {}});_channel_map[_channel_name]._consumer = cp->getConsumer();}else{info = "订阅队列失败, 队列名: " + queue_name + ", 消费者标识: " + consumer_tag + "\n";}pushInfo(info);};tchannel.push_task(func);waitInfo();
}

4.消费者取消订阅

只reset _consumer即可,不要碰_consumer_message_map

void consumeCancel()
{ThreadChannel &tchannel = _channel_map[_channel_name];Consumer::ptr consumer = tchannel._consumer;if (consumer.get() == nullptr){cout << "当前信道尚未订阅消费者,无法取消订阅\n";return;}auto func = [this, consumer](const Channel::ptr &cp){bool ret = cp->BasicCancel();string info;if (ret){info = "取消队列订阅成功, 队列名: " + consumer->_queue_name + ", 消费者标识: " + consumer->_consumer_tag + "\n";_channel_map[_channel_name]._consumer.reset();}else{info = "取消队列订阅失败\n";}pushInfo(info);};tchannel.push_task(func);waitInfo();
}

5.生产者关联

跟消费者订阅如出一辙,这里既就不赘述了

void basicProduce(const string &producer_tag)
{ThreadChannel &tchannel = _channel_map[_channel_name];Productor::ptr productor = tchannel._productor;if (productor.get() != nullptr && productor->_productor_tag != producer_tag){cout << "关联生产者失败,因为该信道已经关联了一个生产者:" << productor->_productor_tag << "\n";return;}else if (productor.get() != nullptr && productor->_productor_tag == producer_tag){cout << "当前生产者已关联,无需重复关联" << productor->_productor_tag << "\n";return;}// 0. 打包任务auto func = [producer_tag, this](const Channel::ptr &cp){bool ret = cp->ProductorBind(producer_tag, std::bind(&GlobalResource::ProducerCallback, this, cp, placeholders::_1));string info;if (ret){info = "关联生产者成功,生产者tag: " + producer_tag + "\n";_channel_map[_channel_name]._producer_message_map.insert({producer_tag, {}});_channel_map[_channel_name]._productor = cp->getProductor();}else{info = "关联生产者失败,生产者tag: " + producer_tag + "\n";}pushInfo(info);};tchannel.push_task(func);waitInfo();
}

6.生产者取消关联

跟消费者取消订阅如出一辙,这里既就不赘述了

void produceCancel()
{ThreadChannel &tchannel = _channel_map[_channel_name];Productor::ptr producer = tchannel._productor;if (producer.get() == nullptr){cout << "当前信道尚未关联生产者者,无法取消关联\n";return;}auto func = [this, producer](const Channel::ptr &cp){bool ret = cp->ProductorUnBind();string info;if (ret){info = "取消生产者关联成功, 生产者tag: " + producer->_productor_tag + "\n";_channel_map[_channel_name]._productor.reset();}else{info = "取消生产者关联成功\n";}pushInfo(info);};tchannel.push_task(func);waitInfo();
}

8.消息的发布与拉取

1.发布消息

void basicPublish(const string &exchange_name, DeliveryMode mode, PublishMechanism mechanism, const string &routing_key, const string &body)
{ThreadChannel &tchannel = _channel_map[_channel_name];Productor::ptr productor = tchannel._productor;if (productor.get() == nullptr){cout << "发布消息失败,因为当前信道并未关联生产者者, 信道名:" << _channel_name << "\n";return;}auto func = [exchange_name, mode, mechanism, routing_key, body, this](const Channel::ptr &cp){BasicProperities bp;bp.set_routing_key(routing_key);bp.set_mode(mode);bp.set_msg_id(UUIDHelper::uuid());bool ret = cp->BasicPublish(_vhost_name, exchange_name, &bp, body, mechanism);string info;if (ret){info = "消息发布成功, 目标交换机: " + exchange_name + ", 发布方式: " + PublishMechanism_Name(mechanism) + ", 路由键: " + routing_key + ", 消息体: " + body + "\n";}else{info = "消息发布失败, 目标交换机: " + exchange_name + "\n";}pushInfo(info);};tchannel.push_task(func);waitInfo();
}

2.拉取消息

void basicPull()
{ThreadChannel &tchannel = _channel_map[_channel_name];Productor::ptr producer = tchannel._productor;if (producer.get() == nullptr){cout << "拉取失败,因为该信道并未关联生产者, 信道名:" << _channel_name << "\n";return;}auto func = [this](const Channel::ptr &cp){Consumer::ptr consumer = cp->getConsumer();bool ret = cp->BasicPull();string info;if (ret){info = "拉取消息成功, 消费者: " + consumer->_consumer_tag;}else{info = "拉取消息失败, 消费者: " + consumer->_consumer_tag;}pushInfo(info);};tchannel.push_task(func);waitInfo();
}

3.查看指定消费者消费过的消息

void showConsumerMessage(const string &consumer_tag)
{auto iter = _channel_map[_channel_name]._consumer_message_map.find(consumer_tag);if (iter == _channel_map[_channel_name]._consumer_message_map.end()){cout << "该消费者不存在, 消费者tag: " << consumer_tag << "\n";}else{for (auto &message : iter->second){cout << message << "\n";}}
}

4.查看指定生产者发布过的消息

void showProducerMessage(const string &producer_tag)
{auto iter = _channel_map[_channel_name]._producer_message_map.find(producer_tag);if (iter == _channel_map[_channel_name]._producer_message_map.end()){cout << "该生产者不存在, 生产者tag: " << producer_tag << "\n";}else{for (auto &message : iter->second){cout << message << "\n";}}
}

七、完整代码

#include "../mqclient/connection.hpp"
using namespace std;
using namespace ns_mq;
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <memory>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <regex>
#include <cstdlib>struct ThreadChannel
{using ChannelCallback = function<void(const Channel::ptr &cp)>;void push_task(ChannelCallback cb){unique_lock<mutex> ulock(task_mtx);task_queue.push(cb);task_cond.notify_one();}void pop_task(const Channel::ptr &cp){ChannelCallback task;{unique_lock<mutex> ulock(task_mtx);task_cond.wait(ulock, [this]() -> bool{ return !task_queue.empty() || !_isrunning; });if (!_isrunning)return;task = task_queue.front();task_queue.pop();}// 代码健壮性if (task == nullptr)return;task(cp);}void push_info(const std::string &info){unique_lock<mutex> ulock(info_mtx);info_queue.push(info);info_cond.notify_one();}string pop_info(){unique_lock<mutex> ulock(info_mtx);info_cond.wait(ulock, [this]() -> bool{ return !info_queue.empty() || !_isrunning; });if (!_isrunning)return "";string info = info_queue.front();info_queue.pop();return info;}// 停止服务void shutdown(){if (_isrunning){_isrunning = false;task_cond.notify_all();info_cond.notify_all();worker.join();}}thread worker;queue<ChannelCallback> task_queue;mutex task_mtx;condition_variable task_cond;queue<string> info_queue;mutex info_mtx;condition_variable info_cond;Consumer::ptr _consumer;Productor::ptr _productor;bool _isrunning = true;unordered_map<string, vector<string>> _consumer_message_map; // key: consumer_tag  value: 消费的消息bodyunordered_map<string, vector<string>> _producer_message_map; // key: productor_tag  value: 发布的消息body
};struct GlobalResource
{void thread_routine(const string &channel_name, const Connection::ptr &conn){Channel::ptr cp = conn->getChannel();ThreadChannel &tchannel = _channel_map[channel_name];while (tchannel._isrunning){tchannel.pop_task(cp);}conn->returnChannel(cp);}struct VirtualHostElem{// 删除指定交换机的所有绑定信息void removeExchangeBindings(const string &exchange_name){_binding_map.erase(exchange_name);}// 删除指定队列的所有绑定信息void removeQueueBindings(const string &queue_name){for (auto &kv : _binding_map){kv.second.erase(queue_name);}}string vhost_info;unordered_map<string, string> _exchange_map;unordered_map<string, string> _queue_map;unordered_map<string, unordered_map<string, string>> _binding_map; // exchange_name -> queue_name -> binding_info};GlobalResource(int channel_num, const string &channel_name_prefix) : init_latch(4){// 1. 建立连接Connection::ptr myconn = std::make_shared<Connection>("127.0.0.1", 8889, std::make_shared<AsyncWorker>());// 2. 创建多线程for (int i = 1; i <= channel_num; i++){string channel_name = channel_name_prefix + to_string(i);_channel_map[channel_name].worker = thread(std::bind(&GlobalResource::thread_routine, this, channel_name, myconn));}// 3. 恢复历史数据(虚拟机,交换机,绑定信息)[从服务器下手了...]// 要有一个单独的channelChannel::ptr cp = myconn->getChannel();cp->getAllVirtualHostInfo(std::bind(&GlobalResource::recover_vhost, this, std::placeholders::_1));{unique_lock<mutex> ulock(init_mtx);init_cond.wait(ulock, [this]() -> bool{ return init_ok; });}for (auto &kv : _vhost_map){cp->getAllExchangeInfo(kv.first, std::bind(&GlobalResource::recover_exchange, this, std::placeholders::_1));cp->getAllMsgQueueInfo(kv.first, std::bind(&GlobalResource::recover_queue, this, std::placeholders::_1));cp->getAllBindingInfo(kv.first, std::bind(&GlobalResource::recover_binding, this, std::placeholders::_1));}init_latch.wait();myconn->returnChannel(cp);}void recover_vhost(const GetAllVirtualHostInfoResponsePtr &resp){unique_lock<mutex> ulock(init_mtx);for (auto &kv : resp->info()){_vhost_map[kv.first].vhost_info = kv.second;}init_latch.countDown();init_ok = true;init_cond.notify_one();}void recover_exchange(const GetAllExchangeInfoResponsePtr &resp){for (auto &kv : resp->info()){_vhost_map[resp->vhost_name()]._exchange_map[kv.first] = kv.second;}init_latch.countDown();}void recover_queue(const GetAllMsgQueueInfoResponsePtr &resp){for (auto &kv : resp->info()){_vhost_map[resp->vhost_name()]._queue_map[kv.first] = kv.second;}init_latch.countDown();}void recover_binding(const GetAllBindingInfoResponsePtr &resp){for (auto &kv : resp->info()){auto &bmap = _vhost_map[resp->vhost_name()]._binding_map;for (auto &elem : kv.second.inner_info()){bmap[kv.first][elem.first] = elem.second; // kv.first是交换机名称  elem.first是队列名称}}init_latch.countDown();}// 打印使用手册void man(){system("cat ./manual.txt");}// 清屏void clear_screen(){system("clear");}// 信道相关void useChannel(const string &channel_name){// 0. 判断是否正在使用信道if (_channel_ok == true){cout << "使用信道失败,因为当前正在使用信道, 当前信道名称: " << _channel_name << " 请先disuse当前信道,然后使用其他信道\n";}// 1. 查询该信道是否存在else if (_channel_map.count(channel_name) == 0){cout << "选择信道失败, 因为该信道不存在, 请重新选择\n";}else{cout << "选择信道成功, 信道名: " << channel_name << "\n";_channel_name = channel_name;_channel_ok = true;}}void disuseChannel(){if (_channel_ok){_channel_name.clear();_channel_ok = false;// 将虚拟机使用也置空_vhost_ok = false;_vhost_name.clear();cout << "取消信道使用成功, 虚拟机也已一并取消使用\n";}else{cout << "当前尚未使用任何信道,无法disuse\n";}}// 虚拟机相关void declareVirtualHost(const string &vhost_name){// 0. 看该虚拟机是否存在if (_vhost_map.count(vhost_name) > 0){cout << "声明虚拟机成功,虚拟机名称:" << vhost_name << "\n";return;}// 1. 打包任务,交由信道执行auto func = [vhost_name, this](const Channel::ptr &cp){// 1. 根据虚拟机名称拿到 数据库名称和消息目录string vhost_dbfile = "./" + vhost_name + "/resource.db";string vhost_basedir = "./" + vhost_name + "/message";// 2. 声明虚拟机bool ok = cp->declareVirtualHost(vhost_name, vhost_dbfile, vhost_basedir);// 3. 根据执行情况, 将执行结果放入info_queue当中string info;if (ok){info = "声明虚拟机成功,虚拟机名称:" + vhost_name + "\n";// 新增虚拟机_vhost_map[vhost_name].vhost_info = "dbfile: " + vhost_dbfile + " basedir: " + vhost_basedir;}elseinfo = "声明虚拟机失败,虚拟机名称:" + vhost_name + "\n";pushInfo(info);};// 2. 将任务抛入任务队列ThreadChannel &tchannel = _channel_map[_channel_name];tchannel.push_task(func);// 3. 等待并打印结果waitInfo();}void useVirtualHost(const string &vhost_name){// 0. 判断是否正在使用虚拟机if (_vhost_ok == true){cout << "使用虚拟机失败,因为当前正在使用虚拟机, 当前虚拟机名称: " << _vhost_name << " 请先disuse当前虚拟机,然后使用其他虚拟机\n";}// 1. 查询该虚拟机是否存在else if (_vhost_map.count(vhost_name)){_vhost_name = vhost_name;_vhost_ok = true;cout << "使用虚拟机成功,虚拟机名称:" << vhost_name << "\n";}else{cout << "使用虚拟机失败,因为该虚拟机不存在,虚拟机名称:" << vhost_name << "\n";}}void eraseVirtualHost(const string &vhost_name){// 1. 查询该虚拟机是否存在if (_vhost_map.count(vhost_name) == 0){cout << "删除虚拟机失败,因为该虚拟机 " << vhost_name << " 并不存在\n";return;}// 2. 打包任务抛入任务队列auto func = [vhost_name, this](const Channel::ptr &cp){// 1. 调用函数获取执行情况bool ret = cp->eraseVirtualHost(vhost_name);// 2. 根据执行情况, 将执行结果放入info_queue当中string info;if (ret){info = "删除虚拟机成功, 虚拟机名称: " + vhost_name + "\n";// 判断被删除虚拟机是否是当前所使用的虚拟机if (vhost_name == _vhost_name){// 取消对该虚拟机的使用_vhost_name.clear();_vhost_ok = false;info += "因为被删除虚拟机为当前所使用虚拟机, 所以虚拟机使用情况重置, 请重新选择虚拟机\n";}// 删除该虚拟机的记录_vhost_map.erase(vhost_name);}else{info = "删除虚拟机失败,虚拟机名称: " + vhost_name + "\n";}pushInfo(info);};ThreadChannel &tchannel = _channel_map[_channel_name];tchannel.push_task(func);// 4. 等待并打印执行结果waitInfo();}void disuseVirtualHost(){if (_vhost_ok){_vhost_name.clear();_vhost_ok = false;cout << "取消虚拟机使用成功\n";}else{cout << "当前尚未使用任何虚拟机,无法disuse\n";}}void showAllVirtualHost(){for (auto &kv : _vhost_map){cout << kv.first << " : " << kv.second.vhost_info << "\n";}}void showVirtualHost(const string &vhost_name){auto iter = _vhost_map.find(vhost_name);if (iter == _vhost_map.end()){cout << "该虚拟机不存在\n";}else{cout << iter->first << " : " << iter->second.vhost_info << "\n";}}// 交换机相关void declareExchange(const string &exchange_name, ExchangeType type, bool durable, bool auto_delete){// 0. 看该交换机是否存在if (_vhost_map[_vhost_name]._exchange_map.count(exchange_name) > 0){cout << "声明交换机成功, 交换机名称: " << exchange_name << "\n";return;}// 1. 打包任务auto func = [exchange_name, type, durable, auto_delete, this](const Channel::ptr &cp){// 1. 调用方法bool ret = cp->declareExchange(_vhost_name, exchange_name, type, durable, auto_delete, {});// 2. 根据执行结果拿到执行情况string info;if (ret){info = "声明交换机成功, 交换机名称: " + exchange_name + "\n";string desc = "type: " + ExchangeType_Name(type) + " durable: " + (durable ? " true " : " false ") + " auto_delete: " + (auto_delete ? " true" : " false");_vhost_map[_vhost_name]._exchange_map.emplace(exchange_name, move(desc));}else{info = "声明交换机失败, 交换机名称: " + exchange_name + "\n";}// 3. 放入info_queue当中pushInfo(info);};// 2. 抛入task_queueThreadChannel &tchannel = _channel_map[_channel_name];tchannel.push_task(func);// 3. 取结果并打印waitInfo();}void eraseExchange(const string &exchange_name){// 0. 查看该交换机是否存在if (_vhost_map[_vhost_name]._exchange_map.count(exchange_name) == 0){cout << "删除交换机失败, 因为该交换机不存在, 交换机名称: " << exchange_name << "\n";return;}// 1. 打包任务auto func = [exchange_name, this](const Channel::ptr &cp){// 1. 调用接口bool ret = cp->eraseExchange(_vhost_name, exchange_name);// 2. 记录执行情况string info;if (ret){info = "删除交换机成功, 交换机名称: " + exchange_name + "\n";_vhost_map[_vhost_name]._exchange_map.erase(exchange_name);_vhost_map[_vhost_name].removeExchangeBindings(exchange_name);}else{info = "删除交换机失败, 交换机名称: " + exchange_name + "\n";}// 3. 将执行情况放到info_queue当中pushInfo(info);};// 2.把任务抛入task_queueThreadChannel &tchannel = _channel_map[_channel_name];tchannel.push_task(func);// 3. 等待并打印结果waitInfo();}void showAllExchange(){for (auto &kv : _vhost_map[_vhost_name]._exchange_map){cout << kv.first << " : " << kv.second << "\n";}}void showExchange(const string &exchange_name){auto iter = _vhost_map[_vhost_name]._exchange_map.find(exchange_name);if (iter == _vhost_map[_vhost_name]._exchange_map.end()){cout << "该交换机不存在\n";}else{cout << iter->first << " : " << iter->second << "\n";}}// 队列相关void declareMsgQueue(const string &queue_name, bool durable, bool auto_delete, bool exclusive){// 1. 查找是否有该队列if (_vhost_map[_vhost_name]._queue_map.count(queue_name) > 0){cout << "声明队列成功, 队列名: " << queue_name << "\n";return;}// 2. 打包任务auto func = [this, queue_name, durable, auto_delete, exclusive](const Channel::ptr &cp){// 1 .调用接口bool ret = cp->declareMsgQueue(_vhost_name, queue_name, durable, exclusive, auto_delete, {});// 2. 拿到执行情况string info;if (ret){info = "声明队列成功, 队列名: " + queue_name + "\n";// 因为问号表达式的类型无法在编译时准确确定,因此operator+不会推导,导致编译错误// 需要手动在其中加上一个明确的string对象,相当于告诉编译器该怎么推导string desc = string() + "durable: " + (durable ? " true " : " false ") + " auto_delete: " + (auto_delete ? " true " : " false ") + " exclusive: " + (exclusive ? " true" : " false");_vhost_map[_vhost_name]._queue_map.emplace(queue_name, move(desc));}else{info = "声明队列失败, 队列名: " + queue_name + "\n";}// 3. 把info放入info_queuepushInfo(info);};// 3. 把任务抛入线程池ThreadChannel &tchannel = _channel_map[_channel_name];tchannel.push_task(func);// 4. 等待打印任务waitInfo();}void eraseMsgQueue(const string &queue_name){// 1. 查找是否存在if (_vhost_map[_vhost_name]._queue_map.count(queue_name) == 0){cout << "删除队列失败, 因为该队列不存在, 队列名: " << queue_name << "\n";return;}// 2. 打包任务auto func = [queue_name, this](const Channel::ptr &cp){bool ret = cp->eraseMsgQueue(_vhost_name, queue_name);string info;if (ret){info = "删除队列成功, 队列名: " + queue_name + "\n";_vhost_map[_vhost_name]._queue_map.erase(queue_name);_vhost_map[_vhost_name].removeQueueBindings(queue_name);}else{info = "删除队列失败, 队列名: " + queue_name + "\n";}pushInfo(info);};ThreadChannel &tchannel = _channel_map[_channel_name];tchannel.push_task(func);waitInfo();}void showAllMsgQueue(){for (auto &kv : _vhost_map[_vhost_name]._queue_map){cout << kv.first << " : " << kv.second << "\n";}}void showMsgQueue(const string &queue_name){auto iter = _vhost_map[_vhost_name]._queue_map.find(queue_name);if (iter == _vhost_map[_vhost_name]._queue_map.end()){cout << "该队列不存在\n";}else{cout << iter->first << " : " << iter->second << "\n";}}// 绑定信息相关void bind(const string &queue_name, const string &exchange_name, const string &binding_key){// 0. 查找是否存在auto eiter = _vhost_map[_vhost_name]._binding_map.find(exchange_name);if (eiter != _vhost_map[_vhost_name]._binding_map.end()){unordered_map<string, string> &queue_binding_map = eiter->second;if (queue_binding_map.count(queue_name) > 0){cout << "绑定成功, 交换机: " << exchange_name << ", 队列: " << queue_name << " 绑定信息: " << binding_key << "\n";return;}}// 1. 打包任务auto func = [queue_name, exchange_name, binding_key, this](const Channel::ptr &cp){bool ret = cp->bind(_vhost_name, exchange_name, queue_name, binding_key);string info;if (ret){info = "exchange: " + exchange_name + ", queue: " + queue_name + " binding_key: " + binding_key + "\n";_vhost_map[_vhost_name]._binding_map[exchange_name].insert({queue_name, info});pushInfo("绑定成功, " + info);}else{info = "绑定失败, 交换机: " + exchange_name + ", 队列: " + queue_name + " 绑定信息: " + binding_key + "\n";pushInfo(info);}};ThreadChannel &tchannel = _channel_map[_channel_name];tchannel.push_task(func);waitInfo();}void unBind(const string &queue_name, const string &exchange_name){// 0. 查找是否存在auto eiter = _vhost_map[_vhost_name]._binding_map.find(exchange_name);if (eiter == _vhost_map[_vhost_name]._binding_map.end())return;unordered_map<string, string> &queue_binding_map = eiter->second;if (queue_binding_map.count(queue_name) == 0){cout << "解绑失败,因为绑定信息不存在: 交换机: " << exchange_name << ", 队列: " << queue_name << "\n";return;}// 1. 打包任务auto func = [queue_name, exchange_name, this](const Channel::ptr &cp){bool ret = cp->unBind(_vhost_name, exchange_name, queue_name);string info;if (ret){info = "解绑成功, 交换机: " + exchange_name + ", 队列: " + queue_name + "\n";_vhost_map[_vhost_name]._binding_map[exchange_name].erase(queue_name);}else{info = "解绑失败, 交换机: " + exchange_name + ", 队列: " + queue_name + "\n";}pushInfo(info);};ThreadChannel &tchannel = _channel_map[_channel_name];tchannel.push_task(func);waitInfo();}void showAllBindings(){for (auto &kv : _vhost_map[_vhost_name]._binding_map){auto binding_kv = kv.second;for (auto &info : binding_kv){cout << info.second << "\n";}}}// 回调相关void ConsumerCallback(const Channel::ptr &cp, const string &consumer_tag, const BasicProperities *bp, const string &body){cout << consumer_tag << " 消费了消息: " << body << "\n";// 存放该消费了的消息_channel_map[_channel_name]._consumer_message_map[consumer_tag].push_back(body);if (bp != nullptr){cp->BasicAck(bp->msg_id());}}void ProducerCallback(const Channel::ptr &cp, const BasicPublishResponsePtr &resp){ostringstream oss;oss << resp->productor_tag() << " 发布消息: " << resp->body() << (resp->ok() ? "成功, " : "失败, ") << resp->status_str() << "\n";cout << oss.str();_channel_map[_channel_name]._producer_message_map[resp->productor_tag()].push_back(oss.str());}// 订阅相关void basicConsume(const string &queue_name, const string &consumer_tag, bool auto_ack){ThreadChannel &tchannel = _channel_map[_channel_name];Consumer::ptr consumer = tchannel._consumer;if (consumer.get() != nullptr && consumer->_consumer_tag != consumer_tag){cout << "订阅队列失败,因为该信道已经关联了一个消费者:" << consumer->_consumer_tag << "\n";return;}else if (consumer.get() != nullptr && consumer->_consumer_tag == consumer_tag){cout << "当前消费者已订阅,无需重复订阅" << consumer->_consumer_tag << "\n";return;}// 这里不能根据查找是否有该订阅来判断订阅状态// 因为每个信道只能关联一个消费者,而消费者map当中的这些消费者可以是来自不同信道的消费者// 不同信道不能有同虚拟机,同队列当中同名的消费者,因为信道发挥资源隔离的方式是限制资源可见性// 0. 打包任务auto func = [queue_name, consumer_tag, auto_ack, this](const Channel::ptr &cp){bool ret = cp->BasicConsume(_vhost_name, consumer_tag, queue_name, std::bind(&GlobalResource::ConsumerCallback, this, cp, placeholders::_1, placeholders::_2, placeholders::_3), auto_ack);string info;if (ret){info = "订阅队列成功, 队列名: " + queue_name + ", 消费者标识: " + consumer_tag + "\n";_channel_map[_channel_name]._consumer_message_map.insert({consumer_tag, {}});_channel_map[_channel_name]._consumer = cp->getConsumer();}else{info = "订阅队列失败, 队列名: " + queue_name + ", 消费者标识: " + consumer_tag + "\n";}pushInfo(info);};tchannel.push_task(func);waitInfo();}void consumeCancel(){ThreadChannel &tchannel = _channel_map[_channel_name];Consumer::ptr consumer = tchannel._consumer;if (consumer.get() == nullptr){cout << "当前信道尚未订阅消费者,无法取消订阅\n";return;}auto func = [this, consumer](const Channel::ptr &cp){bool ret = cp->BasicCancel();string info;if (ret){info = "取消队列订阅成功, 队列名: " + consumer->_queue_name + ", 消费者标识: " + consumer->_consumer_tag + "\n";_channel_map[_channel_name]._consumer.reset();}else{info = "取消队列订阅失败\n";}pushInfo(info);};tchannel.push_task(func);waitInfo();}void basicProduce(const string &producer_tag){ThreadChannel &tchannel = _channel_map[_channel_name];Productor::ptr productor = tchannel._productor;if (productor.get() != nullptr && productor->_productor_tag != producer_tag){cout << "关联生产者失败,因为该信道已经关联了一个生产者:" << productor->_productor_tag << "\n";return;}else if (productor.get() != nullptr && productor->_productor_tag == producer_tag){cout << "当前生产者已关联,无需重复关联" << productor->_productor_tag << "\n";return;}// 0. 打包任务auto func = [producer_tag, this](const Channel::ptr &cp){bool ret = cp->ProductorBind(producer_tag, std::bind(&GlobalResource::ProducerCallback, this, cp, placeholders::_1));string info;if (ret){info = "关联生产者成功,生产者tag: " + producer_tag + "\n";_channel_map[_channel_name]._producer_message_map.insert({producer_tag, {}});_channel_map[_channel_name]._productor = cp->getProductor();}else{info = "关联生产者失败,生产者tag: " + producer_tag + "\n";}pushInfo(info);};tchannel.push_task(func);waitInfo();}void produceCancel(){ThreadChannel &tchannel = _channel_map[_channel_name];Productor::ptr producer = tchannel._productor;if (producer.get() == nullptr){cout << "当前信道尚未关联生产者者,无法取消关联\n";return;}auto func = [this, producer](const Channel::ptr &cp){bool ret = cp->ProductorUnBind();string info;if (ret){info = "取消生产者关联成功, 生产者tag: " + producer->_productor_tag + "\n";_channel_map[_channel_name]._productor.reset();}else{info = "取消生产者关联成功\n";}pushInfo(info);};tchannel.push_task(func);waitInfo();}// 消息的发布与拉取void basicPublish(const string &exchange_name, DeliveryMode mode, PublishMechanism mechanism, const string &routing_key, const string &body){ThreadChannel &tchannel = _channel_map[_channel_name];Productor::ptr productor = tchannel._productor;if (productor.get() == nullptr){cout << "发布消息失败,因为当前信道并未关联生产者者, 信道名:" << _channel_name << "\n";return;}auto func = [exchange_name, mode, mechanism, routing_key, body, this](const Channel::ptr &cp){BasicProperities bp;bp.set_routing_key(routing_key);bp.set_mode(mode);bp.set_msg_id(UUIDHelper::uuid());bool ret = cp->BasicPublish(_vhost_name, exchange_name, &bp, body, mechanism);string info;if (ret){info = "消息发布成功, 目标交换机: " + exchange_name + ", 发布方式: " + PublishMechanism_Name(mechanism) + ", 路由键: " + routing_key + ", 消息体: " + body + "\n";}else{info = "消息发布失败, 目标交换机: " + exchange_name + "\n";}pushInfo(info);};tchannel.push_task(func);waitInfo();}void basicPull(){ThreadChannel &tchannel = _channel_map[_channel_name];Productor::ptr producer = tchannel._productor;if (producer.get() == nullptr){cout << "拉取失败,因为该信道并未关联生产者, 信道名:" << _channel_name << "\n";return;}auto func = [this](const Channel::ptr &cp){Consumer::ptr consumer = cp->getConsumer();bool ret = cp->BasicPull();string info;if (ret){info = "拉取消息成功, 消费者: " + consumer->_consumer_tag;}else{info = "拉取消息失败, 消费者: " + consumer->_consumer_tag;}pushInfo(info);};tchannel.push_task(func);waitInfo();}void showConsumerMessage(const string &consumer_tag){auto iter = _channel_map[_channel_name]._consumer_message_map.find(consumer_tag);if (iter == _channel_map[_channel_name]._consumer_message_map.end()){cout << "该消费者不存在, 消费者tag: " << consumer_tag << "\n";}else{for (auto &message : iter->second){cout << message << "\n";}}}void showProducerMessage(const string &producer_tag){auto iter = _channel_map[_channel_name]._producer_message_map.find(producer_tag);if (iter == _channel_map[_channel_name]._producer_message_map.end()){cout << "该生产者不存在, 生产者tag: " << producer_tag << "\n";}else{for (auto &message : iter->second){cout << message << "\n";}}}// 选择信道之前调用的命令行解析函数void ChannelSelectionParse(const string &command_line){smatch match;// 获取提示信息if (regex_match(command_line, regex(R"(\s*man\s*)"))){man();}// 清屏else if (regex_match(command_line, regex(R"(\s*clear\s*)"))){clear_screen();}// 选择信道else if (regex_match(command_line, match, regex(R"#(\s*use\s+channel_name\s*=\s*(\S+)\s*)#"))){string channel_name = match[1];useChannel(channel_name);}// 退出else if (regex_match(command_line, regex(R"(\s*quit\s*)"))){quit();}else{cout << "请先选择信道,在进行操作!! 方法: use channel_name=你想选择的信道\n";}}// 选择虚拟机之前调用的命令行解析函数void VirtualHostSelectionParse(const std::string &command_line){smatch match;// 获取提示信息if (regex_match(command_line, regex(R"(\s*man\s*)"))){man();}// 清屏else if (regex_match(command_line, regex(R"(\s*clear\s*)"))){clear_screen();}else if (regex_match(command_line, match, regex(R"#(\s*disuse\s+channel\s*)#"))){disuseChannel();}else if (regex_match(command_line, match, regex(R"#(\s*create\s+virtual_host\s*=\s*(\S+)\s*)#"))){string vhost_name = match[1];declareVirtualHost(vhost_name);}else if (regex_match(command_line, match, regex(R"#(\s*drop\s+virtual_host\s*=\s*(\S+)\s*)#"))){// 1. 拿到虚拟机名称std::string vhost_name = match[1];eraseVirtualHost(vhost_name);}else if (regex_match(command_line, match, regex(R"#(\s*use\s+virtual_host\s*=\s*(\S+)\s*)#"))){string vhost_name = match[1];useVirtualHost(vhost_name);}// 退出else if (regex_match(command_line, regex(R"(\s*quit\s*)"))){quit();}else if (regex_match(command_line, regex(R"(\s*show\s+all_virtual_host\s*)"))){showAllVirtualHost();}else{cout << "请先选择虚拟机,在进行操作!! 方法: use virtual_host=你想选择的虚拟机\n";}}// 选择虚拟机之后调用的命令行解析函数void FormalBusinessParse(const std::string &command_line){smatch match;// 获取提示信息if (regex_match(command_line, regex(R"(\s*man\s*)"))){man();}// 清屏else if (regex_match(command_line, regex(R"(\s*clear\s*)"))){clear_screen();}// 信道与虚拟机相关else if (regex_match(command_line, match, regex(R"#(\s*disuse\s+channel\s*)#"))){disuseChannel();}else if (regex_match(command_line, match, regex(R"#(\s*create\s+virtual_host\s*=\s*(\S+)\s*)#"))){string vhost_name = match[1];declareVirtualHost(vhost_name);}else if (regex_match(command_line, match, regex(R"#(\s*drop\s+virtual_host\s*=\s*(\S+)\s*)#"))){// 1. 拿到虚拟机名称std::string vhost_name = match[1];eraseVirtualHost(vhost_name);}else if (regex_match(command_line, match, regex(R"#(\s*disuse\s+virtual_host\s*)#"))){disuseVirtualHost();}else if (regex_match(command_line, regex(R"(\s*show\s+all_virtual_host\s*)"))){showAllVirtualHost();}else if (regex_match(command_line, match, regex(R"#(\s*show\s+virtual_host_info\s*=\s*(\S+)\s*)#"))){string vhost_name = match[1];showVirtualHost(vhost_name);}// 交换机相关else if (regex_match(command_line, match, regex(R"#(\s*create\s+exchange_name\s*=\s*(\S+)\s+type\s*=\s*(direct|fanout|topic)\s+durable\s*=\s*(true|false)\s+auto_delete\s*=\s*(true|false)\s*)#"))){// match[0]是整个command_line   交换机名称当中不允许带空格string exchange_name = match[1];ExchangeType type;bool durable = match[3] == "true" ? true : false;bool auto_delete = match[4] == "true" ? true : false;if (match[2] == "direct")type = DIRECT;else if (match[2] == "fanout")type = FANOUT;elsetype = TOPIC;declareExchange(exchange_name, type, durable, auto_delete);}else if (regex_match(command_line, match, regex(R"#(\s*drop\s+exchange_name\s*=\s*(\S+)\s*)#"))){string exchange_name = match[1];eraseExchange(exchange_name);}else if (regex_search(command_line, regex(R"(\s*show\s+all_exchange_info\s*)"))){cout << "showAllExchange()即将调用\n";showAllExchange();}else if (regex_match(command_line, match, regex(R"#(\s*show\s+exchange_info\s*=\s*(\S+)\s*)#"))){string exchange_name = match[1];showExchange(exchange_name);}// 队列相关else if (regex_match(command_line, match, regex(R"#(\s*create\s+queue_name\s*=\s*(\S+)\s+durable\s*=\s*(true|false)\s+auto_delete\s*=\s*(true|false)\s+exclusive\s*=\s*(true|false)\s*)#"))){string queue_name = match[1];bool durable = match[2] == "true" ? true : false;bool auto_delete = match[3] == "true" ? true : false;bool exclusive = match[4] == "true" ? true : false;declareMsgQueue(queue_name, durable, auto_delete, exclusive);}else if (regex_match(command_line, match, regex(R"#(\s*drop\s+queue_name\s*=\s*(\S+)\s*)#"))){string queue_name = match[1];eraseMsgQueue(queue_name);}else if (regex_match(command_line, regex(R"(\s*show\s+all_queue_info\s*)"))){cout << "showAllMsgQueue()即将调用\n";showAllMsgQueue();}else if (regex_match(command_line, match, regex(R"#(\s*show\s+queue_info\s*=\s*(\S+)\s*)#"))){string queue_name = match[1];showMsgQueue(queue_name);}// 绑定相关else if (regex_match(command_line, match, regex(R"#(\s*bind\s+queue_name\s*=\s*(\S+)\s+to\s+exchange_name\s*=\s*(\S+)\s+with\s+binding_key\s*=\s*(\S+)\s*)#"))){string queue_name = match[1];string exchange_name = match[2];string binding_key = match[3];bind(queue_name, exchange_name, binding_key);}else if (regex_match(command_line, match, regex(R"#(\s*unbind\s+queue_name\s*=\s*(\S+)\s+to\s+exchange_name\s*=\s*(\S+)\s*)#"))){string queue_name = match[1];string exchange_name = match[2];unBind(queue_name, exchange_name);}else if (regex_match(command_line, regex(R"(\s*show\s+all_binding_info\s*)"))){showAllBindings();}// 订阅相关else if (regex_match(command_line, match, regex(R"#(\s*consume\s+queue_name\s*=\s*(\S+)\s+consumer_tag\s*=\s*(\S+)\s+auto_ack\s*=\s*(\S+)\s*)#"))){string queue_name = match[1];string consumer_tag = match[2];bool auto_ack = match[3] == "true" ? true : false;basicConsume(queue_name, consumer_tag, auto_ack);}else if (regex_match(command_line, regex(R"(\s*cancel\s+this_consume\s*)"))){consumeCancel();}else if (regex_match(command_line, match, regex(R"#(\s*produce\s+producer_tag\s*=\s*(\S+)\s*)#"))){string producer_tag = match[1];basicProduce(producer_tag);}else if (regex_match(command_line, regex(R"#(\s*cancel\s+this_produce\s*)#"))){produceCancel();}// 消息的发布与拉取else if (regex_match(command_line, match, regex(R"#(\s*publish\s+message\s+exchange_name\s*=\s*(\S+)\s+delivery_mode\s*=\s*(durable|undurable)\s+mechanism\s*=\s*(push|pull|both)\s+routing_key\s*=\s*(\S+)\s+body\s*=\s*(.*))#"))){string exchange_name = match[1];DeliveryMode mode = (match[2] == "durable" ? DURABLE : UNDURABLE);PublishMechanism mechanism;if (match[3] == "push")mechanism = PUSH;else if (match[3] == "pull")mechanism = PULL;else if (match[3] == "both")mechanism = BOTH;string routing_key = match[4];string body = match[5];basicPublish(exchange_name, mode, mechanism, routing_key, body);}else if (regex_match(command_line, regex(R"(\s*pull\s+message\s*)"))){basicPull();}else if (regex_match(command_line, match, regex(R"#(\s*show\s+consume_message\s+consumer_tag\s*=\s*(\S+)\s*)#"))){string consumer_tag = match[1];showConsumerMessage(consumer_tag);}else if (regex_match(command_line, match, regex(R"#(\s*show\s+publish_message\s+producer_tag\s*=\s*(\S+)\s*)#"))){string producer_tag = match[1];showProducerMessage(producer_tag);}// 退出else if (regex_match(command_line, regex(R"(\s*quit\s*)"))){quit();}else{cout << "指令错误,请输入正确指令\n";}}// 拿取执行结果,给用户进行打印void waitInfo(){ThreadChannel &tchannel = _channel_map[_channel_name];cout << "执行结果:" << tchannel.pop_info();}// 将执行结果放入info_queue当中void pushInfo(const std::string info){ThreadChannel &tchannel = _channel_map[_channel_name];tchannel.push_info(info);}// 主循环函数void main_routine(){while (running){// 1. 打印命令行提示符cout << "<CLImq ";string command_line;getline(cin, command_line);if (command_line.empty()) // 纯换行{cout << "\n";}else if (!_channel_ok){ChannelSelectionParse(command_line);}else if (!_vhost_ok){VirtualHostSelectionParse(command_line);}else{FormalBusinessParse(command_line);}}cout << "bye\n";}// 退出函数void quit(){for (auto &kv : _channel_map){kv.second.shutdown();}running = false;}unordered_map<string, ThreadChannel> _channel_map;unordered_map<string, VirtualHostElem> _vhost_map;string _channel_name;bool _channel_ok = false;string _vhost_name;bool _vhost_ok = false;muduo::CountDownLatch init_latch;mutex init_mtx;condition_variable init_cond;bool init_ok = false;bool running = true;
};// ./climq channel_num channel_name_prefix
int main(int argc, char *argv[])
{if (argc != 3){cout << "Usage: " << argv[0] << " channel_num channel_name_prefix\n";return 1;}shared_ptr<GlobalResource> global_resource = std::make_shared<GlobalResource>(stoi(argv[1]), argv[2]);global_resource->main_routine();return 0;
}

八、演示

一共录制了10分钟的视频,上传到CSDN上面去了,链接:

大家可以放开了测试,项目代码看我的gitee:

以上就是项目扩展五:command-line interface版本的实现的全部内容,希望能对大家有所帮助!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/148214.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

STM32精确控制步进电机

目的&#xff1a;学习使用STM32电机驱动器步进电机&#xff0c;进行电机运动精确控制。 测试环境&#xff1a; MCU主控芯片STM32F103RCT6 &#xff1b;A4988步进电机驱动器模块&#xff1b;微型2相4线步进电机10mm丝杆滑台&#xff0c;金属丝杆安装有滑块。 10mm二相四线微型…

机器学习之非监督学习(二)异常检测(基于高斯概率密度)

机器学习之非监督学习&#xff08;二&#xff09;异常检测&#xff08;基于高斯概率密度&#xff09; 0. 文章传送1.案例引入2.高斯正态分布3.异常检测算法4.异常检测 vs 监督学习5.算法优化6.代码实现 0. 文章传送 机器学习之监督学习&#xff08;一&#xff09;线性回归、多…

C语言中数组和字符串的联系

一、C语言中&#xff0c;数组和字符串 1、C语言中&#xff0c;定义一个数组后&#xff0c;数组名保存的是这个数组的首地址。类似一个指向数组第一个元素的指针&#xff0c;但是这个指针不能重新指向。2、字符串在C语言中是通过字符数组来实现的&#xff0c;也就是说字符串还是…

【小沐学CAD】3ds Max常见操作汇总

文章目录 1、简介2、二次开发2.1 C 和 3ds Max C SDK2.2 NET 和 3ds Max .NET API2.3 3ds Max 中的 Python 脚本2.4 3ds Max 中的 MAXScript 脚本 3、快捷键3.1 3Dmax键快捷键命令——按字母排序3.2 3dmax快捷键命令——数字键3.3 3dmax功能键快捷键命令3.4 3Dmax常用快捷键——…

Elasticsearch 完整格式的 URL 进行分词,有什么好的解决方案吗?

1、问题描述 我想对完整格式的 url 进行分词&#xff0c;请问有什么好的解决方案吗&#xff1f; 比如&#xff1a;https://www.abc.com/any/path?param_1some&param-2other#title 看了官方的分词器&#xff0c;感觉没啥合适的? 预处理的话&#xff0c;又不知道该怎么处理…

Unity对象池的高级写法 (Plus优化版)

唐老师关于对物体分类的OOD的写法确实十分好&#xff0c;代码也耦合度也低&#xff0c;但是我有个简单的写法同样能实现一样的效果&#xff0c;所以我就充分发挥了一下主观能动性 相较于基本功能&#xff0c;这一版做出了如下改动 1.限制了对象池最大数量&#xff0c;多出来的…

C++11 可变的模板参数

前言 本期我们接着继续介绍C11的新特性&#xff0c;本期我们介绍的这个新特性是很多人都感觉抽象的语法&#xff01;它就是可变的模板参数&#xff01; 目录 前言 一、可变的模板参数 1.1可变的参数列表 1.2可变的参数包 1.3可变参数包的解析 • 递归展开解析 • 逗号…

微服务Docker相关指令

1、拉取容器到镜像仓库 docker pull xxx //拉取指令到 镜像仓库 例如 docker pull mysql 、docker pull nginx docker images //查看镜像仓库 2、删除资源 2.1、删除镜像仓库中的资源 docker rmi mysql:latest //删除方式一&#xff1a;格式 docker rmi 要…

【解密 Kotlin 扩展函数】扩展函数的创建(十六)

导读大纲 1.1 为第三方的类添加方法: 扩展函数 1.1 为第三方的类添加方法: 扩展函数 Kotlin 的主题之一是与现有代码的平滑集成 即使是纯 Kotlin 项目,也是构建在 Java 库之上的 如 JDK、Android 框架和其他第三方框架 而当你将 Kotlin 集成到 Java 项目中时 你还要处理尚未或不…

python爬虫:将知乎专栏文章转为pdf

欢迎关注本人的知乎主页~ 实现思路 用户输入专栏ID&#xff1a; 代码首先提示用户输入一个知乎专栏的ID&#xff0c;默认值为 c_1747690982282477569。输入的ID用于构建API请求的URL。 发送HTTP请求&#xff1a; 使用 requests.get() 向知乎API发送GET请求&#xff0c;获取指定…

【QGIS入门实战精品教程】6.1:QGIS根据属性条件查询数据(SQL表达式)

文章目录 一、字段过滤二、高级过滤(表达式)一、字段过滤 对于单个字段的查询,可以采用字段过滤,例如,从县区数据中,根据NAME字段,查找出县级市玉门市。操作为:右键县区→打开属性表: 点击左下角,选择name字段。 输入玉门市,回车,选择查找除的属性表记录,此时图斑…

【Linux】入门【更详细,带实操】

Linux全套讲解系列&#xff0c;参考视频-B站韩顺平&#xff0c;本文的讲解更为详细 目录 1、课程内容 2、应用领域 3、概述 4、 Linux和Unix 5、VMware15.5和CentOS7.6安装 6、网络连接三种方式 7、虚拟机克隆 8、虚拟机快照 9、虚拟机迁移删除 10、vmtools 11、目录…

set-ExecutionPolicy RemoteSigned 提示不是内部或外部命令,也不是可运行的程序或批处理文件

这个错误一般发生在使用命令提示符或者PowerShell窗口中找不到set-ExecutionPolicy RemoteSigned。如果你想在命令提示符或者PowerShell窗口运行set-ExecutionPolicy RemoteSigned&#xff0c;你需要搜索打开Window PowerShell ISE&#xff0c;并以管理员身份打开&#xff0c;输…

基于微信小程序的美食外卖管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码 精品专栏&#xff1a;Java精选实战项目…

Tomcat系列漏洞复现

CVE-2017-12615——Tomcat put⽅法任意⽂件写⼊漏洞 漏洞描述 当 Tomcat运⾏在Windows操作系统时&#xff0c;且启⽤了HTTP PUT请求⽅法&#xff08;例如&#xff0c;将 readonly初始化参数由默认值设置为false&#xff09;&#xff0c;攻击者将有可能可通过精⼼构造的攻击请求…

身份安全风险不断上升:企业为何必须立即采取行动

在推动安全AI 模型的过程中&#xff0c;许多组织已转向差异隐私。但这种旨在保护用户数据的工具是否阻碍了创新&#xff1f; 开发人员面临一个艰难的选择&#xff1a;平衡数据隐私或优先考虑精确结果。差分隐私可以保护数据&#xff0c;但通常以牺牲准确性为代价——对于医疗保…

某省公共资源交易中心爬虫逆向分析

目标网站 aHR0cHM6Ly95Z3AuZ2R6d2Z3Lmdvdi5jbi8jLzQ0L3NjenQteHEvP3VzZXJJZD02NzM4OTg2MzkyNjA3NzAzMDQmcm93SWQ9NTI1MDYyMDI2ODg0NzE2NTQ0JnRpbWU9MjAwOC0xMS0yNiZjZXJ0aWZpY2F0ZU5vPTkxNDQwOTA0NjgyNDI2MzU4QyZjZXJ0aWZpY2F0ZVR5cGU9Mjg 一、抓包分析 请求头参数加密 二、…

【学习笔记】手写 Tomcat 五

目录 一、优化 Servlet 创建一个抽象类 继承抽象类 二、三层架构 业务逻辑层 数据访问层 1. 在 Dao 层操作数据库 2. 调用 Dao 层&#xff0c;实现业务逻辑功能 3. 调用 Service 层&#xff0c;响应数据 测试 三、数据库连接池 1. 手写数据库连接池 2. 创建数据库…

2024年9月19日---关于ES6(2)

五 异步编程 5.1 回调函数 5.1.1 概念 回调函数(callback function)&#xff0c;当一个函数作为参数传入另一个参数中&#xff0c;并且它不会立即执行&#xff0c;只有当满足一定条件后该函数才可以执行&#xff0c;这种函数就称为回调函数。 你可以将其理解为 回头再调用的…

GNU编译器(GCC):编译的4个过程及.elf、.list、.map文件功能说明

0 参考资料 GNU-LD-v2.30-中文手册.pdf GNU linker.pdf1 前言 一个完整的编译工具链应该包含以下4个部分&#xff1a; &#xff08;1&#xff09;编译器 &#xff08;2&#xff09;汇编器 &#xff08;3&#xff09;链接器 &#xff08;4&#xff09;lib库 在GNU工具链中&…