项目第九弹:信道管理模块

一、为何要有信道管理模块

是为了进行资源隔离:
通过利用线程独占栈空间来限制句柄资源的可见性,从而实现多线程之间的资源隔离,将同一个连接这个临界资源细分为信道这个非临界资源,从而在保证线程安全的情况下提高程序运行的并发效率

这样做还有其他的好处:

  1. 减少了TCP连接建立和销毁的开销,信道的创建与销毁更加轻量
    【就像是线程创建,销毁比进程的创建,销毁更加轻量】
  2. 迁就多线程的并发编程,提高并发编程的运行效率

二、信道管理模块的设计

1.信道模块的设计

上一弹我们完成了自定义应用层网络协议,下面就正式进入网络模块的设计

信道是具体提供对应服务的模块,而我们的服务:

  1. 声明/删除虚拟机
  2. 声明/删除交换机
  3. 声明/删除队列
  4. 绑定/解绑队列
  5. 订阅/取消订阅队列
  6. 发布/确认消息

因此,成员就需要有:

  1. 虚拟机管理模块句柄
  2. 消费者管理模块句柄

我们也说了,我们的项目是采用请求—响应模式
所以我们针对这12个请求都要进行处理,并且在处理之后要根据结果构建响应发送给对应的客户端

因此成员还需要有:

  1. TcpConnectionPtr
  2. ProtobufCodecPtr(这里保存句柄是因为我们会有多个信道,所以共用同一个ProtobufCodec即可,用shared_ptr共享一下提高效率。毕竟:勿以小优化而不为,勿以浪费资源而为之)

我们之前写的异步工作线程池也要用到,因为有些操作我们不想耽误当前的信道服务执行流
(老师傅将耗时,解耦任务交给学徒去干)
【比如:推送消息、消费消息这两个任务就是耗时,解耦的】

信道自身成员:

  1. 信道ID(信道自身的唯一标识,信道的句柄)
  2. 当前信道关联的消费者句柄(出于简化RabbitMQ,我们规定一个信道最多关联一个消费者)

信道模块要不要互斥锁呢?
不需要,因为下面三个句柄资源的内部都有互斥锁
ConsumerManager::ptr _consumer_manager_ptr;
VirtualHostManager::ptr _vhost_manager_ptr;
threadpool::ptr _pool_ptr;

1.为何要规定一个信道最多要关联一个消费者?

我们要从消息的发布与消费还有信道的问题来进行考虑:
在这里插入图片描述

2.信道管理模块的设计

增、删、查
接口:

  1. 声明/删除信道
  2. 获取指定信道

成员:
unordered_map<信道ID,信道::ptr> 哈希表
互斥锁

我们就提供这两个接口了,遵循KISS和YAGNI,至于其他的函数,都需要通过获取信道之后用信道句柄进行访问

Talk is cheap,Show me the code

三、信道管理模块的实现

1.信道模块的实现

1.前置工作

为了进行优化,提高效率,我们的参数类型都是Request的智能指针
所以需要定义12个Request(打开和关闭信道由连接模块负责)和1个ProtobufCodecPtr

using ProtobufCodecPtr = std::shared_ptr<ProtobufCodec>;

using DeclareVirtualHostRequestPtr = std::shared_ptr<DeclareVirtualHostRequest>;
using EraseVirtualHostRequestPtr = std::shared_ptr<EraseVirtualHostRequest>;

using DeclareExchangeRequestPtr = std::shared_ptr<DeclareExchangeRequest>;
using EraseExchangeRequestPtr = std::shared_ptr<EraseExchangeRequest>;

using DeclareMsgQueueRequestPtr = std::shared_ptr<DeclareMsgQueueRequest>;
using EraseMsgQueueRequestPtr = std::shared_ptr<EraseMsgQueueRequest>;

using BindRequestPtr = std::shared_ptr<BindRequest>;
using UnbindRequestPtr = std::shared_ptr<UnbindRequest>;

