信道管理模块和异步线程模块

1. 信道管理

同样的,客户端也有信道,其功能与服务端几乎一致,或者说不管是客户端的 channel 还是服务端的 channel 都是为了用户提供具体服务而存在的,只不过服务端是为客户端的对应请求提供服务,而客户端的接口服务是为了用户具体需要服务,也可以理解是用户通过客户端 channel 的接口调用来向服务端发送对应请求,获取请求的服务。

  1. 信道信息:
  • 信道 ID
  • 信道关联的网络通信连接对象
  • protobuf 协议处理对象
  • 信道关联的消费者
  • 请求对应的响应信息队列(这里队列使用<请求 ID,响应>hash 表,以便于查找指定的响应)
  • 互斥锁&条件变量(大部分的请求都是阻塞操作,发送请求后需要等到响应才能继续,但是 muduo 库的通信是异步的,因此需要我们自己在收到响应后,通过判断是否是等待的指定响应来进行同步)
  1. 信道操作:
  • 提供创建信道操作
  • 提供删除信道操作
  • 提供声明交换机操作(强断言-有则 OK,没有则创建)
  • 提供删除交换机
  • 提供创建队列操作(强断言-有则 OK,没有则创建)
  • 提供删除队列操作
  • 提供交换机-队列绑定操作
  • 提供交换机-队列解除绑定操作
  • 提供添加订阅操作
  • 提供取消订阅操作
  • 提供发布消息操作
  • 提供确认消息操作
  1. 信道管理:
  • 创建信道
  • 查询信道
  • 删除信道
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 "consumer.hpp"
#include <iostream>
#include <mutex>
#include <condition_variable>
#include <unordered_map>

