第十五章:客户端信道管理模块

目录

第一节:代码实现

        1-1.Channel类

        1-2.ChannelManager

下期预告:


        客户端信道管理模块在mq_client目录下实现。

第一节:代码实现

        创建mq_channel.hpp文件,打开并添加以下内容:

#ifndef __M_CHANNEL_H__
#define __M_CHANNEL_H__
#include "muduo/net/TcpConnection.h"
#include "muduo/proto/codec.h"
#include "muduo/proto/dispatcher.h"

#include "mq_consumer.hpp"

#include "../mqcommon/mq_logger.hpp"
#include "../mqcommon/mq_helper.hpp"
#include "../mqcommon/mq_msg.pb.h"
#include "../mqcommon/mq_proto.pb.h"
#include "../mqcommon/mq_threadpool.hpp"

#include 

namespace zd
{};

#endif

        先定义协议处理器和响应的智能指针的别名,方便使用:

    using ProtobufCodecPtr = std::shared_ptr;
    using BasicConsumerResponsePtr = std::shared_ptr;
    using BaseResponsePtr = std::shared_ptr;

        1-1.Channel类

        class Channel的作用是提供各种业务的接口,这些接口收到来自用户的数据之后,将数据组织成请求,然后发送给客户端,之后等待服务器的响应。

        先定义它需要的成员变量:

        private:
            std::string _channel_id;       // 信道唯一id:由随机id生成器生成
            Consumer::ptr _consumer;       // 信道关联的消费者 
            muduo::net::TcpConnectionPtr _conn; // 信道使用的连接  
            ProtobufCodecPtr _codec;       // protobuf协议处理器
            std::mutex _mtx;               // 互斥锁
            std::condition_variable _cv;   // 条件变量:用于线程等待响应
            std::unordered_map _baseResp; // 客户端收到的、还未处理的响应

                构造函数,传入使用的连接和协议处理器即可:

            using ptr = std::shared_ptr;

            Channel(
                    const ProtobufCodecPtr& codec,
                    const muduo::net::TcpConnectionPtr& conn
                   ):
            _channel_id(UUIDHelper::uuid()),_codec(codec),_conn(conn)
            {}

        获取信道的唯一识别码,用于信道的管理:

            std::string getId()
            {
                return _channel_id;
            }

        基本上每个请求发送后都需要等待服务器响应,所以将等待响应的功能封装成一个成员函数:

        private:
            // 客户端发送请求后,等待服务器响应
            BaseResponsePtr waitResponse(const std::string& rid)
            {
                std::unique_lock lock(_mtx);
                // 不仅需要外部唤醒,还需要满足条件(就是等待消息的响应)
                _cv.wait(lock,[&rid,this](){
                    return _baseResp.find(rid) != _baseResp.end();
                });
                BaseResponsePtr resp = _baseResp[rid];
                _baseResp.erase(rid);
                return resp;
            };

        然后是连接收到响应后的处理。

        连接收到响应后,会找到对应信道,如果是普通响应,调用该信道的第一个函数,唤醒所有正在等待响应的线程,如果某个线程发现是自己等待的响应(请求id与响应id相同),就执行waitResponse()。

        如果是消息推送的响应,就调用关联消费者的消息处理回调函数。

        public:
            // 连接收到基础响应后,将响应保存起来
            void putBaseResponse(const BaseResponsePtr& resp)
            {
                std::unique_lock lock(_mtx);
                _baseResp.insert(std::make_pair(resp->rid(),resp));
                // 收到响应就唤醒
                _cv.notify_all();
            }

            // 连接收到消息响应后,执行用户添加的消息处理回调函数
            void consume(const BasicConsumerResponsePtr& resp)
            {
                // 正常情况下不会出现两种错误的情况
                if(_consumer.get() == nullptr)
                {
                    LOG("收到的消息找不到订阅者信息");
                    return;
                }
                if(_consumer->tag != resp->consumer_tag())
                {
                    LOG("收到的消息的消费者与当前信道消费者不符");
                    return;
                }    
                // 回调函数,创建消费者时由用户传入
                _consumer->callback(resp->consumer_tag(),resp->mutable_properties(),resp->body());
            }

        请求发送函数的逻辑都是一样的,它们的参数都是由用户输入的、完成业务所需的数据,然后由这些函数将信息构造成mq_proto.proto中定义的类型,发送给服务器,等待服务器的响应:

            // 打开这个信道(服务器创建对应信道)
            bool open()
            {
                ChannelOpenRequest req;
                std::string rid = UUIDHelper::uuid();
                req.set_rid(rid);
                req.set_channel_id(_channel_id);
                // 向服务器发送请求
                _codec->send(_conn,req);
                // 等待服务器响应
                BaseResponsePtr resp = waitResponse(rid);
                // 返回响应结果
                return resp->ok();
            }
            // 关闭这个信道(服务器关闭对应信道)
            void close()
            {
                ChannelCloseRequest req;
                std::string rid = UUIDHelper::uuid();
                req.set_rid(rid);
                req.set_channel_id(_channel_id);
                _codec->send(_conn,req);
                waitResponse(rid);
            }
            // 声明交换机
            bool declareExchange(
                const std::string& name,
                ExchangeType type,
                bool durable,
                bool autodelete,
                google::protobuf::Map& args)
            {
                // 构造声明交换机的请求对象
                ExchangeDeclareRequest req;
                std::string rid = UUIDHelper::uuid();
                req.set_rid(rid);
                req.set_channel_id(_channel_id);
                req.set_exchange_name(name);
                req.set_exchange_type(type);
                req.set_durable(durable);
                req.set_autodelete(autodelete);
                req.mutable_args()->swap(args);
                // 向服务器发送请求
                _codec->send(_conn,req);
                // 等待服务器响应
                BaseResponsePtr resp = waitResponse(rid);
                // 返回响应结果
                return resp->ok();
            }
            // 移除交换机
            void deleteExchange(const std::string& name)
            {
                ExchangeDeleteRequest req;
                std::string rid = UUIDHelper::uuid();
                req.set_rid(rid);
                req.set_channel_id(_channel_id);
                req.set_exchange_name(name);
                // 向服务器发送请求
                _codec->send(_conn,req);
                // 等待服务器响应
                waitResponse(rid);
            }
            
            // 声明队列
            bool declareMsgQueue(
                const std::string& name,
                bool durable,
                bool exclusive,
                bool autodelete, 
                google::protobuf::Map& args)
            {
                // 构造声明队列的请求对象
                MsgQueueDeclareRequest req;
                std::string rid = UUIDHelper::uuid();
                req.set_rid(rid);
                req.set_channel_id(_channel_id);
                req.set_msgqueue_name(name);
                req.set_durable(durable);
                req.set_exclusive(exclusive);
                req.set_autodelete(autodelete);
                req.mutable_args()->swap(args);
                // 向服务器发送请求
                _codec->send(_conn,req);
                // 等待服务器响应
                BaseResponsePtr resp = waitResponse(rid);
                // 返回响应结果
                return resp->ok();
            }
            // 移除队列
            void deleteMsgQueue(const std::string& name)
            {
                MsgQueueDeleteRequest req;
                std::string rid = UUIDHelper::uuid();
                req.set_rid(rid);
                req.set_channel_id(_channel_id);
                req.set_msgqueue_name(name);
                // 向服务器发送请求
                _codec->send(_conn,req);
                // 等待服务器响应
                waitResponse(rid);
            }
            
            // 绑定
            bool bind(const std::string& ename,const std::string& qname,const std::string& bid_ky)
            {
                QueueBindRequest req;
                std::string rid = UUIDHelper::uuid();
                req.set_rid(rid);
                req.set_channel_id(_channel_id);
                req.set_exchange_name(ename);
                req.set_msgqueue_name(qname);
                req.set_binding_key(bid_ky);

                _codec->send(_conn,req);
                BaseResponsePtr resp = waitResponse(rid);
                return resp->ok();
            }
            // 解绑
            void unBind(const std::string& ename,const std::string& qname)
            {
                QueueUnbindRequest req;
                std::string rid = UUIDHelper::uuid();
                req.set_rid(rid);
                req.set_channel_id(_channel_id);
                req.set_exchange_name(ename);
                req.set_msgqueue_name(qname);

                _codec->send(_conn,req);
                waitResponse(rid);
            }
            
            // 添加消息
            bool basicPublish(const std::string& ename,BasicProperties* bp,const std::string& body)
            {
                BasicPublishRequest req;
                std::string rid = UUIDHelper::uuid();
                req.set_rid(rid);
                req.set_channel_id(_channel_id);
                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);
                BaseResponsePtr resp = waitResponse(rid);
                return resp->ok();
            }
            // 确认/删除消息
            void basicAck(const std::string& msg_id)
            {
                if(_consumer.get() == nullptr)
                {
                    LOG("消息确认时,找不到消费者信息");
                    return;
                }
                BasicAckRequest req;
                std::string rid = UUIDHelper::uuid();
                req.set_rid(rid);
                req.set_channel_id(_channel_id);
                req.set_msgqueue_name(_consumer->qname);
                req.set_message_id(msg_id);

                _codec->send(_conn,req);
                waitResponse(rid);
            }

            // 订阅队列
            bool basicConsume(const std::string& qname,const std::string& ctag,bool auto_ack,const ConsumerCallback& cb)
            {
                if(_consumer.get() != nullptr)
                {
                    LOG("当前用户信道已订阅其他队列");
                    return false;
                }
                BasicConsumerRequest req;
                std::string rid = UUIDHelper::uuid();
                req.set_rid(rid);
                req.set_channel_id(_channel_id);
                req.set_msgqueue_name(qname);
                req.set_consumer_tag(ctag);
                req.set_auto_ack(auto_ack);
                
                _codec->send(_conn,req);
                BaseResponsePtr resp = waitResponse(rid);
                if(resp->ok() == false)
                {
                    LOG("订阅队列失败");
                    return false;
                } 
                //订阅队列后,这个信道就拥有消费者的身份了
                _consumer = std::make_shared(ctag,qname,auto_ack,cb);
                return true;
            }
            // 取消订阅
            void basicCancel()
            {
                if(_consumer.get() == nullptr)
                {
                    return;
                }
                BasicCancelRequest req;
                std::string rid = UUIDHelper::uuid();
                req.set_rid(rid);
                req.set_channel_id(_channel_id);
                req.set_consumer_tag(_consumer->tag);
                req.set_msgqueue_name(_consumer->qname);

                _codec->send(_conn,req);
                waitResponse(rid);
                // 取消订阅后,这个信道就失去消费者的身份了
                _consumer = nullptr;
            }

        析构函数:信道被销毁时,取消关联消费者对队列的订阅。

            ~Channel()
            {
                // 信道被销毁时,取消订阅
                basicCancel();
            }

        1-2.ChannelManager

        该类用来管理一个连接的所有信道,因为客户端信道的功能就是组织请求,所以它并不需要像服务器的class ChannelManager 一样传入各种管理句柄。

        其次,因为客户端信道的id是随机id生成器生成的,在创建信道时,也不需要判断信道是否已经存在。

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

            Channel::ptr createChannel(
                const ProtobufCodecPtr& codec,
                const muduo::net::TcpConnectionPtr& conn)
            {
                Channel::ptr channel = std::make_shared(codec,conn);
                std::unique_lock lock(_mtx);
                _channels.insert(std::make_pair(channel->getId(),channel));
                return channel;
            }

            void removeChannel(const std::string cid)
            {
                std::unique_lock lock(_mtx);
                _channels.erase(cid);
            }

            Channel::ptr getOneChannel(const std::string& cid)
            {
                std::unique_lock lock(_mtx);
                auto it = _channels.find(cid);
                if(it == _channels.end())
                    return nullptr;
                return it->second;
            }
        private:
            std::unordered_map _channels;
            std::mutex _mtx;
    };

        那么客户端信道管理模块也完成了,这个模块也不进行单元测试。

下期预告:

        最后是客户端模块的编写,在消息队列中弱化了客户端的概念,一个连接其实就是一个客户端,因为一个连接可以拥有多个信道,意味着单个连接就可以使用服务器的所有业务。

        而且客户端与服务器的结构也是十分类似的,甚至因为没有如此多的业务处理函数而更简单了。

你可能感兴趣的:(仿Rabbit消息队列,c++,消息队列,C++)