using BasicConsumeRequestPtr = std::shared_ptr<BasicConsumeRequest>;
using BasicCancelRequestPtr = std::shared_ptr<BasicCancelRequest>;

using BasicPublishRequestPtr = std::shared_ptr<BasicPublishRequest>;
using BasicAckRequestPtr = std::shared_ptr<BasicAckRequest>;

2.大框架

class Channel
{
public:
    using ptr = std::shared_ptr<Channel>;
    // 处理Channel自身相关联的消费者句柄_consumer无需显式初始化以外,其他都要初始化
    //_consumer默认初始化为空的shared_ptr
    Channel(const std::string &channel_id, const muduo::net::TcpConnectionPtr &conn, const ProtobufCodecPtr &codec,
            const ConsumerManager::ptr consumer_manager_ptr, const VirtualHostManager::ptr vhost_manager_ptr, threadpool::ptr pool_ptr);

    ~Channel();

    void declareVirtualHost(const DeclareVirtualHostRequestPtr &req);

    void eraseVirtualHost(const EraseVirtualHostRequestPtr &req);

    void declareExchange(const DeclareExchangeRequestPtr &req);
    
    void eraseExchange(const EraseExchangeRequestPtr &req);

    void declareMsgQueue(const DeclareMsgQueueRequestPtr &req);

    void eraseMsgQueue(const EraseMsgQueueRequestPtr &req);

    void bind(const BindRequestPtr &req);

    void unBind(const UnbindRequestPtr &req);

    void basicConsume(const BasicConsumeRequestPtr &req);
    

    // 正常情况下肯定是当前信道所关联的消费者才会调用取消订阅这个函数
    void basicCancel(const BasicCancelRequestPtr &req);

    void basicAck(const BasicAckRequestPtr &req);

    void basicPublish(const BasicPublishRequestPtr &req);

private:
    //  推送消息(取出消息,取出消费者,调用对应消费者的消费处理回调函数)
    void publishCallback(const std::string &vname, const std::string &qname);
	
	// 消费消息(构造BasicConsumeResponse,发送给对应的消费者)
    void consumeCallback(const std::string &consumer_tag, const BasicProperities *bp, const std::string &body);

	// 基础响应(构造BasicCommonResponse,发送给对应的消费者)
    void basicResponse(const std::string &req_id, const std::string &channel_id, bool ret);

    // channel自身成员
    std::string _channel_id;
    Consumer::ptr _consumer;

    // muduo    protobuf
    muduo::net::TcpConnectionPtr _conn; // muduo库底层网络通信连接
    ProtobufCodecPtr _codec;

    // 资源句柄
    ConsumerManager::ptr _consumer_manager_ptr;
    VirtualHostManager::ptr _vhost_manager_ptr;
    threadpool::ptr _pool_ptr;
};

下面我们一一实现:
其实总体步骤就一个:

  1. 进行操作
  2. 返回基础响应

3.构造与析构

构造的时候,就直接构造就行了,其他的啥也不用做(因为对应的事情早在那些对象刚创建的时候就做好了)

析构的时候,说明该信道已经关闭,那么该信道关联的消费者也需要移除了,因为我们规定,一个信道关联一个消费者,一个消费者也关联一个信道,它们是一个命运共同体

// 处理Channel自身相关联的消费者句柄_consumer无需显式初始化以外,其他都要初始化
//_consumer默认初始化为空的shared_ptr
Channel(const std::string &channel_id, const muduo::net::TcpConnectionPtr &conn, const ProtobufCodecPtr &codec,
        const ConsumerManager::ptr consumer_manager_ptr, const VirtualHostManager::ptr vhost_manager_ptr, threadpool::ptr pool_ptr)
    : _channel_id(channel_id), _conn(conn), _codec(codec), _consumer_manager_ptr(consumer_manager_ptr), _vhost_manager_ptr(vhost_manager_ptr), _pool_ptr(pool_ptr) {}