namespace rabbitmq
{
    typedef std::shared_ptr<google::protobuf::Message> MessagePtr;
    using ProtobufCodecPtr = std::shared_ptr<ProtobufCodec>;
    using basicConsumeResponsePtr = std::shared_ptr<basicConsumeResponse>;
    using basicCommonResponsePtr = std::shared_ptr<basicCommonResponse>;
    class Channel
    {
    public:
        using ptr = std::shared_ptr<Channel>;
        Channel(const muduo::net::TcpConnectionPtr& conn, const ProtobufCodecPtr& codec)
            :_cid(UUIDHelper::uuid()), _conn(conn), _codec(codec) {}
        ~Channel() { basicCancel(); }
        std::string cid() { return _cid; }
        bool openChannel()
        {
            //构造一个打开信道的请求对象,
            std::string rid = UUIDHelper::uuid();
            openChannelRequest req;
            req.set_rid(rid);
            req.set_cid(_cid);
            //然后向服务器发送请求
            _codec->send(_conn, req);
            //等待服务器的响应
           basicCommonResponsePtr resp = waitResponse(rid);
           //返回
           return resp->ok(); 
        }
        void closeChannel()
        {
            //构造一个关闭信道的请求对象,
            std::string rid = UUIDHelper::uuid();
            closeChannelRequest req;
            req.set_rid(rid);
            req.set_cid(_cid);
            //然后向服务器发送请求
            _codec->send(_conn, req);
            //等待服务器的响应
           waitResponse(rid);
        }
        bool declareExchange(
            const std::string &name,
            ExchangeType type, 
            bool durable, 
            bool auto_delete,
            google::protobuf::Map<std::string, std::string> &args)
        {
            //构造一个声明交换机的请求对象,
            std::string rid = UUIDHelper::uuid();
            declareExchangeRequest req;
            req.set_rid(rid);
            req.set_cid(_cid);
            req.set_exchange_name(name);
            req.set_exchange_type(type);
            req.set_durable(durable);
            req.set_auto_delete(auto_delete);
            req.mutable_args()->swap(args);
            //然后向服务器发送请求
            _codec->send(_conn, req);
            //等待服务器的响应
           basicCommonResponsePtr resp = waitResponse(rid);
           //返回
           return resp->ok(); 
        }
        void deleteExchange(const std::string& name)
        {
            //构造一个删除交换机的请求对象,
            std::string rid = UUIDHelper::uuid();
            deleteExchangeRequest req;
            req.set_rid(rid);
            req.set_cid(_cid);
            req.set_exchange_name(name);
            //然后向服务器发送请求
            _codec->send(_conn, req);
            //等待服务器的响应
            waitResponse(rid);
        }
        bool declareQueue(
            const std::string &qname, 
            bool qdurable, 
            bool qexclusive,
            bool qauto_delete,
            google::protobuf::Map<std::string, std::string> &qargs)
        {
            //构造一个声明队列的请求对象,
            std::string rid = UUIDHelper::uuid();
            declareQueueRequest req;
            req.set_rid(rid);
            req.set_cid(_cid);
            req.set_queue_name(qname);
            req.set_durable(qdurable);
            req.set_exclusive(qexclusive);
            req.set_auto_delete(qauto_delete);
            req.mutable_args()->swap(qargs);
            //然后向服务器发送请求
            _codec->send(_conn, req);
            //等待服务器的响应
           basicCommonResponsePtr resp = waitResponse(rid);
           //返回
           return resp->ok(); 
        }
        void deleteQueue(const std::string& qname)
        {
            //构造一个删除队列的请求对象,
            std::string rid = UUIDHelper::uuid();
            deleteQueueRequest req;
            req.set_rid(rid);
            req.set_cid(_cid);
            req.set_queue_name(qname);
            //然后向服务器发送请求
            _codec->send(_conn, req);
            //等待服务器的响应
            waitResponse(rid);
        }
        bool queueBind(
            const std::string &ename, 
            const std::string &qname, 
            const std::string &key)
        {
            //构造一个队列绑定的请求对象,
            std::string rid = UUIDHelper::uuid();
            queueBindRequest req;
            req.set_rid(rid);
            req.set_cid(_cid);
            req.set_exchange_name(ename);
            req.set_queue_name(qname);
            req.set_binding_key(key);
            //然后向服务器发送请求
            _codec->send(_conn, req);
            //等待服务器的响应
           basicCommonResponsePtr resp = waitResponse(rid);
           //返回
           return resp->ok(); 
        }
        void queueUnBind(const std::string &ename, const std::string &qname)
        {
            //构造一个队列解绑的请求对象,
            std::string rid = UUIDHelper::uuid();
            queueUnBindRequest req;
            req.set_rid(rid);
            req.set_cid(_cid);
            req.set_exchange_name(ename);
            req.set_queue_name(qname);
            //然后向服务器发送请求
            _codec->send(_conn, req);
            //等待服务器的响应
           waitResponse(rid);
        }
        void basicPublish(
            const std::string &ename,
            const BasicProperties *bp,
            const std::string &body)
        {
            //构造一个消息发布的请求对象,
            std::string rid = UUIDHelper::uuid();
            basicPublishRequest req;
            req.set_rid(rid);
            req.set_cid(_cid);
            req.set_exchange_name(ename);
            req.set_body(body);
            if(bp != nullptr)
            {
                req.mutable_properties()->set_id(bp->id());
                req.mutable_properties()->set_delivery_mode(bp->delivery_mode());
                req.mutable_properties()->set_routing_key(bp->routing_key());
            }
            //然后向服务器发送请求
            _codec->send(_conn, req);
            //等待服务器的响应
            waitResponse(rid);
        }
        void basicAck(const std::string &msgid)
        {
            if (_consumer.get() == nullptr) 
            {
                DLOG("消息确认时,找不到消费者信息!");
                return ;
            }
            //构造一个消息确认的请求对象,
            std::string rid = UUIDHelper::uuid();
            basicAckRequest req;
            req.set_rid(rid);
            req.set_cid(_cid);
            req.set_queue_name(_consumer->_qname);
            req.set_message_id(msgid);
            //然后向服务器发送请求
            _codec->send(_conn, req);
            //等待服务器的响应
            waitResponse(rid);
        }
        void basicCancel()
        {
            if (_consumer.get() == nullptr) 
            {
                return ;
            }
            //构造一个取消消息订阅的请求对象,
            std::string rid = UUIDHelper::uuid();
            basicCancelRequest req;
            req.set_rid(rid);
            req.set_cid(_cid);
            req.set_queue_name(_consumer->_qname);
            req.set_consumer_tag(_consumer->_tag);
            //然后向服务器发送请求
            _codec->send(_conn, req);
            //等待服务器的响应
            waitResponse(rid);
            _consumer.reset();
        }
        bool basicConsume(
            const std::string &consumer_tag,
            const std::string &queue_name,
            bool auto_ack,
            const ConsumerCallback &cb)
        {
            if (_consumer.get() != nullptr) 
            {
                DLOG("当前信道已订阅其他队列消息!");
                return false;
            }
            //构造一个消息订阅的请求对象,
            std::string rid = UUIDHelper::uuid();
            basicConsumeRequest req;
            req.set_rid(rid);
            req.set_cid(_cid);
            req.set_queue_name(queue_name);
            req.set_consumer_tag(consumer_tag);
            req.set_auto_ack(auto_ack);
            //然后向服务器发送请求
            _codec->send(_conn, req);
            //等待服务器的响应
            basicCommonResponsePtr resp = waitResponse(rid);
            if(resp->ok() == false)
            {
                DLOG("添加订阅失败!");
                return false;
            }
            _consumer = std::make_shared<Consumer>(consumer_tag, queue_name, auto_ack, cb);
            return true;
        }
        //连接收到基础响应后,向hash_map中添加响应
        void putBasicResponse(const basicCommonResponsePtr& resp)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _basic_resp.insert(std::make_pair(resp->cid(), resp));
            _cv.notify_all();
        }
        //连接收到消息推送后,需要通过信道找到对应的消费者对象,通过回调函数进行消息处理
        void consume(const basicConsumeResponsePtr& resp)
        {
            if (_consumer.get() == nullptr) 
            {
                DLOG("消息处理时,未找到订阅者信息!");
                return;
            }
            if (_consumer->_tag != resp->consumer_tag()) 
            {
                DLOG("收到的推送消息中的消费者标识,与当前信道消费者标识不一致!");
                return ;
            }
            _consumer->_callback(resp->consumer_tag(), resp->mutable_properties(), resp->body());
        }
    private:
        basicCommonResponsePtr waitResponse(const std::string &rid)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _cv.wait(lock, [&rid, this](){
                return _basic_resp.find(rid) != _basic_resp.end();
            });
            //等价于while(condition()) _cv.wait();
            basicCommonResponsePtr basic_resp = _basic_resp[rid];
            _basic_resp.erase(rid);
            return basic_resp;
        }
    private:
        std::string _cid;
        muduo::net::TcpConnectionPtr _conn;
        ProtobufCodecPtr _codec;
        Consumer::ptr _consumer;
        std::mutex _mutex;
        std::condition_variable _cv;
        std::unordered_map<std::string, basicCommonResponsePtr> _basic_resp;
    };
}


