1. 信道管理
在 AMQP 模型中,除了通信连接 Connection 概念外,还有一个 Channel 的概念,Channel 是针对 Connection 连接的一个更细粒度的通信信道,多个 Channel 可以使用同一个通信连接 Connection 进行通信,但是同一个 Connection 的 Channel 之间相互独立。
信道本身不直接进行网络通信,而是通过其所属的Connection进行网络通信。 更具体地说:
- 当我们通过Channel发送一个AMQP方法(比如声明队列、发布消息等)时,实际上是将该方法封装成一个AMQP帧,然后通过Connection的TCP连接发送出去。
- 同样,当服务器通过Channel向客户端发送数据(比如消息推送)时,也是将数据封装成AMQP帧,然后通过同一个TCP连接发送,并在帧头中指定信道ID。
而信道模块就是再次将之前的模块进行整合提供服务的模块
- 管理信息:
- 信道 ID:信道的唯一标识
- 信道关联的消费者:用于消费者信道在关闭的时候取消订阅,删除订阅者信息
- 信道关联的连接:用于向客户端发送数据(响应,推送的消息)
- protobuf 协议处理句柄:网络通信前的协议处理
- 消费者管理句柄:信道关闭/取消订阅的时候,通过句柄删除订阅者信息
- 虚拟机句柄:交换机/队列/绑定/消息数据管理
- 工作线程池句柄(一条消息被发布到队列后,需要将消息推送给订阅了对应队列的消费者,过程由线程池完成)
- 管理操作:
- 提供声明&删除交换机操作(删除交换机的同时删除交换机关联的绑定信息)
- 提供声明&删除队列操作(删除队列的同时,删除队列关联的绑定信息,消息,消费者信息)
- 提供绑定&解绑队列操作
- 提供订阅&取消订阅队列消息操作
- 提供发布&确认消息操作
- 信道管理
- 信道的增删查。
在AMQP中,信道(Channel)是逻辑上的通信通道,所有的AMQP协议操作(如声明队列、发布消息等)都是在信道上进行的。为了使得信道管理模块能够处理各种操作,需要定义相应的请求和响应参数
2. 网络通信协议设计
2.1 设计应用层协议
使用二进制的方式设计应用层协议。 因为 MQMessage 的消息体是使用 Protobuf 进行序列化的,本身是按照二进制存储的,所以不太适合用 json 等文本格式来定义协议。
下面我们设计一下应用层协议:请求/响应报文设计

- len:4 个字节, 表示整个报文的长度
- nameLen: 4 个字节, 表示 typeName 数组的长度
- typeName:是个字节数组, 占 nameLen 个字节, 表示请求/响应报文的类型名,作用是分发不同消息到对应的远端接口调用中
- protobufData:是个字节数组, 占 len - nameLen - 8 个字节, 表示请求/响应参数数据通过 protobuf 序列化之后的二进制
- checkSum:4 个字节, 表示整个消息的校验和, 作用是为了校验请求/响应报文的完整性
2.2 定义请求/响应参数
因为这里的参数需要进行网络传输以及序列化, 所以我们需要将参数定义在 pb 文件中,
- channel 创建与关闭参数
protobuf
syntax = "proto3";
package rabbitmq;
import "msg.proto";
//信道的打开与关闭
message openChannelRequest{
string rid = 1; // 唯一请求/响应 id, 用来将请求和响应对上
string cid = 2; // 标识唯一 channel
};
message closeChannelRequest{
string rid = 1;
string cid = 2;
};
- exchange 声明与删除参数
protobuf
//交换机的声明与删除
message declareExchangeRequest{
string rid = 1;
string cid = 2;
string exchange_name = 3;
ExchangeType exchange_type = 4;
bool durable = 5;
bool auto_delete = 6;
map<string, string> args = 7;
};
message deleteExchangeRequest{
string rid = 1;
string cid = 2;
string exchange_name = 3;
};
一个创建交换机的请求, 如下图示:

按照 len - nameLen - 8 的长度读取出 protobufData 就可以将读到的二进制数据反序列化成 ExchangeDeclareArguments 对象进行后续处理。后续的请求报文和这里都是类似的。
- 其他参数
protobuf
//队列的声明与删除
message declareQueueRequest{
string rid = 1;
string cid = 2;
string queue_name = 3;
bool exclusive = 4;
bool durable = 5;
bool auto_delete = 6;
map<string, string> args = 7;
};
message deleteQueueRequest{
string rid = 1;
string cid = 2;
string queue_name = 3;
};
//队列的绑定与解除绑定
message queueBindRequest{
string rid = 1;
string cid = 2;
string exchange_name = 3;
string queue_name = 4;
string binding_key = 5;
};
message queueUnBindRequest{
string rid = 1;
string cid = 2;
string exchange_name = 3;
string queue_name = 4;
};
//消息的发布
message basicPublishRequest {
string rid = 1;
string cid = 2;
string exchange_name = 3;
string body = 4;
BasicProperties properties = 5;
};
//消息的确认
message basicAckRequest {
string rid = 1;
string cid = 2;
string queue_name = 3;
string message_id = 4;
};
//队列的订阅
message basicConsumeRequest {
string rid = 1;
string cid = 2;
string consumer_tag =3;
string queue_name = 4;
bool auto_ack = 5;
};
//订阅的取消
message basicCancelRequest {
string rid = 1;
string cid = 2;
string consumer_tag = 3;
string queue_name = 4;
};
//消息的推送
message basicConsumeResponse {
string cid = 1;
string consumer_tag = 2;
string body = 3;
BasicProperties properties = 4;
};
//通用响应
message basicCommonResponse {
string rid = 1;
string cid = 2;
bool ok = 3;
}
对该.proto文件进行编译生成相关文件
3. 封装信道管理类
还记得我们在介绍muduo库时,写了几个示例来熟悉muduo库的使用