~Channel()
{
    // 信道被删除的时候,要删除该信道关联的消费者
    // 信道被删除的时候,要删除该信道关联的消费者
    if (_consumer.get() != nullptr)
    {
        _consumer_manager_ptr->removeConsumer(_consumer->_vhost_name, _consumer->_qname, _consumer->_consumer_tag);
        _consumer.reset();
    }
}

4.声明/删除虚拟机,basicResponse

void declareVirtualHost(const DeclareVirtualHostRequestPtr &req)
{
    // 1. 调用函数
    bool ret = _vhost_manager_ptr->declareVirtualHost(req->vhost_name(), req->dbfile(), req->basedir());
    // 2. 构建响应
    basicResponse(req->req_id(), req->channel_id(), ret);
}

void eraseVirtualHost(const EraseVirtualHostRequestPtr &req)
{
    // 1. 调用函数
    bool ret = _vhost_manager_ptr->eraseVirtualHost(req->vhost_name());
    // 2. 构建响应
    basicResponse(req->req_id(), req->channel_id(), ret);
}

void basicResponse(const std::string &req_id, const std::string &channel_id, bool ret)
{
    // 构建基础响应
    BasicCommonResponse resp;
    resp.set_req_id(req_id);
    resp.set_channel_id(channel_id);
    resp.set_ok(ret);
    // 直接传给codec
    // void send(const muduo::net::TcpConnectionPtr & conn, const google::protobuf::Message & message)
    _codec->send(_conn, resp);
}

5.声明/删除交换机

因为交换机的声明与删除只牵扯虚拟机内部的行为,而那些行为早已在虚拟机模块内部屏蔽掉了。
所以我们直接复用即可

void declareExchange(const DeclareExchangeRequestPtr &req)
{
    bool ret = _vhost_manager_ptr->declareExchange(req->vhost_name(), req->exchange_name(), req->type(), req->durable(), req->auto_delete(), req->args());
    basicResponse(req->req_id(), req->channel_id(), ret);
}

void eraseExchange(const EraseExchangeRequestPtr &req)
{
    bool ret = _vhost_manager_ptr->eraseExchange(req->vhost_name(), req->exchange_name());
    basicResponse(req->req_id(), req->channel_id(), ret);
}

6.声明/删除队列

队列的声明与删除不仅仅跟虚拟机模块有关,而且跟消费者管理模块有关,因此需要我们在信道这一层来进行额外操作:

即:初始化和销毁队列的消费者管理句柄

void declareMsgQueue(const DeclareMsgQueueRequestPtr &req)
{
    // 注意: 初始化队列的消费者管理句柄!!!!
    _consumer_manager_ptr->initQueueConsumerManager(req->vhost_name(), req->queue_name());
    bool ret = _vhost_manager_ptr->declareMsgQueue(req->vhost_name(), req->queue_name(), req->durable(), req->exclusive(),
                                                   req->auto_delete(), req->args());
    basicResponse(req->req_id(), req->channel_id(), ret);
}

void eraseMsgQueue(const EraseMsgQueueRequestPtr &req)
{
    // 注意: 销毁队列的消费者管理句柄
    _consumer_manager_ptr->destroyQueueConsumerManager(req->vhost_name(), req->queue_name());
    bool ret = _vhost_manager_ptr->eraseMsgQueue(req->vhost_name(), req->queue_name());
    basicResponse(req->req_id(), req->channel_id(), ret);
}

7.绑定/解绑队列

绑定和解绑也是只跟虚拟机模块有关,内部细节已经被屏蔽,我们直接复用即可

void bind(const BindRequestPtr &req)
{
    bool ret = _vhost_manager_ptr->bind(req->vhost_name(), req->exchange_name(), req->queue_name(), req->binding_key());
    basicResponse(req->req_id(), req->channel_id(), ret);
}

void unBind(const UnbindRequestPtr &req)
{
    bool ret = _vhost_manager_ptr->unBind(req->vhost_name(), req->exchange_name(), req->queue_name());
    basicResponse(req->req_id(), req->channel_id(), ret);
}