#endif

关键点分析

  1. 信道标识(_cid)

每个信道在创建时都会生成一个唯一的UUID作为信道ID,用于在请求和响应中标识信道。

  1. 网络通信

依赖muduo库的TcpConnection进行网络通信,使用ProtobufCodec进行消息的编解码。每个操作都会构造对应的Protobuf请求对象,通过_codec发送到服务器,然后等待服务器的响应。

  1. 同步请求-响应机制

每个请求都会生成一个唯一的请求ID(rid),发送请求后,调用waitResponse方法阻塞等待直到收到对应的响应。响应通过putBasicResponse方法存入哈希表,并由条件变量通知等待的线程。

  1. 消费者管理

每个信道同一时间只能有一个消费者(_consumer),消费者包含消费者标签、队列名称、自动确认标志和回调函数。当收到服务器的推送消息(basicConsumeResponse)时,会调用消费者的回调函数处理消息。

  1. 线程安全

使用互斥锁(_mutex)和条件变量(_cv)保护响应哈希表(_basic_resp)的并发访问,确保多线程环境下等待和通知的正确性。

  1. 资源管理

在析构函数中会自动调用basicCancel取消订阅,释放资源。消费者对象使用智能指针管理生命周期。

代码结构

每个AMQP操作遵循相同的模式:

  1. 生成请求ID(rid)。
  2. 构造对应的Protobuf请求对象,设置rid和cid(信道ID)等参数。
  3. 通过_codec发送请求。
  4. 调用waitResponse等待服务器的响应。
  5. 根据响应返回操作结果(成功或失败)。

异常处理

代码中通过DLOG输出日志,但在错误处理方面相对简单,例如在basicAck和basicCancel中,如果找不到消费者,只是打印日志并返回,没有抛出异常。在basicConsume中,如果已经存在消费者,则返回false。

流程总结

下面,我们对这个客户端Channel类的使用流程进行简要说明:

  1. 首先,客户端需要建立与服务器的TCP连接,并创建ProtobufCodec用于编解码。
  2. 然后,创建一个Channel对象,并调用openChannel来打开信道。
  3. 之后,就可以通过Channel对象调用declareExchange、declareQueue等方法,这些方法会阻塞直到收到服务器响应。
  4. 如果要做消费者,调用basicConsume,并传递一个回调函数。当有消息时,服务器会推送,然后回调函数会被调用。
  5. 在回调函数中,处理消息,然后可能需要调用basicAck来确认消息(如果不是自动确认的话)。
  6. 最后,在不再需要时,可以调用closeChannel关闭信道,或者由析构函数自动清理。

