项目扩展五:command-line interface版本的实现
CLI(command-line interface)命令行界面,类似于我们常用的shell就是采用CLI进行交互的
与之相对的有GUI(图形交互界面,比如我们常用的Windows和手机)
一、CLI交互的设计
1.为何要设计这个CLI交互
RabbitMQ官方支持图形化界面进行操作,诸如创建虚拟机,交换机,发布消息等等操作
而这个图形化界面的实现,后端代码我们好写,但是也要写前端代码,所以我们就不用图形化界面了
而是仿照mysql类似的方法,实现一个CLI命令行界面
2.具体设计
使用步骤:
- 启动服务
- 使用信道
- 使用虚拟机
- 在该虚拟机上进行操作
一个从线程维护一个信道,从线程负责提供具体服务
主线程负责解释命令行指令,分发任务,打印输出消息
我们选择让主线程打印输出消息是因为:
主线程需要串行化执行所有指令,跟用户交互时,用户输入一个指令,就需要给用户反馈执行情况
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: 消费的消息body
unordered_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.信道模块
信道模块具体函数的任务是:
- 构建req并发送
- 等待基础响应
(我们要保证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