8.队列的订阅与取消订阅

1.取消订阅

正常情况下肯定是当前信道所关联的消费者才会调用取消订阅这个函数
所以别忘了_consumer.reset();

// 正常情况下肯定是当前信道所关联的消费者才会调用取消订阅这个函数
void basicCancel(const BasicCancelRequestPtr &req)
{
    bool ret = _consumer_manager_ptr->removeConsumer(req->vhost_name(), req->queue_name(), req->consumer_tag());
    _consumer.reset();
    basicResponse(req->req_id(), req->channel_id(), ret);
}
2.订阅

在这里插入图片描述
按照我们之前说的,这里需要实现并绑定一个消费处理回调函数

服务器的消费处理回调函数的任务就是:
构建基础消费响应,发送给该信道关联的消费者

void consumeCallback(const std::string &consumer_tag, const BasicProperities *bp, const std::string &body)
{
    // 构建基础消费响应,发给该信道关联的消费者
    BasicConsumeResponse resp;
    resp.set_consumer_tag(consumer_tag);
    if (bp != nullptr)
    {
        resp.mutable_properities()->set_msg_id(bp->msg_id());
        resp.mutable_properities()->set_mode(bp->mode());
        resp.mutable_properities()->set_routing_key(bp->routing_key());
    }
    resp.set_body(body);
    _codec->send(_conn, resp);
}
void basicConsume(const BasicConsumeRequestPtr &req)
{
    // 1. 看看当前信道有无关联什么消费者
    if (_consumer.get() != nullptr)
    {
    	default_error("当前信道的队列订阅失败,因为当前信道已经关联了消费者,信道ID:%s ,消费者tag:%s",_channel_id.c_str(),_consumer->_consumer_tag.c_str());
        basicResponse(req->req_id(), req->channel_id(), false);
        return;
    }
    // 2. 判断队列是否存在
    if(_vhost_manager_ptr->existMsgQueue(req->vhost_name(),req->queue_name()))
    {
    	default_error("当前信道的队列订阅失败,因为队列不存在,队列名:%s",req->queue_name().c_str());
        basicResponse(req->req_id(), req->channel_id(), false);
        return;
    }

   // 3. 创建消费者
    _consumer = _consumer_manager_ptr->createConsumer(req->vhost_name(), req->queue_name(), req->consumer_tag(),
                                                      ::std::bind(&Channel::consumeCallback, this, ::std::placeholders::_1, ::std::placeholders::_2, ::std::placeholders::_3), req->auto_ack());
    // 4. 返回响应
    basicResponse(req->req_id(), req->channel_id(), true);
}

这里设置回调函数就是将该消费者的消费回调处理函数与该消费者关联的信道相绑定,通过this指针绑定

9.消息的发布与确认

1.消息的确认

因为消息的确认只跟虚拟机模块有关,内部细节已被屏蔽,所以在信道视角来看直接复用即可

void basicAck(const BasicAckRequestPtr &req)
{
    bool ret = _vhost_manager_ptr->basicAck(req->vhost_name(), req->queue_name(), req->msg_id());
    basicResponse(req->req_id(), req->channel_id(), ret);
}
2.消息的发布

消息的发布涉及到:

  1. 虚拟机模块
  2. 路由匹配模块
  3. 信道模块当中实现的publishCallback函数
  4. 异步工作线程池

涉及到这4个模块,因此我们并没有在虚拟机模块来完成这个操作,二是放到了信道模块来完成这个操作

它的步骤:

  1. 拿到该交换机的类型
  2. 拿到该交换机的所有绑定信息
  3. 遍历对应的绑定信息,进行消息的路由匹配
  4. 若匹配,则继续第5步,沿着往下走
  5. 把消息放到对应匹配成功的队列
  6. 往异步线程池当中抛入一个消息消费任务,交给线程池中的线程去做,解放Channel线程去做更重要的任务
