Dispatcher模块剖析

Dispatcher模块剖析

1.Dispatcher模块是干什么的

我在做一个JsonRpc的小项目,其中的请求响应存在着多种类型,针对每一种消息类型都需要一个特定的回调函数进行处理,Dispatcher模块就是负责把不同消息类型跟对应的消息处理函数存起来的进行管理的模块

本质就是类里面有一个map<type, handler>

2.代码实现

2.1 初版实现

c++ 复制代码
namespace testrpc
{
    void onRpcRequest(const testrpc::BaseConnection::ptr&conn, testrpc::BaseMessage::ptr&message)
    {
    }

    void test_dispatcher()
    {
        Dispatcher::ptr dpp=std::make_shared<Dispatcher>();
        dpp->registerHandler(testrpc::MType::REQ_RPC, onRpcRequest);
    }

    using callback_01_t=std::function<void(const testrpc::BaseConnection::ptr&, testrpc::BaseMessage::ptr&)>;
    class Dispatcher
    {
    public:
        using ptr=std::shared_ptr<Dispatcher>;

        // 对外提供一个注册函数
        void registerHandler(testrpc::MType mtype, const callback_01_t& callback)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _handlers.insert(std::make_pair(mtype, callback));
        }

        // 实现一个对外的回调函数,提供给外部,当外部需要根据mtype调用对应的函数时,执行这个回调,间接调用
        void onMessage(const testrpc::BaseConnection::ptr&conn, testrpc::BaseMessage::ptr&message)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto it=_handlers.find(message->mtype());
            if(it==_handlers.end())
            {
                ELOG("错误类型");
                return ;
            }
            it->second(conn, message);
        }
    private:
        std::mutex _mutex;
        // 一个用于存放类型跟回调函数的map
        std::unordered_map<testrpc::MType, callback_01_t> _handlers;
    };
}

代码讲解:定义一个Dispatcher类,类里面有两个属性

  • _handlers:用来存放消息类型和对应消息的处理函数

  • _mutex:用来保护临界资源

两个函数的作用

  • registerHandler:外部将消息类型和回调函数注册到map里面

  • onMessage:提供给外部,当有消息需要处理时,调用这个函数,进而调用提前注册好的消息处理函数

这个类的不足:

外部必须使用指定类型的回调函数进行注册,该类型必须是std::function<void(const testrpc::BaseConnection::ptr&conn, testrpc::BaseMessage::ptr&message)>;。外部还需要把message的类型使用std::dynamic_pointer_cast<testrpc::RpcRequest>(message);进行转换,才可以使用该类型消息内部的方法。这样用起来虽然也可行,但是不方便。

那能不能使用非固定的参数进行注册呢??可以的!可以让Dispatcher里面的unordered_map存不同类型的回调函数,可以利用多态来实现

2.2 改版之后的代码

c++ 复制代码
namespace testrpc
{
    class BaseCallback
    {
    public:
        using ptr=std::shared_ptr<BaseCallback>;
        virtual void onMessage(const BaseConnection::ptr&, BaseMessage::ptr&)=0;
    };

    template<typename T>
    class Callback: public BaseCallback
    {
    public:
        using ptr=std::shared_ptr<Callback<T>>;
        using Callback_t=std::function<void(const BaseConnection::ptr&,std::shared_ptr<T>&)>;

        Callback(const Callback_t&handler)
        :_handler(handler)
        {}
        void onMessage(const BaseConnection::ptr&conn, BaseMessage::ptr&message) override
        {
            // 将message类型进行转换
            auto type_message=std::dynamic_pointer_cast<T>(message);
            // 调用对用的回调函数
            _handler(conn, type_message);
        }
    private:
        // 用来保存外部传入的不同类型的回调函数
        Callback_t _handler;
    };
    class Dispatcher
    {
    public:
        using ptr=std::shared_ptr<Dispatcher>;
        template<typename T>
        void registerHandler(MType mtype, const typename Callback<T>::Callback_t&handler)
        {
            std::unique_lock<std::mutex> lock(_mutex);

            BaseCallback::ptr callback=std::make_shared<Callback<T>>(handler);
            _handlers.insert(std::make_pair(mtype, callback));
        }
        void onMessage(const BaseConnection::ptr&conn, BaseMessage::ptr&message)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto it=_handlers.find(message->mtype());
            if(it==_handlers.end())
            {
                ELOG("消息类型错误");
                conn->shutdown();
                return ;
            }
            // 执行BaseCallback里面的onMessage(父类指针指向子类对象,调用子类的onMessage)
            it->second->onMessage(conn, message);
        }
    private:
        std::mutex _mutex;
        std::unordered_map<MType, BaseCallback::ptr> _handlers;
    };
}