在当时我们就针对不同的请求定义了智能指针类型,在回调函数中作为参数来使用,这里我们也这样来做
cpp
#ifndef __M_CHANNEL_H__
#define __M_CHANNEL_H__
#include "muduo/net/TcpConnection.h"
#include "muduo/proto/codec.h"
#include "muduo/proto/dispatcher.h"
#include "../mqcommon/logger.hpp"
#include "../mqcommon/helper.hpp"
#include "../mqcommon/msg.pb.h"
#include "../mqcommon/proto.pb.h"
#include "../mqcommon/threadpool.hpp"
#include "consumer.hpp"
#include "host.hpp"
#include "route.hpp"
namespace rabbitmq
{
using ProtobufCodecPtr = std::shared_ptr<ProtobufCodec>;
using openChannelRequestPtr = std::shared_ptr<openChannelRequest>;
using closeChannelRequestPtr = std::shared_ptr<closeChannelRequest>;
using declareExchangeRequestPtr = std::shared_ptr<declareExchangeRequest>;
using deleteExchangeRequestPtr = std::shared_ptr<deleteExchangeRequest>;
using declareQueueRequestPtr = std::shared_ptr<declareQueueRequest>;
using deleteQueueRequestPtr = std::shared_ptr<deleteQueueRequest>;
using queueBindRequestPtr = std::shared_ptr<queueBindRequest>;
using queueUnBindRequestPtr = std::shared_ptr<queueUnBindRequest>;
using basicPublishRequestPtr = std::shared_ptr<basicPublishRequest>;
using basicAckRequestPtr = std::shared_ptr<basicAckRequest>;
using basicConsumeRequestPtr = std::shared_ptr<basicConsumeRequest>;
using basicCancelRequestPtr = std::shared_ptr<basicCancelRequest>;
class Channel
{
public:
using ptr = std::shared_ptr<Channel>;
Channel(const std::string &id,
const VirtualHost::ptr &host,
const ConsumerManager::ptr &cmp,
const ProtobufCodecPtr &codec,
const muduo::net::TcpConnectionPtr &conn,
const threadpool::ptr &pool)
:_cid(id),_conn(conn),_codec(codec),_cmp(cmp),_host(host),_pool(pool)
{
DLOG("new Channel: %p", this);
}
~Channel()
{
if (_consumer.get() != nullptr)
{
_cmp->remove(_consumer->_tag, _consumer->_qname);
}
DLOG("del Channel: %p", this);
}
//交换机的声明与删除
void declareExchange(const declareExchangeRequestPtr &req);
void deleteExchange(const deleteExchangeRequestPtr &req);
//队列的声明与删除
void declareQueue(const declareQueueRequestPtr &req);
void deleteQueue(const deleteQueueRequestPtr &req);
//队列的绑定与解除绑定
void queueBind(const queueBindRequestPtr &req);
void queueUnBind(const queueUnBindRequestPtr &req);
//消息的发布
void basicPublish(const basicPublishRequestPtr &req);
//消息的确认
void basicAck(const basicAckRequestPtr &req);
//订阅队列消息
void basicConsume(const basicConsumeRequestPtr &req);
//取消订阅
void basicCancel(const basicCancelRequestPtr &req);
private:
std::string _cid;
Consumer::ptr _consumer;
muduo::net::TcpConnectionPtr _conn;
ProtobufCodecPtr _codec;
ConsumerManager::ptr _cmp;
VirtualHost::ptr _host;
threadpool::ptr _pool;
};
}
#endif
信道管理类的框架已经完成,下面我们就来将这些接口一一完成
完整代码如下:
cpp
#ifndef __M_CHANNEL_H__
#define __M_CHANNEL_H__
#include "muduo/net/TcpConnection.h"
#include "muduo/proto/codec.h"
#include "muduo/proto/dispatcher.h"
#include "../mqcommon/logger.hpp"
#include "../mqcommon/helper.hpp"
#include "../mqcommon/msg.pb.h"
#include "../mqcommon/proto.pb.h"
#include "../mqcommon/threadpool.hpp"
#include "consumer.hpp"
#include "host.hpp"
#include "route.hpp"
namespace rabbitmq
{
using ProtobufCodecPtr = std::shared_ptr<ProtobufCodec>;
using openChannelRequestPtr = std::shared_ptr<openChannelRequest>;
using closeChannelRequestPtr = std::shared_ptr<closeChannelRequest>;
using declareExchangeRequestPtr = std::shared_ptr<declareExchangeRequest>;
using deleteExchangeRequestPtr = std::shared_ptr<deleteExchangeRequest>;
using declareQueueRequestPtr = std::shared_ptr<declareQueueRequest>;
using deleteQueueRequestPtr = std::shared_ptr<deleteQueueRequest>;
using queueBindRequestPtr = std::shared_ptr<queueBindRequest>;
using queueUnBindRequestPtr = std::shared_ptr<queueUnBindRequest>;
using basicPublishRequestPtr = std::shared_ptr<basicPublishRequest>;
using basicAckRequestPtr = std::shared_ptr<basicAckRequest>;
using basicConsumeRequestPtr = std::shared_ptr<basicConsumeRequest>;
using basicCancelRequestPtr = std::shared_ptr<basicCancelRequest>;
class Channel
{
public:
using ptr = std::shared_ptr<Channel>;
Channel(const std::string &id,
const VirtualHost::ptr &host,
const ConsumerManager::ptr &cmp,
const ProtobufCodecPtr &codec,
const muduo::net::TcpConnectionPtr &conn,
const threadpool::ptr &pool)
:_cid(id),_conn(conn),_codec(codec),_cmp(cmp),_host(host),_pool(pool)
{
DLOG("new Channel: %p", this);
}
~Channel()
{
if (_consumer.get() != nullptr)
{
_cmp->remove(_consumer->_tag, _consumer->_qname);
}
DLOG("del Channel: %p", this);
}
//交换机的声明与删除
void declareExchange(const declareExchangeRequestPtr &req)
{
bool ret = _host->declareExchange(req->exchange_name(),
req->exchange_type(), req->durable(),
req->auto_delete(), req->args());
basicResponse(ret, req->rid(), req->cid());
}
void deleteExchange(const deleteExchangeRequestPtr &req)
{
_host->deleteExchange(req->exchange_name());
basicResponse(true, req->rid(), req->cid());
}
//队列的声明与删除
void declareQueue(const declareQueueRequestPtr &req)
{
bool ret = _host->declareQueue(req->queue_name(),
req->durable(), req->exclusive(),
req->auto_delete(), req->args());
if (ret == false)
{
basicResponse(false, req->rid(), req->cid());
return;
}
_cmp->initQueueConsumer(req->queue_name());//初始化队列的消费者管理句柄
basicResponse(true, req->rid(), req->cid());
}
void deleteQueue(const deleteQueueRequestPtr &req)
{
_cmp->destroyQueueConsumer(req->queue_name());
_host->deleteQueue(req->queue_name());
basicResponse(true, req->rid(), req->cid());
}
//队列的绑定与解除绑定
void queueBind(const queueBindRequestPtr &req)
{
bool ret = _host->bind(req->exchange_name(),
req->queue_name(), req->binding_key());
basicResponse(ret, req->rid(), req->cid());
}
void queueUnBind(const queueUnBindRequestPtr &req)
{
_host->unBind(req->exchange_name(), req->queue_name());
basicResponse(true, req->rid(), req->cid());
}
//消息的发布
void basicPublish(const basicPublishRequestPtr &req)
{
//1. 获取交换机对象
auto ep = _host->selectExchange(req->exchange_name());
if(ep.get() == nullptr)
{
basicResponse(false, req->rid(), req->cid());
return;
}
//2. 进行交换路由,判断消息可以发布到交换机绑定的哪个队列中
MsgQueueBindingMap mqbmap = _host->exchangeBindings(req->exchange_name());
BasicProperties* properties = nullptr;
std::string routing_key;
if(req->has_properties())
{
properties = req->mutable_properties();
routing_key = properties->routing_key();
}
for(auto& binding : mqbmap)
{
if(Router::route(ep->_type, routing_key, binding.second->_binding_key))
{
//3. 将消息添加到队列中(添加消息的管理)
_host->basicPublish(binding.first, properties, req->body());
//4. 向线程池中添加一个消息消费任务(向指定队列的订阅者去推送消息--线程池完成)
auto task = std::bind(&Channel::consume, this, binding.first);
_pool->push(task);
}
}
basicResponse(true, req->rid(), req->cid());
}
//消息的确认
void basicAck(const basicAckRequestPtr &req)
{
_host->basicAck(req->queue_name(), req->message_id());
basicResponse(true, req->rid(), req->cid());
}
//订阅队列消息
void basicConsume(const basicConsumeRequestPtr &req)
{
//1. 判断队列是否存在
bool ret = _host->existsQueue(req->queue_name());
if(!ret)
{
basicResponse(false, req->rid(), req->cid());
return;
}
//2. 创建队列的消费者
auto cb = std::bind(&Channel::callback, this, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3);
//创建了消费者之后,当前的channel角色就是个消费者
_consumer = _cmp->create(req->consumer_tag(), req->queue_name(), req->auto_ack(), cb);
basicResponse(true, req->rid(), req->cid());
}
//取消订阅
void basicCancel(const basicCancelRequestPtr &req)
{
// 取消订阅意味着不再需要消费者帮客户端从队列中消费消息
// 直接将消费者删除
_cmp->remove(req->consumer_tag(), req->queue_name());
basicResponse(true, req->rid(), req->cid());
}
private:
void callback(const std::string tag, const BasicProperties *bp, const std::string &body)
{
//针对参数组织出推送消息请求,将消息推送给channel对应的客户端
basicConsumeResponse resp;
resp.set_cid(_cid);
resp.set_consumer_tag(tag);
resp.set_body(body);
if(bp)
{
resp.mutable_properties()->set_id(bp->id());
resp.mutable_properties()->set_delivery_mode(bp->delivery_mode());
resp.mutable_properties()->set_routing_key(bp->routing_key());
}
_codec->send(_conn, resp);
}
void consume(const std::string &qname)
{
//指定队列消费消息
//1. 从队列中取出一条消息
MessagePtr msg = _host->basicConsume(qname);
if(msg.get() == nullptr)
{
DLOG("执行消费任务失败,%s 队列没有消息!", qname.c_str());
return;
}
//2. 从队列订阅者中取出一个订阅者
Consumer::ptr consumer = _cmp->choose(qname);
if(consumer.get() == nullptr)
{
DLOG("执行消费任务失败,%s 队列没有消费者!", qname.c_str());
return;
}
//3. 调用订阅者对应的消息处理函数,实现消息的推送
consumer->_callback(consumer->_tag, msg->mutable_payload()->mutable_properties(), msg->payload().body());
//4. 判断如果订阅者是自动确认---不需要等待确认,直接删除消息,否则需要外部收到消息确认后再删除
if(consumer->_auto_ack)
{
_host->basicAck(qname, msg->payload().properties().id());
}
}
void basicResponse(bool ok, const std::string &rid, const std::string &cid)
{
basicCommonResponse resp;
resp.set_rid(rid);
resp.set_cid(cid);
resp.set_ok(ok);
_codec->send(_conn, resp);
}
private:
std::string _cid;
Consumer::ptr _consumer;
muduo::net::TcpConnectionPtr _conn;
ProtobufCodecPtr _codec;
ConsumerManager::ptr _cmp;
VirtualHost::ptr _host;
threadpool::ptr _pool;
};
}
#endif
这个Channel类是RabbitMQ服务器的核心业务处理器,它:
- 作为AMQP协议的信道抽象
- 处理客户端与服务器之间的各种AMQP操作
- 管理消息的发布、消费和确认流程
核心成员变量解析
- VirtualHost (_host)
- 管理交换机、队列的存储和查找
- 实现消息的路由和持久化
- ConsumerManager (_cmp)
- 管理消费者的创建、删除和选择
- 实现消费者负载均衡
- ProtobufCodec (_codec)
- 协议的编码和解码
- 负责与客户端的网络通信
- threadpool (_pool)
- 提供异步任务处理能力
- 提高系统的并发性能
- TcpConnection (_conn)
- 与客户端的连接通道
- 实现网络层通信
消息流完整路径
发布流程:
plain
客户端请求 → ProtobufCodec解码 → Channel::basicPublish
→ VirtualHost路由 → 消息入队 → 线程池消费任务
→ Channel::consume → 选择消费者 → 回调推送
消费流程:
plain
客户端订阅 → basicConsume → 创建消费者
→ 消息到达 → consume任务执行 → 回调函数
→ 编码响应 → 网络发送 → 客户端接收
4. 封装信道管理对外的类
cpp
class ChannelManager
{
public:
using ptr = std::shared_ptr<ChannelManager>;
ChannelManager(){}
bool openChannel(const std::string &id,
const VirtualHost::ptr &host,
const ConsumerManager::ptr &cmp,
const ProtobufCodecPtr &codec,
const muduo::net::TcpConnectionPtr &conn,
const threadpool::ptr &pool)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _channels.find(id);
if (it != _channels.end())
{
DLOG("信道:%s 已经存在!", id.c_str());
return false;
}
auto channel = std::make_shared<Channel>(id, host, cmp, codec, conn, pool);
_channels.insert(std::make_pair(id, channel));
return true;
}
void closeChannel(const std::string &id)
{
std::unique_lock<std::mutex> lock(_mutex);
_channels.erase(id);
}
Channel::ptr getChannel(const std::string &id)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _channels.find(id);
if (it == _channels.end())
{
return Channel::ptr();
}
return it->second;
}
private:
std::mutex _mutex;
std::unordered_map<std::string, Channel::ptr> _channels;
};