void basicPublish(const BasicPublishRequestPtr &req)
{
    // 1. 先找到该交换机的交换机类型
    Exchange::ptr ep = _vhost_manager_ptr->getExchange(req->vhost_name(), req->exchange_name());
    if (ep.get() == nullptr)
    {
    	default_error("发布消息失败,因为交换机不存在\n,交换机名称:%s",req->exchange_name().c_str());
        basicResponse(req->req_id(), req->channel_id(), false);
        return;
    }
    // 2. 先找到消息发布的交换机  绑定的所有队列
    MsgQueueBindingMap qmap = _vhost_manager_ptr->getAllBindingsByExchange(req->vhost_name(), req->exchange_name());
    // 3. 遍历所有队列,进行路由匹配与消息投递
    for (auto &kv : qmap)
    {
        Binding::ptr bp = kv.second;
        BasicProperities *properities = nullptr;
        std::string routing_key;

        if (req->has_properities())
        {
            properities = req->mutable_properities();
            routing_key = properities->routing_key();
        }
        if (Router::route(routing_key, bp->binding_key, ep->type))
        {
            // 4. 把消息投递到指定队列
            _vhost_manager_ptr->basicPublish(req->vhost_name(), bp->queue_name, properities, req->body());
            // 5. 向线程池添加一个消息消费任务,消费任务交给线程池中的线程去做,解放Channel线程去做更重要的任务
            _pool_ptr->put(std::bind(&Channel::publishCallback, this, req->vhost_name(), bp->queue_name));
        }
    }
    // 返回响应即可
    basicResponse(req->req_id(), req->channel_id(), true);
}
3.消息推送任务的回调
  1. 取出消息
  2. 拿到消费者
  3. 调用对应消费者的消费处理回调函数
  4. 如果消费者有自动确认标志,则进行自动确认
//  推送消息(取出消息,取出消费者,调用对应消费者的消费处理回调函数)
void publishCallback(const std::string &vname, const std::string &qname)
{
    // 1.取出消息
    MessagePtr mp = _vhost_manager_ptr->basicConsume(vname, qname);
    if (mp.get() == nullptr)
    {
    	default_info("消息的消费失败, 因为消息队列为空,没有消息: %s",qname.c_str());
        return;
    }
    // 2.取出消费者
    Consumer::ptr cp = _consumer_manager_ptr->selectConsumer(vname, qname);
    if (cp.get() == nullptr)
    {
    	default_info("该队列中暂无消费者,无法消费消息 %s",qname.c_str());
        return;
    }
    // 3.调用消费者的消费处理回调函数
    cp->_callback(cp->_consumer_tag, mp->mutable_valid()->mutable_properities(), mp->valid().body());
    // 4.如果消费者有自动确认标志,则进行自动确认
    if (cp->_auto_ack == true)
    {
        _vhost_manager_ptr->basicAck(vname, qname, mp->valid().properities().msg_id());
    }
}

10.完整代码

using ProtobufCodecPtr = std::shared_ptr<ProtobufCodec>;
using namespace ns_helper;
using namespace ns_proto;

using DeclareVirtualHostRequestPtr = std::shared_ptr<DeclareVirtualHostRequest>;
using EraseVirtualHostRequestPtr = std::shared_ptr<EraseVirtualHostRequest>;

using DeclareExchangeRequestPtr = std::shared_ptr<DeclareExchangeRequest>;
using EraseExchangeRequestPtr = std::shared_ptr<EraseExchangeRequest>;

using DeclareMsgQueueRequestPtr = std::shared_ptr<DeclareMsgQueueRequest>;
using EraseMsgQueueRequestPtr = std::shared_ptr<EraseMsgQueueRequest>;

using BindRequestPtr = std::shared_ptr<BindRequest>;
using UnbindRequestPtr = std::shared_ptr<UnbindRequest>;

using BasicConsumeRequestPtr = std::shared_ptr<BasicConsumeRequest>;
using BasicCancelRequestPtr = std::shared_ptr<BasicCancelRequest>;

