目录
一.Channel模块介绍
对于信道管理Channel来说,这是一个在网络通信中的概念,表示的就是通信通道,当进行网络通信的时候,必然是借助一个网络通信连接来完成的,为了更加方便的进行资源的利用,因此要对于通信连接进行了一个更加进一步的细化,细化出了通信通道。
对于用户来说,一个通信通道就是网络通信的载体,而一个真正的通信连接,是可以创建出多个通信通道每一个信道和信道之间,在使用者的角度来看是相互独立的存在,所有的网络通信服务都是由信道提供的
二.网络通信请求和响应的定义
用protobuf定义出来的请求和响应格式,将所需要的参数封装起来,方便后续使用。
都包含请求id:rid,信道id:chid
syntax = "proto3";
package msg;
import "msg.proto";
//信道相关
message OpenChannelReq
{
string rid = 1;
string chid = 2;
};
message CloseChannelReq
{
string rid = 1;
string chid = 2;
};
//交换机相关
message DeclareExchangeReq
{
string rid = 1; // 请求id
string chid = 2; // 信道id
string e_name = 3;// 交换机名称
ExchangeType e_type = 4;// 交换机类型
bool durable = 5;// 持久化
bool auto_delete = 6;// 自动删除
map<string, string> args = 7;// 其他参数
};
message RemoveExchangeReq
{
string rid = 1;
string chid = 2;
string e_name = 3;
};
//队列相关
message DeclareQueueReq
{
string rid = 1;
string chid = 2;
string q_name = 3;
bool durable = 4;
bool exclusive = 5;
bool auto_delete = 6;
map<string, string> args = 7;
};
message RemoveQueueReq
{
string rid = 1;
string chid = 2;
string q_name = 3;
};
//绑定相关
message BindReq
{
string rid = 1;
string chid = 2;
string e_name = 3;
string q_name = 4;
string bind_key = 5;
};
message UnbindReq
{
string rid = 1;
string chid = 2;
string e_name = 3;
string q_name = 4;
};
//消息相关
message BasicPublishReq//发布消息
{
string rid = 1;
string chid = 2;
string e_name = 3; // 消息发布给交换机
BasicAttributes attr = 4;// 消息属性
string body = 5;// 消息体
};
message BasicAckReq//消息确认
{
string rid = 1;
string chid = 2;
string q_name = 3;
string messagg_id = 4;
};
// 订阅和取消订阅
message BasicConsumeReq //某个消费者订阅某个队列
{
string rid = 1;
string chid = 2;
string consumer_tag = 3;
string q_name = 4;
bool auto_ack = 5;
};
message BasicCancelReq
{
string rid = 1;
string chid = 2;
string consumer_tag = 3;
string q_name = 4;
};
//消息的推送
message BasicConsumeRsp
{
string chid = 1;// 信道id
string consumer_tag = 2;
string body = 3;
BasicAttributes attr = 4;
}
//通用响应
message BasicCommonResponse
{
string rid = 1; // 响应id,与请求id一致
string chid = 2;
bool success = 3;
};
三.Channel的定义
成员变量
class Channel
{
private:
std::string _chid; // 信道ID
VirtualHost::ptr _vhost; // 虚拟主机句柄
Consumer::ptr _consumer; // 信道关联的消费者
ConsumerManager::ptr _cmp; // 消费者管理器
ProtobufCodecPtr _codec; // Protobuf协议处理器, 处理接收到的请求数据
muduo::net::TcpConnectionPtr _conn; // TCP连接
ThreadPool::ptr _pool; // 线程池
};
_chid
:每个信道都有一个唯一的ID用于标识。_vhost
:虚拟主机,管理多个交换机、队列的对象。_consumer
:当前信道的消费者。_cmp
:管理所有消费者的消费者管理器。_codec
:负责处理Protobuf协议的编解码器。_conn
:通过这个TCP连接与客户端进行通信。_pool
:线程池,用于处理异步任务。
构造和析构函数
Channel
类提供了两个构造函数,一个是默认构造函数,另一个是带参数的构造函数。
析构函数则用于在信道销毁时,移除该信道channel对应的消费者。
Channel()
{
DLOG("channel:%p created", this);
}
Channel(const std::string &chid,
const VirtualHost::ptr &vhost,
const ConsumerManager::ptr &cmp,
const muduo::net::TcpConnectionPtr &conn,
const ProtobufCodecPtr &codec,
const ThreadPool::ptr &pool)
: _chid(chid),
_vhost(vhost),
_cmp(cmp),
_codec(codec),
_conn(conn),
_pool(pool)
{
DLOG("channel:%p created", this);
}
~Channel()
{
if (_cmp.get() != nullptr)
{
_cmp->remove(_consumer->_qname, _consumer->_tag);
DLOG("channel:%p destroyed", this);
}
}
CommonResponse
完成某个常规操作时,发回给客户端的默认相应,方便客户端判断该操作是否成功完成。
void basicCommonResponse(const std::string &rid, const std::string &chid, bool success)
{
msg::BasicCommonResponse resp;
resp.set_rid(rid);
resp.set_chid(chid);
resp.set_success(success);
return _codec->send(_conn, resp);
}
交换机的声明与删除
declareExchange
:调用_vhost
的declareExchange
方法声明一个交换机,并返回操作结果。removeExchange
:调用_vhost
的removeExchange
方法删除一个交换机,并返回操作结果。
void declareExchange(const DeclareExchangeReqPtr &req)
{
bool ret = _vhost->declareExchange(req->e_name(),
req->e_type(), req->durable(),
req->auto_delete(), req->args());
return basicCommonResponse(req->rid(), req->chid(), ret);
}
void removeExchange(const RemoveExchangeReqPtr &req)
{
bool ret = _vhost->removeExchange(req->e_name());
return basicCommonResponse(req->rid(), req->chid(), ret);
}
队列的声明和删除
declareQueue
:在_vhost
中声明一个队列,并且在消费者管理器_cmp
中初始化该队列的消费者。removeQueue
:在_vhost
中删除队列,同时在消费者管理器中销毁与该队列相关的消费者。
// 2. 声明/删除 队列
void declareQueue(const DeclareQueueReqPtr &req)
{
//_vhost中添加队列并初始化QueueMsg
// channel中需要初始化该队列的QueueConsumer
bool ret = _vhost->declareQueue(req->q_name(),
req->durable(),
req->exclusive(),
req->auto_delete(),
req->args());
if (!ret)
return basicCommonResponse(req->rid(), req->chid(), ret);
_cmp->initQueueConsumer(req->q_name());
return basicCommonResponse(req->rid(), req->chid(), ret);
}
void removeQueue(const RemoveQueueReqPtr &req)
{
//_vhost中移除队列+消息+绑定
// channel中需要额外移除该队列的QueueConsumer
bool ret = _vhost->removeQueue(req->q_name());
if (!ret)
return basicCommonResponse(req->rid(), req->chid(), ret);
_cmp->destroyQueueConsumer(req->q_name());
return basicCommonResponse(req->rid(), req->chid(), ret);
}
绑定和解绑
绑定和解绑只涉及到交换机,队列和绑定模块,与消费者模块无关,调用_vhost相应接口即可。
// 3. 绑定/解绑
void bind(const BindReqPtr &req)
{
bool ret = _vhost->bind(req->e_name(), req->q_name(), req->bind_key());
return basicCommonResponse(req->rid(), req->chid(), ret);
}
void unbind(const UnbindReqPtr &req)
{
bool ret = _vhost->unbind(req->e_name(), req->q_name());
return basicCommonResponse(req->rid(), req->chid(), ret);
}
消息的发布和确认
先从虚拟机中找到对应的交换机和队列,然后根据路由键将消息发布到符合条件的队列中。
每个消息的消费任务会被封装成一个异步任务,提交到线程池中执行。
// 4. 发布/确认 消息到某个队列
void basicPublish(const BasicPublishReqPtr &req) // 生产者发布消息
{
// 1.获取要发布给的交换机的信息
Exchange::ptr p_exchange = _vhost->selectExchange(req->e_name());
if (p_exchange.get() == nullptr)
{
DLOG("basicPublish: exchange not found %s", p_exchange->_name.c_str());
return basicCommonResponse(req->rid(), req->chid(), false);
}
// 2.获取该交换机绑定的所有队列
MsgQueueBindingMap queue_bind_map = _vhost->selectByExchange(req->e_name());
if (queue_bind_map.empty())
{
DLOG("basicPublish: queue not found %s", p_exchange->_name.c_str());
return basicCommonResponse(req->rid(), req->chid(), false);
}
// 3.遍历所有队列,进行交换路由,成功则将该发布消息到该队列
for (auto &kv : queue_bind_map)
{
bool ret = Routine::route(p_exchange->_type,
req->attr().routine_key(),
kv.second->_binding_key);
if (ret)
{
// 将消息添加到队列中(添加消息的管理)
_vhost->basicPublish(kv.first, req->mutable_attr(), req->body());
// 4.消费者消费该条消息,将消息封装成任务,push到线程池的任务队列并异步执行
Consumer::ptr p_consumer = _cmp->choose(kv.first);
auto task = std::bind(&Channel::consume, this, kv.first);
_pool->push(task);
}
}
return basicCommonResponse(req->rid(), req->chid(), true);
}
consume函数(消费任务)
实现了某个队列consume一条消息的过程。
void consume(const std::string &qname)
{
// 1.取出一条消息
msg_ptr msgp = _vhost->basicConsume(qname);
if (msgp.get() == nullptr)
{
DLOG("consume: msg not found %s", qname.c_str());
return;
}
// 2.取出一个消费者
Consumer::ptr p_consumer = _cmp->choose(qname);
if (p_consumer.get() == nullptr)
{
DLOG("consume: consumer not found %s", qname.c_str());
return;
}
// 3.调用消费者回调函数
p_consumer->_cb(p_consumer->_tag,
msgp->mutable_msg_self()->mutable_basic_attributes(),
msgp->msg_self().body());
// 4.判断是否需要自动ack确认
if (p_consumer->_auto_ack)
{
_vhost->basicAck(p_consumer->_qname, msgp->msg_self().basic_attributes().id());
}
}
callback函数(消息处理)
// 消费者回调函数
void callback(const std::string &consumer_tag, const msg::BasicAttributes *pbasic,
const std::string &body)
{
// 1.封装响应消息
msg::BasicConsumeRsp rsp;
rsp.set_chid(_chid);
rsp.set_consumer_tag(consumer_tag);
std::string tmp_body = "这是消费者回调函数返回的消息: ";
tmp_body += body;
rsp.set_body(tmp_body);
if (pbasic)
{
rsp.mutable_attr()->CopyFrom(*pbasic);
}
// 2.发送响应消息给客户端
_codec->send(_conn, rsp);
}
消息确认
void basicAck(const BasicAckReqPtr &req)
{
_vhost->basicAck(req->q_name(), req->messagg_id());
return basicCommonResponse(req->rid(), req->chid(), true);
}
消息订阅与取消订阅
订阅时,检查队列是否存在,然后创建消费者并管理。
取消订阅时,移除队列中的这个消费者。
// 5. 订阅/解除订阅 某个队列
void basicSubscribe(const BasicSubscribeReqPtr &req)
{
// 消费者用户订阅某一个队列的消息
// 创建一个消费者给该用户对应的信道里的_consumer,并添加到内存中管理
// 1.判断队列是否存在
if (!_vhost->queueExists(req->q_name()))
{
DLOG("basicSubscribe: queue not found %s", req->q_name().c_str());
return;
}
// 2.创建消费者并管理
auto cb = std::bind(&Channel::callback, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
Consumer::ptr consumer = _cmp->create(req->q_name(), req->consumer_tag(), req->auto_ack(), cb);
_consumer = consumer; // 该信道对应消费者用户
return basicCommonResponse(req->rid(), req->chid(), true);
}
void basicCancel(const BasicCancelReqPtr &req) // 取消订阅
{
_cmp->remove(req->q_name(), _consumer->_tag);
return basicCommonResponse(req->rid(), req->chid(), true);
}
四.ChannelManager的实现
ChannelManager
类负责管理所有的 Channel
实例,提供了 Channel
的创建、关闭和获取等功能。这个类的设计目的是为了有效管理信道资源,并确保线程安全。
成员变量和方法
class ChannelManager
{
private:
std::mutex _mutex; // 互斥锁,用于保护_channel的线程安全
std::unordered_map<std::string, Channel::ptr> _channels; // 存储信道的哈希表
public:
using ptr = std::shared_ptr<ChannelManager>;
ChannelManager() {}
bool openChannel(const std::string &chid,
const VirtualHost::ptr &vhost,
const ConsumerManager::ptr &cmp,
const muduo::net::TcpConnectionPtr &conn,
const ProtobufCodecPtr &codec,
const ThreadPool::ptr &pool);
void closeChannel(const std::string &chid);
Channel::ptr getChannel(const std::string &chid);
};
信道的新增删除获取
bool openChannel(const std::string &chid,
const VirtualHost::ptr &vhost,
const ConsumerManager::ptr &cmp,
const muduo::net::TcpConnectionPtr &conn,
const ProtobufCodecPtr &codec,
const ThreadPool::ptr &pool)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _channels.find(chid);
if (it != _channels.end())
{
DLOG("openChannel:channel already exists %s", chid.c_str());
return true;
}
auto ret = std::make_shared<Channel>(chid, vhost, cmp, conn, codec, pool);
_channels.insert(std::make_pair(chid, ret));
DLOG("openChannel:channel created %s", chid.c_str());
return true;
}
void closeChannel(const std::string &chid)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _channels.find(chid);
if (it == _channels.end())
{
DLOG("closeChannel:channel not exists %s", chid.c_str());
return;
}
_channels.erase(it);
}
Channel::ptr getChannel(const std::string &chid)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _channels.find(chid);
if (it == _channels.end())
{
DLOG("getChannel:channel not exists %s", chid.c_str());
return Channel::ptr();
}
return it->second;
}
五.全部代码
#pragma once
#include "../common_mq/helper.hpp"
#include "../common_mq/logger.hpp"
#include "../common_mq/msg.pb.h"
#include "../common_mq/myproto.pb.h"
#include <string>
#include <unordered_map>
#include <mutex>
#include <memory>
#include <cassert>
#include <cstring>
#include "virtual_host.hpp"
#include "consumer.hpp"
#include "proto/codec.h"
#include "muduo/net/TcpConnection.h"
#include "thread_pool.hpp"
#include "routine.hpp"
namespace mq
{
using ProtobufCodecPtr = std::shared_ptr<ProtobufCodec>;
using OpenChannelPtr = std::shared_ptr<msg::OpenChannelReq>;
using CloseChannelReqPtr = std::shared_ptr<msg::CloseChannelReq>;
using DeclareExchangeReqPtr = std::shared_ptr<msg::DeclareExchangeReq>;
using RemoveExchangeReqPtr = std::shared_ptr<msg::RemoveExchangeReq>;
using DeclareQueueReqPtr = std::shared_ptr<msg::DeclareQueueReq>;
using RemoveQueueReqPtr = std::shared_ptr<msg::RemoveQueueReq>;
using BindReqPtr = std::shared_ptr<msg::BindReq>;
using UnbindReqPtr = std::shared_ptr<msg::UnbindReq>;
using BasicPublishReqPtr = std::shared_ptr<msg::BasicPublishReq>;
using BasicAckReqPtr = std::shared_ptr<msg::BasicAckReq>;
using BasicSubscribeReqPtr = std::shared_ptr<msg::BasicSubscribeReq>;
using BasicCancelReqPtr = std::shared_ptr<msg::BasicCancelReq>;
class Channel
{
private:
std::string _chid; // channel id
VirtualHost::ptr _vhost; // 虚拟机的句柄
Consumer::ptr _consumer; // 信道关联的消费者
ConsumerManager::ptr _cmp; // 消费者管理器
ProtobufCodecPtr _codec; // Protobuf协议处理器,对收到的req数据进行协议处理
muduo::net::TcpConnectionPtr _conn; // tcp连接
ThreadPool::ptr _pool; // 线程池
public:
using ptr = std::shared_ptr<Channel>;
Channel()
{
DLOG("channel:%p created", this);
}
Channel(const std::string &chid,
const VirtualHost::ptr &vhost,
const ConsumerManager::ptr &cmp,
const muduo::net::TcpConnectionPtr &conn,
const ProtobufCodecPtr &codec,
const ThreadPool::ptr &pool)
: _chid(chid),
_vhost(vhost),
_cmp(cmp),
_codec(codec),
_conn(conn),
_pool(pool)
{
DLOG("channel:%p created", this);
}
~Channel() // 在消费者管理器中移除该消费者
{
if (_cmp.get() != nullptr)
{
_cmp->remove(_consumer->_qname, _consumer->_tag);
DLOG("channel:%p destroyed", this);
}
}
// 1. 声明/删除 交换机
void declareExchange(const DeclareExchangeReqPtr &req)
{
bool ret = _vhost->declareExchange(req->e_name(),
req->e_type(), req->durable(),
req->auto_delete(), req->args());
return basicCommonResponse(req->rid(), req->chid(), ret);
}
void removeExchange(const RemoveExchangeReqPtr &req)
{
bool ret = _vhost->removeExchange(req->e_name());
return basicCommonResponse(req->rid(), req->chid(), ret);
}
// 2. 声明/删除 队列
void declareQueue(const DeclareQueueReqPtr &req)
{
//_vhost中添加队列并初始化QueueMsg
// channel中需要初始化该队列的QueueConsumer
bool ret = _vhost->declareQueue(req->q_name(),
req->durable(),
req->exclusive(),
req->auto_delete(),
req->args());
if (!ret)
return basicCommonResponse(req->rid(), req->chid(), ret);
_cmp->initQueueConsumer(req->q_name());
return basicCommonResponse(req->rid(), req->chid(), ret);
}
void removeQueue(const RemoveQueueReqPtr &req)
{
//_vhost中移除队列+消息+绑定
// channel中需要额外移除该队列的QueueConsumer
bool ret = _vhost->removeQueue(req->q_name());
if (!ret)
return basicCommonResponse(req->rid(), req->chid(), ret);
_cmp->destroyQueueConsumer(req->q_name());
return basicCommonResponse(req->rid(), req->chid(), ret);
}
// 3. 绑定/解绑
void bind(const BindReqPtr &req)
{
bool ret = _vhost->bind(req->e_name(), req->q_name(), req->bind_key());
return basicCommonResponse(req->rid(), req->chid(), ret);
}
void unbind(const UnbindReqPtr &req)
{
bool ret = _vhost->unbind(req->e_name(), req->q_name());
return basicCommonResponse(req->rid(), req->chid(), ret);
}
// 4. 发布/确认 消息到某个队列
void basicPublish(const BasicPublishReqPtr &req) // 生产者发布消息
{
// 1.获取要发布给的交换机的信息
Exchange::ptr p_exchange = _vhost->selectExchange(req->e_name());
if (p_exchange.get() == nullptr)
{
DLOG("basicPublish: exchange not found %s", p_exchange->_name.c_str());
return basicCommonResponse(req->rid(), req->chid(), false);
}
// 2.获取该交换机绑定的所有队列
MsgQueueBindingMap queue_bind_map = _vhost->selectByExchange(req->e_name());
if (queue_bind_map.empty())
{
DLOG("basicPublish: queue not found %s", p_exchange->_name.c_str());
return basicCommonResponse(req->rid(), req->chid(), false);
}
// 3.遍历所有队列,进行交换路由,成功则将该发布消息到该队列
for (auto &kv : queue_bind_map)
{
bool ret = Routine::route(p_exchange->_type,
req->attr().routine_key(),
kv.second->_binding_key);
if (ret)
{
// 将消息添加到队列中(添加消息的管理)
_vhost->basicPublish(kv.first, req->mutable_attr(), req->body());
// 4.消费者消费该条消息,将消息封装成任务,push到线程池的任务队列并异步执行
Consumer::ptr p_consumer = _cmp->choose(kv.first);
auto task = std::bind(&Channel::consume, this, kv.first);
_pool->push(task);
}
}
return basicCommonResponse(req->rid(), req->chid(), true);
}
void basicAck(const BasicAckReqPtr &req)
{
_vhost->basicAck(req->q_name(), req->messagg_id());
return basicCommonResponse(req->rid(), req->chid(), true);
}
// 5. 订阅/解除订阅 某个队列
void basicSubscribe(const BasicSubscribeReqPtr &req)
{
// 消费者用户订阅某一个队列的消息
// 创建一个消费者给该用户对应的信道里的_consumer,并添加到内存中管理
// 1.判断队列是否存在
if (!_vhost->queueExists(req->q_name()))
{
DLOG("basicSubscribe: queue not found %s", req->q_name().c_str());
return;
}
// 2.创建消费者并管理
auto cb = std::bind(&Channel::callback, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
Consumer::ptr consumer = _cmp->create(req->q_name(), req->consumer_tag(), req->auto_ack(), cb);
_consumer = consumer; // 该信道对应消费者用户
return basicCommonResponse(req->rid(), req->chid(), true);
}
void basicCancel(const BasicCancelReqPtr &req) // 取消订阅
{
_cmp->remove(req->q_name(), _consumer->_tag);
return basicCommonResponse(req->rid(), req->chid(), true);
}
private:
// 消费者回调函数
void callback(const std::string &consumer_tag, const msg::BasicAttributes *pbasic,
const std::string &body)
{
// 1.封装响应消息
msg::BasicConsumeRsp rsp;
rsp.set_chid(_chid);
rsp.set_consumer_tag(consumer_tag);
std::string tmp_body = "这是消费者回调函数返回的消息: ";
tmp_body += body;
rsp.set_body(tmp_body);
if (pbasic)
{
rsp.mutable_attr()->CopyFrom(*pbasic);
}
// 2.发送响应消息给客户端
_codec->send(_conn, rsp);
}
void consume(const std::string &qname)
{
// 1.取出一条消息
msg_ptr msgp = _vhost->basicConsume(qname);
if (msgp.get() == nullptr)
{
DLOG("consume: msg not found %s", qname.c_str());
return;
}
// 2.取出一个消费者
Consumer::ptr p_consumer = _cmp->choose(qname);
if (p_consumer.get() == nullptr)
{
DLOG("consume: consumer not found %s", qname.c_str());
return;
}
// 3.调用消费者回调函数
p_consumer->_cb(p_consumer->_tag,
msgp->mutable_msg_self()->mutable_basic_attributes(),
msgp->msg_self().body());
// 4.判断是否需要自动ack确认
if (p_consumer->_auto_ack)
{
_vhost->basicAck(p_consumer->_qname, msgp->msg_self().basic_attributes().id());
}
}
void basicCommonResponse(const std::string &rid, const std::string &chid, bool success)
{
msg::BasicCommonResponse resp;
resp.set_rid(rid);
resp.set_chid(chid);
resp.set_success(success);
return _codec->send(_conn, resp);
}
};
class ChannelManager
{
private:
std::mutex _mutex;
std::unordered_map<std::string, Channel::ptr> _channels;
public:
using ptr = std::shared_ptr<ChannelManager>;
ChannelManager() {}
bool openChannel(const std::string &chid,
const VirtualHost::ptr &vhost,
const ConsumerManager::ptr &cmp,
const muduo::net::TcpConnectionPtr &conn,
const ProtobufCodecPtr &codec,
const ThreadPool::ptr &pool)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _channels.find(chid);
if (it != _channels.end())
{
DLOG("openChannel:channel already exists %s", chid.c_str());
return true;
}
auto ret = std::make_shared<Channel>(chid, vhost, cmp, conn, codec, pool);
_channels.insert(std::make_pair(chid, ret));
DLOG("openChannel:channel created %s", chid.c_str());
return true;
}
void closeChannel(const std::string &chid)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _channels.find(chid);
if (it == _channels.end())
{
DLOG("closeChannel:channel not exists %s", chid.c_str());
return;
}
_channels.erase(it);
}
Channel::ptr getChannel(const std::string &chid)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _channels.find(chid);
if (it == _channels.end())
{
DLOG("getChannel:channel not exists %s", chid.c_str());
return Channel::ptr();
}
return it->second;
}
};
};