2. 封装对外管理的类

cpp 复制代码
    class ChannelManager 
    {
    public:
        using ptr = std::shared_ptr<ChannelManager>;
        ChannelManager(){}
        Channel::ptr create(const muduo::net::TcpConnectionPtr &conn, const ProtobufCodecPtr &codec) 
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto channel = std::make_shared<Channel>(conn, codec);
            _channels.insert(std::make_pair(channel->cid(), channel));
            return channel;
        }
        void remove(const std::string &cid) 
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _channels.erase(cid);
        }
        Channel::ptr get(const std::string &cid) 
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _channels.find(cid);
            if (it == _channels.end()) 
            {
                return Channel::ptr();
            }
            return it->second;
        }
    private:
        std::mutex _mutex;
        std::unordered_map<std::string, Channel::ptr> _channels;
    };

这段代码是一个简单的信道管理类(ChannelManager),用于管理多个Channel对象。其功能类似于一个容器,提供了创建、删除和查找信道的功能,并且是线程安全的。

主要功能:

  1. 创建信道(create):创建一个新的Channel对象,并将其添加到管理器中。
  2. 删除信道(remove):根据信道ID从管理器中移除对应的Channel对象。
  3. 查找信道(get):根据信道ID获取对应的Channel对象。

细节分析:

  1. 线程安全:使用互斥锁(std::mutex)保护对内部unordered_map的并发访问,所有公共方法都加锁。
  2. 智能指针管理:使用shared_ptr管理Channel对象,便于自动管理生命周期。
  3. 映射关系:使用unordered_map存储信道ID到Channel对象的映射,便于快速查找。

3. 异步工作线程实现

客户端这边存在两个异步工作线程,

  • 一个是 muduo 库中客户端连接的异步循环线程 EventLoopThread,
  • 一个是当收到消息后进行异步处理的工作线程池。

这两项都不是以连接为单元进行创建的,而是创建后,可以用以多个连接中,因此单独进行封装。

cpp 复制代码
#ifndef __M_WORKER_H__
#define __M_WORKER_H__
#include "muduo/net/EventLoopThread.h"
#include "../mqcommon/logger.hpp"
#include "../mqcommon/helper.hpp"
#include "../mqcommon/threadpool.hpp"

namespace rabbitmq 
{
    class AsyncWorker 
    {
    public:
        using ptr = std::shared_ptr<AsyncWorker>;
        muduo::net::EventLoopThread _loopthread;
        threadpool _pool;
    };
}

#endif

两个异步线程组件详解

  1. muduo::net::EventLoopThread _loopthread

功能描述

这是基于 muduo 网络库的事件循环线程,专门处理网络I/O事件

工作内容

  • 监听 TCP 连接的读写事件
  • 处理连接建立和断开
  • 接收服务器发送的数据
  • 发送客户端请求到服务器
  • 管理定时器(如心跳、超时)
  1. threadpool _pool

功能描述

这是一个通用的工作线程池,专门处理业务逻辑计算

工作内容

  • 处理服务器推送的消息
  • 执行消息确认操作
  • 处理用户回调函数
  • 其他耗时的业务计算
相关推荐
LawrenceLan2 小时前
30.Flutter 零基础入门(三十):GridView 网格布局 —— 九宫格与商品列表必学
开发语言·前端·flutter·dart
yoyo君~2 小时前
从内存管理到并发架构:C++ 核心内功修炼指南
开发语言·c++·学习·无人机
2501_941982052 小时前
告别手动,Java 自动化调用企微外部群的深度实践
开发语言·python
问好眼2 小时前
《算法竞赛进阶指南》0x01 位运算-4.最短Hamilton路径
c++·算法·动态规划·位运算·信息学奥赛
载数而行5202 小时前
算法系列5之交换排序
c语言·数据结构·c++·算法·排序算法
cici158742 小时前
基于C#的智能仓储上位机系统实现方案
开发语言·c#
-Try hard-2 小时前
线程间通信 | 避免资源竞争、实现同步通信
linux·开发语言·信息与通信
楼田莉子2 小时前
C++并发库介绍(上)
开发语言·c++·学习
Nightmare0042 小时前
切换conda环境的时候输出zstandard could not be imported. Running without .conda support.
开发语言·python·conda