using BasicPublishRequestPtr = std::shared_ptr<BasicPublishRequest>;
using BasicAckRequestPtr = std::shared_ptr<BasicAckRequest>;

class Channel
{
public:
    using ptr = std::shared_ptr<Channel>;
    // 处理Channel自身相关联的消费者句柄_consumer无需显式初始化以外,其他都要初始化
    //_consumer默认初始化为空的shared_ptr
    Channel(const std::string &channel_id, const muduo::net::TcpConnectionPtr &conn, const ProtobufCodecPtr &codec,
            const ConsumerManager::ptr consumer_manager_ptr, const VirtualHostManager::ptr vhost_manager_ptr, threadpool::ptr pool_ptr)
        : _channel_id(channel_id), _conn(conn), _codec(codec), _consumer_manager_ptr(consumer_manager_ptr), _vhost_manager_ptr(vhost_manager_ptr), _pool_ptr(pool_ptr) {}

    ~Channel()
    {
        // 信道被删除的时候,要删除该信道关联的消费者
        // 信道被删除的时候,要删除该信道关联的消费者
        if (_consumer.get() != nullptr)
        {
            _consumer_manager_ptr->removeConsumer(_consumer->_vhost_name, _consumer->_qname, _consumer->_consumer_tag);
            _consumer.reset();
        }
    }

    void declareVirtualHost(const DeclareVirtualHostRequestPtr &req)
    {
        // 1. 调用函数
        bool ret = _vhost_manager_ptr->declareVirtualHost(req->vhost_name(), req->dbfile(), req->basedir());
        // 2. 构建响应
        basicResponse(req->req_id(), req->channel_id(), ret);
    }

    void eraseVirtualHost(const EraseVirtualHostRequestPtr &req)
    {
        // 1. 调用函数
        bool ret = _vhost_manager_ptr->eraseVirtualHost(req->vhost_name());
        // 2. 构建响应
        basicResponse(req->req_id(), req->channel_id(), ret);
    }

    void declareExchange(const DeclareExchangeRequestPtr &req)
    {
        bool ret = _vhost_manager_ptr->declareExchange(req->vhost_name(), req->exchange_name(), req->type(), req->durable(), req->auto_delete(), req->args());
        basicResponse(req->req_id(), req->channel_id(), ret);
    }

    void eraseExchange(const EraseExchangeRequestPtr &req)
    {
        bool ret = _vhost_manager_ptr->eraseExchange(req->vhost_name(), req->exchange_name());
        basicResponse(req->req_id(), req->channel_id(), ret);
    }

    void declareMsgQueue(const DeclareMsgQueueRequestPtr &req)
	{
	    // 注意: 初始化队列的消费者管理句柄!!!!
	    _consumer_manager_ptr->initQueueConsumerManager(req->vhost_name(), req->queue_name());
	    bool ret = _vhost_manager_ptr->declareMsgQueue(req->vhost_name(), req->queue_name(), req->durable(), req->exclusive(),
	                                                   req->auto_delete(), req->args());
	    basicResponse(req->req_id(), req->channel_id(), ret);
	}
	
	void eraseMsgQueue(const EraseMsgQueueRequestPtr &req)
	{
	    // 注意: 销毁队列的消费者管理句柄
	    _consumer_manager_ptr->destroyQueueConsumerManager(req->vhost_name(), req->queue_name());
	    bool ret = _vhost_manager_ptr->eraseMsgQueue(req->vhost_name(), req->queue_name());
	    basicResponse(req->req_id(), req->channel_id(), ret);
	}

    void bind(const BindRequestPtr &req)
    {
        bool ret = _vhost_manager_ptr->bind(req->vhost_name(), req->exchange_name(), req->queue_name(), req->binding_key());
        basicResponse(req->req_id(), req->channel_id(), ret);
    }