上面这段代码新增了两个类

  • BaseCallback类:回调函数的基类,主要用于存放到unordered_map里面,对这个map来说存放的是同一种类型都是BaseCallback类型,实际上存放的基类都是指向不同的类型的派生的,可以说是来欺骗这个map的

  • Callback类:回调函数的派生类,其中的onMessage函数将同一类型的message转成不同类型的消息类型,进而调用先前注册好的回调函数

3.模块使用

c++ 复制代码
void onRpcResponse(const testrpc::BaseConnection::ptr&conn, testrpc::RpcResponse::ptr&msg)
{
    std::string body=msg->serialize();
    std::cout<<body<<std::endl;
}
void onTopicResponse(const testrpc::BaseConnection::ptr&conn, testrpc::TopicResponse::ptr&msg)
{
    std::string body=msg->serialize();
    std::cout<<body<<std::endl;
}

int main()
{
    // 构造一个调度器(dispatcher)
    testrpc::Dispatcher::ptr dpp=std::make_shared<testrpc::Dispatcher>();
    dpp->registerHandler<testrpc::RpcResponse>(testrpc::MType::RSP_RPC, onRpcResponse);
    dpp->registerHandler<testrpc::TopicResponse>(testrpc::MType::RSP_TOPIC, onTopicResponse);


    auto message_callback=std::bind(&testrpc::Dispatcher::onMessage, dpp.get(), std::placeholders::_1, std::placeholders::_2);
    testrpc::BaseClient::ptr client=testrpc::ClientFactory::create("127.0.0.1", 9090);    
    client->setMessageCallback(message_callback);
    client->connect();
    return 0;
}

模块执行逻辑:外部在使用registerHandler函数时,需要先声明模板类型,传入对应的消息处理函数,该函数明确好消息类型T之后,会构造对应的Callback类型,赋值给基类BaseCallback类型,此时父类指针指向了子类对象,再将这个父类指针存放到unordered_map里面(我总觉得就是欺骗map)。外部客户端会设置回调函数,设置的就是Dispatcher里面的onMessage函数,这个函数根据对应的消息类型,找到先前存入的回调函数执行it->second->onMessage(conn, message);其中的onMessage就是BaseCallback里面的onMessage函数,通过it->second找到的虽然类型是BaseCallback::ptr即:父类指针,但是其指向的是子类对象,onMessage(conn, message)会执行子类的onMessage,将message类型进行转换之后,执行_handler(conn, message)就是调用外部使用registerHandler注册的回调函数,代码段中就是这两个回调函数:

c++ 复制代码
void onRpcResponse(const testrpc::BaseConnection::ptr&conn, testrpc::RpcResponse::ptr&msg)
void onTopicResponse(const testrpc::BaseConnection::ptr&conn, testrpc::TopicResponse::ptr&msg)

使用这种方式,虽然看起来有些多此一举,但是正是理解运行多态的典型应用,也符合开发中的开闭原则(对修改关闭)

相关推荐
依然鸣2 小时前
PTA团体程序设计天梯赛L1真题讲解L1-077-080
开发语言·c++·算法·深度优先·pat考试·图论
allforgood2 小时前
运行容器
linux
Light_It3 小时前
Linux 内核参数 pci-stub.ids=
linux·kernel
RisunJan4 小时前
Linux命令-scriptreplay(终端会话回放)
linux·运维·chrome
库玛西4 小时前
现代 C++ 智能指针全景指南:从 RAII 思想到工业级实践
c语言·开发语言·c++·笔记·面试
spencer_tseng4 小时前
[kylin & linux] install docker
linux·docker·kylin
liulilittle4 小时前
无锁并发容器的设计与实现原理
开发语言·c++·set·map·并发·无锁·lock-free
qeen875 小时前
【数据结构】自平衡二叉搜索树各种旋转算法原理解析及AVL树的C++实现
数据结构·c++·算法
x²+(y-√³x²)²=15 小时前
Linux打包文件到Windows,文件/文件类型丢失
linux·运维·windows