    void unBind(const UnbindRequestPtr &req)
    {
        bool ret = _vhost_manager_ptr->unBind(req->vhost_name(), req->exchange_name(), req->queue_name());
        basicResponse(req->req_id(), req->channel_id(), ret);
    }

    void basicConsume(const BasicConsumeRequestPtr &req)
    {
        // 1. 看看当前信道有无关联什么消费者
        if (_consumer.get() != nullptr)
        {
        	default_error("当前信道的队列订阅失败,因为当前信道已经关联了消费者,信道ID:%s ,消费者tag:%s",_channel_id.c_str(),_consumer->_consumer_tag.c_str());
            basicResponse(req->req_id(), req->channel_id(), false);
            return;
        }
        // 2. 判断队列是否存在
        if(_vhost_manager_ptr->existMsgQueue(req->vhost_name(),req->queue_name()))
        {
        	default_error("当前信道的队列订阅失败,因为队列不存在,队列名:%s",req->queue_name().c_str());
            basicResponse(req->req_id(), req->channel_id(), false);
            return;
        }

        // 3. 创建消费者
        _consumer = _consumer_manager_ptr->createConsumer(req->vhost_name(),req->queue_name(), req->consumer_tag(),
                                                          std::bind(&Channel::consumeCallback, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), req->auto_ack());
        // 4. 返回响应
        basicResponse(req->req_id(), req->channel_id(), true);
    }

    // 正常情况下肯定是当前信道所关联的消费者才会调用取消订阅这个函数
    void basicCancel(const BasicCancelRequestPtr &req)
    {
        bool ret = _consumer_manager_ptr->removeConsumer(req->vhost_name(), req->queue_name(), req->consumer_tag());
        basicResponse(req->req_id(), req->channel_id(), ret);
    }

    void basicAck(const BasicAckRequestPtr &req)
    {
        bool ret = _vhost_manager_ptr->basicAck(req->vhost_name(), req->queue_name(), req->msg_id());
        basicResponse(req->req_id(), req->channel_id(), ret);
    }

    void basicPublish(const BasicPublishRequestPtr &req)
    {
        // 1. 先找到该交换机的交换机类型
        Exchange::ptr ep = _vhost_manager_ptr->getExchange(req->vhost_name(), req->exchange_name());
        if (ep.get() == nullptr)
        {
        	default_error("发布消息失败,因为交换机不存在\n,交换机名称:%s",req->exchange_name().c_str());
            basicResponse(req->req_id(), req->channel_id(), false);
            return;
        }
        // 2. 先找到消息发布的交换机  绑定的所有队列
        MsgQueueBindingMap qmap = _vhost_manager_ptr->getAllBindingsByExchange(req->vhost_name(), req->exchange_name());
        // 3. 遍历所有队列,进行路由匹配与消息投递
        for (auto &kv : qmap)
        {
            Binding::ptr bp = kv.second;
            BasicProperities *properities = nullptr;
            std::string routing_key;

            if (req->has_properities())
            {
                properities = req->mutable_properities();
                routing_key = properities->routing_key();
            }
            if (Router::route(routing_key, bp->binding_key, ep->type))
            {
                // 把消息投递到指定队列
                _vhost_manager_ptr->basicPublish(req->vhost_name(), bp->queue_name, properities, req->body());
                // 5. 向线程池添加一个消息消费任务,消费任务交给线程池中的线程去做,解放Channel线程去做更重要的任务
                _pool_ptr->put(std::bind(&Channel::publishCallback, this, req->vhost_name(), bp->queue_name));
            }
        }
        // 返回响应即可
        basicResponse(req->req_id(), req->channel_id(), true);
    }

private:
    //  推送消息(取出消息,取出消费者,调用对应消费者的消费处理回调函数)
    void publishCallback(const std::string &vname, const std::string &qname)
    {
        // 1.取出消息
        MessagePtr mp = _vhost_manager_ptr->basicConsume(vname, qname);
        if (mp.get() == nullptr)
        {
        	default_info("消息的消费失败, 因为消息队列为空,没有消息: %s",qname.c_str());
            return;
        }
        // 2.取出消费者
        Consumer::ptr cp = _consumer_manager_ptr->selectConsumer(vname, qname);
        if (cp.get() == nullptr)
        {
        	default_info("该队列中暂无消费者,无法消费消息 %s",qname.c_str());
            return;
        }
        // 3.调用消费者的消费处理回调函数
        cp->_callback(cp->_consumer_tag, mp->mutable_valid()->mutable_properities(), mp->valid().body());
        // 4.如果消费者有自动确认标志,则进行自动确认
        if (cp->_auto_ack == true)
        {
            _vhost_manager_ptr->basicAck(vname, qname, mp->valid().properities().msg_id());
        }
    }

    void consumeCallback(const std::string &consumer_tag, const BasicProperities *bp, const std::string &body)
    {
        // 构建基础消费响应,发给该信道关联的消费者
        BasicConsumeResponse resp;
        resp.set_consumer_tag(consumer_tag);
        if (bp != nullptr)
        {
            resp.mutable_properities()->set_msg_id(bp->msg_id());
            resp.mutable_properities()->set_mode(bp->mode());
            resp.mutable_properities()->set_routing_key(bp->routing_key());
        }
        resp.set_body(body);
        _codec->send(_conn, resp);
    }

    void basicResponse(const std::string &req_id, const std::string &channel_id, bool ret)
    {
        // 构建基础响应
        BasicCommonResponse resp;
        resp.set_req_id(req_id);
        resp.set_channel_id(channel_id);
        resp.set_ok(ret);
        // 直接传给codec
        // void send(const muduo::net::TcpConnectionPtr & conn, const google::protobuf::Message & message)
        _codec->send(_conn, resp);
    }

    // channel自身成员
    std::string _channel_id;
    Consumer::ptr _consumer;

    // muduo    protobuf
    muduo::net::TcpConnectionPtr _conn; // muduo库底层网络通信连接
    ProtobufCodecPtr _codec;

    // 资源句柄
    ConsumerManager::ptr _consumer_manager_ptr;
    VirtualHostManager::ptr _vhost_manager_ptr;
    threadpool::ptr _pool_ptr;
};

2.信道管理模块实现

1.要不要给basicResponse函数?

不给,因为我们的信道上面还有连接,我们在连接层给声明和删除信道的响应
换言之,这一层只负责干活,不负责交差
【仅负责执行具体任务,不负责任务成果的交付】
不负责处理具体Request

2.完整代码

class ChannelManager
{
public:
	using ptr=std::shared_ptr<ChannelManager>;

    void OpenChannel(const std::string &channel_id, const muduo::net::TcpConnectionPtr &conn, const ProtobufCodecPtr &codec,
                     const ConsumerManager::ptr &consumer_manager_ptr, const VirtualHostManager::ptr &vhost_manager_ptr, const threadpool::ptr &pool_ptr)
    {
        std::unique_lock<std::mutex> ulock(_mutex);
        if (_channel_map.count(channel_id))
            return;

        Channel::ptr cp = std::make_shared<Channel>(channel_id, conn, codec, consumer_manager_ptr, vhost_manager_ptr, pool_ptr);
        _channel_map.insert(std::make_pair(channel_id, cp));
    }

    void CloseChannel(const std::string &channel_id)
    {
        std::unique_lock<std::mutex> ulock(_mutex);
        _channel_map.erase(channel_id);
    }

    Channel::ptr getChannel(const std::string &channel_id)
    {
        std::unique_lock<std::mutex> ulock(_mutex);
        auto iter = _channel_map.find(channel_id);
        if (iter == _channel_map.end())
        {
            return Channel::ptr();
        }
        return iter->second;
    }

private:
    std::mutex _mutex;
    std::unordered_map<std::string, Channel::ptr> _channel_map;
};

以上就是项目第九弹:信道管理模块的全部内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

program-learner

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值