【jsonRpc项目】Dispatcher模块

目录

一.Dispatcher模块相关介绍

1.1.为什么需要Dispatcher模块?

1.2.Dispatcher模块的实现思想

二.第一版本

三.第二版本

3.1.第一版出现的问题

3.2.解决方案------模板

3.3.测试


一.Dispatcher模块相关介绍

1.1.为什么需要Dispatcher模块?

你可以想象一下这个场景:你的服务器程序就像一个前台接待员,每天要处理各种各样的客户请求,比如有人来咨询、有人来办业务、有人来投诉。同样,它也要给客户端发出各种不同的通知。

现在,最直接的编程方法就是:在这个"接待员"的函数里,写一大堆 if...else if... 语句。

  • 收到一个消息:
  • 如果 消息类型是A,就 执行A的处理逻辑;
  • 否则如果 消息类型是B,就 执行B的处理逻辑;
  • 否则如果 消息类型是C,就 执行C的处理逻辑;
  • ...(可能有几十个)

这种方法一开始看似简单直接,但很快就会变得难以管理。

它主要带来两个大问题:

  1. 代码混乱,难以阅读和维护:所有处理逻辑都堆在一个巨大的函数里,每次想看懂或修改一个类型的处理,都得在这一长串条件语句里费力地寻找。

  2. 每次新增功能都很麻烦,而且危险 :这是更关键的问题。假设一个月后,你需要增加处理一种新的消息类型D。这时,你必须打开这个已经非常庞大、且正在稳定运行的函数,在里面找到合适的位置,**小心翼翼地插入一个新的 else if 分支。**这个过程就像在一台正在运转的复杂机器里,直接动手拧螺丝、改线路,一不小心就可能碰坏现有的、运行良好的部分(这被称为"回归错误")。

我们期望的理想状态是怎样的呢?

在软件开发中,有一个很好的设计原则叫 "开闭原则"。通俗点讲就是:

  • 对修改关闭:已经写好并测试通过的、主要运转的代码(比如那个核心的"接待员"函数),我们尽量不去动它、改它。因为改它就可能引入新的错误。

  • 对扩展开放:当系统需要增加新功能(比如处理新消息类型D)时,我们应该能够用"添加"新代码的方式来完成,而不是"修改"旧代码。

Dispatcher模块就是来实现这个理想的。

它的工作方式是这样的:

  • 我们不再在那个核心函数里写 if...else。这个函数现在只做一件事:收到任何消息后,都直接转交给 Dispatcher(分发器)

  • Dispatcher 是一个独立的模块,它内部维护了一个"路由表"。这个表就像一本通讯录,记录了"消息类型A"对应"处理函数A","消息类型B"对应"处理函数B"。

  • Dispatcher 收到消息后,会查这本"通讯录",立刻就知道该把消息交给哪个具体的、专门的函数来处理。

这样做的好处立刻就体现出来了:

  • 核心代码干净、稳定:那个最初的"接待员"函数变得非常简洁和稳定,它只负责转发,再也不需要修改。

  • 维护和扩展变得安全、简单:当你需要增加对新消息类型D的支持时,你完全不用去碰那些旧的、核心的函数。你要做的只有两件事:

    1. 写一个新的、独立的函数,专门用来处理类型D。

    2. 在 Dispatcher 的"路由表"里注册一条新记录:"当收到类型D时,请调用这个新函数"。

  • 结构清晰:所有处理逻辑都被分解到一个个独立的函数中,代码组织得井井有条,易于理解和协作。

总结一下,Dispatcher 模块本质上是一个"消息路由器"。它通过将"判断消息类型"和"执行处理逻辑"这两个动作解耦,把我们从混乱的条件分支中解放出来,让代码更容易维护,也让系统能够以更安全、更优雅的方式增长和变化。

1.2.Dispatcher模块的实现思想

Dispatcher模块在RPC通信框架中扮演着消息路由中枢的角色。

它的核心职责是:根据消息的类型,将其精准分派到对应的业务处理函数中执行,从而实现网络数据包与上层应用逻辑的解耦。

当有新消息到来,那么框架底层(muduo网络库)就会调用onMessage回调函数,那么在这个回调函数里面,我们需要对接收到的原始字节流完成应用层协议解析,那么这个应用层协议处理的过程,就是存在于上面的Protocol模块,经过应用层协议处理解析后,我们得到的是一个结构化的消息对象。

这个结构化的消息对象里面有很多数据:比如说这条消息代表客户端的何种请求。

但是我们现在仅仅只是获取到了客户端发来的这个数据,我们还需要对这个消息进行处理。

我们需要根据消息类型来作出对应的处理,去选择调用不同的消息处理回调函数。

Dispatcher类正是为此而设计。

工作原理与设计

  • Dispatcher内部维护一个 "消息类型-处理函数"映射表 (通常实现为std::unordered_map或类似哈希结构)。
  • 框架的使用者可以提前向该模块注册 不同消息类型对应的业务回调函数的注册。Dispatcher就把这个注册进来的**"消息类型-处理函数"存储到上面那个映射表里面去。**
  • 当一条消息抵达时,Dispatcher通过其类型标识快速查找映射表,获取对应的回调函数并执行,完成消息的处理闭环。

核心处理的消息类型

Dispatcher需要区分并处理以下几类核心消息,它们共同支撑了RPC框架的基础功能与高级特性:

  1. RPC调用与响应

    • RPC请求:客户端发起的远程方法调用。

    • RPC响应:服务端返回的方法执行结果或错误。

  2. 服务治理相关

    • 服务注册/发现请求与响应:服务提供者向注册中心注册服务,消费者查询可用服务。

    • 服务上线/下线通知:注册中心主动推送服务节点状态变更。

  3. 发布/订阅相关

    • 主题管理请求:创建、删除主题。

    • 订阅管理请求:订阅或取消订阅特定主题。

    • 消息发布请求与投递:向指定主题发布消息,并由系统分发给所有订阅者。

通过这种设计,Dispatcher模块将复杂的网络消息处理流程标准化、模块化。它不仅简化了业务逻辑的接入流程------新功能只需注册新的消息类型与回调即可接入系统,同时也提高了框架的可扩展性与可维护性,为整个RPC系统的清晰分层与高效协作奠定了坚实基础。

二.第一版本

dispatcher.hpp

cpp 复制代码
#pragma once
#include "../../common/message.hpp"
#include "../../common/net.hpp"

namespace jsonRpc
{

    class Dispatcher
    {
    public:
        // 使用智能指针别名,方便管理
        using ptr = std::shared_ptr<Dispatcher>;

        // 注册消息处理器:将消息类型与对应的处理函数绑定
        void registerHandler(MType mtype,const MessageCallback &handler)
        {
            // 加锁确保线程安全,防止多线程同时修改处理器映射表
            std::unique_lock<std::mutex> lock(_mutex);
            // 将消息类型和处理函数插入到映射表中
            _handlers.insert(std::make_pair(mtype, handler));
        }

        // 消息处理入口:根据消息类型分发到对应的处理函数
        void onMessage(const BaseConnection::ptr &conn, BaseMessage::ptr &msg)
        {
            // 找到消息类型对应的业务处理函数,进行调用即可

            // 加锁确保线程安全,防止多线程同时访问处理器映射表
            std::unique_lock<std::mutex> lock(_mutex);
            // 根据消息类型在映射表中查找对应的处理函数
            auto it = _handlers.find(msg->mtype());

            if (it != _handlers.end())
            {
                // 找到对应的处理函数,直接调用它处理消息
                return it->second(conn, msg);
            }

            // 没有找到指定类型的处理回调--因为客户端和服务端都是我们自己设计的,因此理论上不可能出现这种情况
            // 如果确实收到未知消息,记录日志并关闭连接以确保安全
            ELOG("收到未知类型的消息!");
            conn->shutdown();
        }

    private:
        std::mutex _mutex; // 互斥锁,用于保证多线程环境下的线程安全

        // 消息处理器映射表:键为消息类型(MType),值为对应的消息处理函数(MessageCallback)
        // 通过这个映射表实现消息类型到处理函数的快速查找
        std::unordered_map<MType, MessageCallback> _handlers;
    };

}

我们来进行测试一下

client.cpp

cpp 复制代码
#include "../../common/message.hpp"
#include "../../common/net.hpp"
#include<thread>
#include"dispatcher.hpp"

// onMessage函数:处理接收到的消息
// 参数:conn - 连接对象指针,msg - 接收到的消息对象指针
void onRPCResponse(const jsonRpc::BaseConnection::ptr &conn, jsonRpc::BaseMessage::ptr &msg)
{
    // 将消息对象序列化为字符串格式
    std::cout<<"收到了RPC响应"<<std::endl;
}
void onTopicResponse(const jsonRpc::BaseConnection::ptr &conn, jsonRpc::BaseMessage::ptr &msg)
{
    // 将消息对象序列化为字符串格式
    std::cout<<"收到了topic响应"<<std::endl;
}

// main函数:程序入口点
int main()
{
    auto dispatcher =std::make_shared<jsonRpc::Dispatcher>();
    dispatcher->registerHandler(jsonRpc::MType::RSP_RPC,onRPCResponse);//注册RSP_RPC类型的回调函数
    dispatcher->registerHandler(jsonRpc::MType::RSP_TOPIC,onTopicResponse);//注册RSP_RPC类型的回调函数

    // 创建一个JSON-RPC服务器实例,监听端口9090
    auto client = jsonRpc::ClientFactory::create("127.0.0.1", 9090);
    auto message_cb =std::bind(&jsonRpc::Dispatcher::onMessage,dispatcher.get(),std::placeholders::_1,std::placeholders::_2);

    client->setMessageCallback(message_cb);//消息到达回调函数,消息一到达就会去调用jsonRpc::Dispatcher::onMessage函数,
    //然后onMessage函数会根据消息类型去调用对应的处理函数,例如上面收到了RSP_RPC类型的消息,就会去调用onRPCResponse函数
    client->connect();
    
    // 发送RPC请求
    auto rpc_req = jsonRpc::MessageFactory::create<jsonRpc::RpcRequest>();
    rpc_req->setId("11111");
    rpc_req->setMType(jsonRpc::MType::REQ_RPC);
    rpc_req->setMethod("Add");
    
    Json::Value param;
    param["num1"] = 11;
    param["num2"] = 22;
    rpc_req->setParams(param);
    
    if (client->send(rpc_req)) {
        std::cout << "RPC请求发送成功!" << std::endl;
    } else {
        std::cout << "RPC请求发送失败!" << std::endl;
    }
    
    // 发送TOPIC请求
    auto topic_req = jsonRpc::MessageFactory::create<jsonRpc::TopicRequest>();
    topic_req->setId("22222");
    topic_req->setMType(jsonRpc::MType::REQ_TOPIC);
    if (client->send(topic_req)) {
        std::cout << "TOPIC请求发送成功!" << std::endl;
    } else {
        std::cout << "TOPIC请求发送失败!" << std::endl;
    }
    
    // 等待响应
    std::this_thread::sleep_for(std::chrono::seconds(10));

    client->shutdown();

    // 程序正常结束,返回0
    return 0;
}

server.cpp

cpp 复制代码
#include "../../common/message.hpp"
#include "../../common/net.hpp"
#include "dispatcher.hpp"

void onRPCRequest(const jsonRpc::BaseConnection::ptr &conn, jsonRpc::BaseMessage::ptr &msg)
{
    // 创建一个RPC响应消息对象
    // 使用MessageFactory工厂类创建指定类型的消息对象
    auto rpc_req = jsonRpc::MessageFactory::create<jsonRpc::RpcResponse>();
    // 设置响应消息的ID为"11111"
    rpc_req->setId("11111");
    // 设置消息类型为RPC响应类型
    rpc_req->setMType(jsonRpc::MType::RSP_RPC);
    // 设置响应码为成功
    rpc_req->setRCode(jsonRpc::RCode::RCODE_OK);
    // 通过连接对象将响应消息发送回客户端
    conn->send(rpc_req);
}
// onMessage函数:处理接收到的消息
// 参数:conn - 连接对象指针,msg - 接收到的消息对象指针
void onTopicRequest(const jsonRpc::BaseConnection::ptr &conn, jsonRpc::BaseMessage::ptr &msg)
{
    auto rpc_req = jsonRpc::MessageFactory::create<jsonRpc::TopicResponse>();
    // 设置响应消息的ID为"11111"
    rpc_req->setId("11111");
    // 设置消息类型为TOPIC响应类型
    rpc_req->setMType(jsonRpc::MType::RSP_TOPIC);
    // 设置响应码为成功
    rpc_req->setRCode(jsonRpc::RCode::RCODE_OK);
    // 通过连接对象将响应消息发送回客户端
    conn->send(rpc_req);
}

// main函数:程序入口点
int main()
{

    auto dispatcher = std::make_shared<jsonRpc::Dispatcher>();
    dispatcher->registerHandler(jsonRpc::MType::REQ_RPC, onRPCRequest);  
    dispatcher->registerHandler(jsonRpc::MType::REQ_TOPIC, onTopicRequest); 

    // 创建一个JSON-RPC服务器实例,监听端口9090
    auto server = jsonRpc::ServerFactory::create(9090);

    auto message_cb = std::bind(&jsonRpc::Dispatcher::onMessage, dispatcher.get(), std::placeholders::_1, std::placeholders::_2);

    // 设置服务器的消息处理回调函数为jsonRpc::Dispatcher::onMessage函数
    server->setMessageCallback(message_cb);

    // 启动服务器,开始监听客户端连接和处理请求
    server->start();

    // 程序正常结束,返回0
    return 0;
}

三.第二版本

3.1.第一版出现的问题

不知道大家有没有注意上面的不同类型的回调处理函数的参数

cpp 复制代码
void onRPCResponse(const jsonRpc::BaseConnection::ptr &conn, jsonRpc::BaseMessage::ptr &msg)
{
    // 将消息对象序列化为字符串格式
    std::cout<<"收到了RPC响应"<<std::endl;
}
void onTopicResponse(const jsonRpc::BaseConnection::ptr &conn, jsonRpc::BaseMessage::ptr &msg)
{
    // 将消息对象序列化为字符串格式
    std::cout<<"收到了topic响应"<<std::endl;
}

我们发现第二个参数都是jsonRpc::BaseMessage::ptr?

jsonRpc::BaseMessage是作为基类,那么我们就不能使用子类的那些接口了。那怎么办?

我们都知道这个就是消息基类里面的指针

cpp 复制代码
// 抽象消息基类 - 定义RPC消息的通用接口
class BaseMessage
{
public:
    using ptr = std::shared_ptr<BaseMessage>;
    virtual ~BaseMessage() {}
    // 设置消息ID
    virtual void setId(const std::string &id)
    {
        _rid = id;
    }
    // 获取消息ID
    virtual std::string rid()
    {
        return _rid;
    }
    // 设置消息类型
    virtual void setMType(MType mtype)
    {
        _mtype = mtype;
    }
    // 获取消息类型
    virtual MType mtype()
    {
        return _mtype;
    }
    // 序列化消息到字符串
    virtual std::string serialize() = 0;
    // 从字符串反序列化消息
    virtual bool unserialize(const std::string &msg) = 0;
    // 检查消息有效性
    virtual bool check() = 0;

private:
    MType _mtype;     // 消息类型
    std::string _rid; // 请求ID
};

我们最后根据这个基类派生出来了6个类

消息类继承层次结构

第一层:抽象基类

  • BaseMessage:所有消息类型的基类,定义了消息的通用接口(如序列化、反序列化、检查等)。

第二层:JSON 消息基类

  • JsonMessage :继承自 BaseMessage。作为所有 JSON 格式消息的基类,封装了 JSON 数据的存储和基本操作。

第三层:请求/响应分类

  • JsonRequest :继承自 JsonMessage。所有 JSON 请求消息的基类。

  • JsonResponse :继承自 JsonMessage。所有 JSON 响应消息的基类。

第四层:具体消息类型

请求类型 (都继承自 JsonRequest):

  1. RpcRequest:RPC(远程过程调用)请求消息。

  2. TopicRequest:主题(发布/订阅)请求消息。

  3. ServiceRequest:服务(注册/发现)请求消息。

响应类型 (都继承自 JsonResponse):

  1. RpcResponse:RPC(远程过程调用)响应消息。

  2. TopicResponse:主题(发布/订阅)响应消息。

  3. ServiceResponse:服务(注册/发现)响应消息。

那么我们这里为什么不直接使用最后的这6个类来作为回调函数里面的第二个参数??

事实上这个和我们的回调函数设定的类型是有关的

cpp 复制代码
using MessageCallback = std::function<void(const BaseConnection::ptr &, BaseMessage::ptr &)>; // 消息接收回调

我们消息类型设定的就是BaseMessage::ptr&,如果说我们去修改这个设定好的回调函数类型,将它修改成6种专门对应的消息类型的话!那么我们的整个的代码修改量/工作量其实是非常巨大的。

那么其实还有一种方法,就是使用dynamic_pointer_cast,来将父类指针转换成子类指针。这个过程一定会成功!!为什么呢?

因为我们在发送请求还是响应的时候,都是需要先构造出一个子类对象的,然后我们将子类对象发送出去**,而构建这些子类对象都是通过一个工厂类的**,就像下面这样子的

cpp 复制代码
void onRPCRequest(const jsonRpc::BaseConnection::ptr &conn, jsonRpc::BaseMessage::ptr &msg)
{
    // 创建一个RPC响应消息对象
    // 使用MessageFactory工厂类创建指定类型的消息对象
    auto rpc_rsp = jsonRpc::MessageFactory::create<jsonRpc::RpcResponse>();
    // 设置响应消息的ID为"11111"
    rpc_rsp->setId("11111");
    // 设置消息类型为RPC响应类型
    rpc_rsp->setMType(jsonRpc::MType::RSP_RPC);
    // 设置响应码为成功
    rpc_rsp->setRCode(jsonRpc::RCode::RCODE_OK);
    // 通过连接对象将响应消息发送回客户端
    conn->send(rpc_rsp);
}

jsonRpc::MessageFactory里面也就是下面这个

cpp 复制代码
// 消息对象工厂类
    class MessageFactory
    {
    public:
        // 静态方法:根据消息类型创建对应的消息对象
        static BaseMessage::ptr create(MType mtype)
        {
            switch (mtype)
            {
            case MType::REQ_RPC:
                return std::make_shared<RpcRequest>();
            case MType::RSP_RPC:
                return std::make_shared<RpcResponse>();
            case MType::REQ_TOPIC:
                return std::make_shared<TopicRequest>();
            case MType::RSP_TOPIC:
                return std::make_shared<TopicResponse>();
            case MType::REQ_SERVICE:
                return std::make_shared<ServiceRequest>();
            case MType::RSP_SERVICE:
                return std::make_shared<ServiceResponse>();
            }
            return BaseMessage::ptr();
        }

        // 模板方法:创建指定类型的消息对象,支持传递构造参数
        template <typename T, typename... Args>
        static std::shared_ptr<T> create(Args &&...args)
        {
            return std::make_shared<T>(std::forward(args)...);
        }
    };

可以看到这个工厂类里面是创建了子类指针,但是却返回了父类指针。

现在我们在回调函数里面使用dynamic_pointer_cast将父类指针转换回子类指针,这是一定可以的。

cpp 复制代码
void onRPCRequest(const jsonRpc::BaseConnection::ptr &conn, jsonRpc::BaseMessage::ptr &msg)
{
    //查看发送过来的请求
    auto rpc_req = dynamic_pointer_cast<jsonRpc::RpcReuest>();//一定可以成功


}

这样子我们就可以去使用子类的那些接口了。

但是大家有没有想过,这样子可是会增加一定的性能开销的。


3.2.解决方案------模板

Dispatcher类工作原理与设计

  • Dispatcher内部维护一个 "消息类型-处理函数"映射表 (通常实现为std::unordered_map或类似哈希结构)。
  • 框架的使用者可以提前向该模块注册 不同消息类型对应的业务回调函数的注册。Dispatcher就把这个注册进来的**"消息类型-处理函数"存储到上面那个映射表里面去。**
  • 当一条消息抵达时,Dispatcher通过其类型标识快速查找映射表,获取对应的回调函数并执行,完成消息的处理闭环。

因此,我们引入了模板,我们先将Dispatcher的注册函数的第二个参数------回调函数 改成模板

cpp 复制代码
template<typename T>
void registerHandler(MType mtype, T&& handler)
{

}

但是问题来了,这个Dispatcher内部还有一个哈希表的成员啊,这个回调函数类型也是需要修改成模板T类型吗?

cpp 复制代码
// ❌ 错误:T是模板参数,不是具体类型
template<typename T>
std::unordered_map<int, T> myMap;  // 编译错误

​哈希表(如std::unordered_map, std::unordered_set)可以存储具体类型的实例,不能存储模板本身。

因为T在编译时还不确定具体类型,编译器不知道要为它分配多少内存。

如果说我们一定需要在哈希表里面存储模板类型的话,那么只有2种方法

  1. 方式1:实例化模板类对象
  2. 方式2:使用多态(运行时类型)

我们下面就来看看这2种方法

方式1:实例化模板类对象

cpp 复制代码
// ✅ 存储具体实例化的模板类对象
template<typename T>
class MyClass {
    T value;
};

// 使用时必须指定具体类型
std::unordered_map<int, MyClass<int>> map1;       // ✅ 存储int版本
std::unordered_map<int, MyClass<std::string>> map2; // ✅ 存储string版本

方式2:使用多态(运行时类型)

cpp 复制代码
// 基类
class Base {
public:
    virtual ~Base() = default;
    virtual void print() const = 0;
};

// 模板派生类
template<typename T>
class Derived : public Base {
    T value;
public:
    Derived(T v) : value(v) {}
    void print() const override {
        std::cout << value << std::endl;
    }
};

// ✅ 存储基类指针
std::unordered_map<int, std::unique_ptr<Base>> map;
map[1] = std::make_unique<Derived<int>>(42);
map[2] = std::make_unique<Derived<std::string>>("hello");

那么我们这里就是采用了多态的方式来解决这个问题

我们先看一个小例子

例子

cpp 复制代码
#include <iostream>
#include <unordered_map>
#include <memory>

// 1. 基类:能存数据的盒子
class Box {
public:
    virtual ~Box() = default;
    virtual void show() = 0;
};

// 2. 模板派生类:具体装东西的盒子
template<typename T>
class TemplateBox : public Box {
    T content;
public:
    TemplateBox(T t) : content(t) {}
    
    void show() override {
        std::cout << "盒子里有: " << content << std::endl;
    }
};

int main() {
    // 3. 仓库(哈希表)里放各种盒子
    std::unordered_map<int, std::unique_ptr<Box>> warehouse;
    
    // 4. 放不同类型的盒子
    warehouse[1] = std::make_unique<TemplateBox<int>>(100);    // 装整数的盒子
    warehouse[2] = std::make_unique<TemplateBox<double>>(3.14); // 装小数的盒子
    warehouse[3] = std::make_unique<TemplateBox<std::string>>("苹果"); // 装字符串的盒子
    
    // 5. 查看所有盒子(统一接口)
    for (const auto& pair : warehouse) {
        std::cout << "货架" << pair.first << ": ";
        pair.second->show();
    }
    
    return 0;
}

那么按照这种思想,我们很快就能写出下面这个

cpp 复制代码
#pragma once
#include "net.hpp"
#include "message.hpp"

namespace jsonRpc {
    // Callback基类:定义统一的消息处理接口
    class Callback {
        public:
            using ptr = std::shared_ptr<Callback>;
            // 纯虚函数:处理消息的统一接口
            virtual void onMessage(const BaseConnection::ptr &conn, BaseMessage::ptr &msg) = 0;
    };

    // CallbackT模板类:针对特定消息类型的回调处理
    template<typename T>
    class CallbackT : public Callback{
        public:
            using ptr = std::shared_ptr<CallbackT<T>>;

            //我们这里对消息类型进行重定义了
            // 定义特定消息类型的回调函数类型
            //我们之前的回调函数类型using MessageCallback = std::function<void(const BaseConnection::ptr &, BaseMessage::ptr &)>; // 消息接收回调
            //将里面的BaseMessage::ptr修改成了std::shared_ptr<T>
            //这样子我们传递任意类型的子类进来,我们就能动态的根据子类的类型来生成对应的消息处理回调函数类型
            using MessageCallback = std::function<void(const BaseConnection::ptr &conn, std::shared_ptr<T> &msg)>;
            
            // 构造函数:接收特定消息类型的处理函数
            CallbackT(const MessageCallback &handler):_handler(handler) { }
            
            // 实现基类的onMessage接口
            void onMessage(const BaseConnection::ptr &conn, BaseMessage::ptr &msg) override 
            {
                //将父类指针转换成子类指针,这个过程一定会成功,因为子类都是工厂类来创建的
                // 将基类消息指针向下转型为特定消息类型
                auto type_msg = std::dynamic_pointer_cast<T>(msg);
                // 调用用户注册的具体处理函数
                _handler(conn, type_msg);
            }
        private:
            MessageCallback _handler;  // 存储用户注册的具体处理函数------这个回调函数的类型会跟着模板类里面的类型T跟着变化
    };
    
    // Dispatcher类:消息分发器,负责将消息路由到对应的处理器
    class Dispatcher {
        public:
            using ptr = std::shared_ptr<Dispatcher>;
            
            // 注册消息处理器:模板方法,支持不同类型消息
            template<typename T>
            void registerHandler(MType mtype, const typename CallbackT<T>::MessageCallback &handler)//传递进来的是一个消息类型和对应的处理器 
            {
                std::unique_lock<std::mutex> lock(_mutex);  // 加锁保证线程安全
                // 创建特定消息类型T的CallbackT对象
                auto cb = std::make_shared<CallbackT<T>>(handler);//我们构建的是模板类的对象指针
                // 将消息类型与处理器映射关系存入哈希表
                _handlers.insert(std::make_pair(mtype, cb));//注意:父类对象指针可以存储子类对象指针!!!
            }
            
            // 处理接收到的消息:根据消息类型查找并调用对应的处理器
            void onMessage(const BaseConnection::ptr &conn, BaseMessage::ptr &msg) {
                // 找到消息类型对应的业务处理函数,进行调用即可
                std::unique_lock<std::mutex> lock(_mutex);  // 加锁保证线程安全
                auto it = _handlers.find(msg->mtype());      // 根据消息类型查找处理器
                if (it != _handlers.end()) {
                    // 找到对应的处理器,调用其onMessage方法
                    return it->second->onMessage(conn, msg);//注意我们这里传递的是父类指针进去,
                    //而在模板类里面的onMessage函数里面会先将父类指针转换成子类指针,再去调用消息处理回调函数
                }
                // 没有找到指定类型的处理回调--因为客户端和服务端都是我们自己设计的,因此不可能出现这种情况,如果出现了,就代表有第3方
                ELOG("收到未知类型的消息: %d!", (int)msg->mtype());  // 记录错误日志
                conn->shutdown();  // 未知消息类型,关闭连接以确保安全
            }
        private:
            std::mutex _mutex;  // 互斥锁,保证多线程环境下的线程安全
            // 消息处理器映射表:键为消息类型,值为对应的回调处理器
            std::unordered_map<MType, Callback::ptr> _handlers;//注意第二个值存储的是父类指针!!!
    };
}

这个设计的精妙之处在于建立了一条类型信息的隐式传递通道,让模板参数T从用户代码无缝传递到框架的各个层次,而用户只需要在入口处指定一次。整个过程就像接力赛一样,每个环节都自动传递类型信息:

复制代码
用户代码指定类型 → Dispatcher注册 → CallbackT构造 → 回调函数绑定 → 运行时类型转换
    ↓                  ↓              ↓               ↓               ↓
<RpcRequest>       <RpcRequest>   <RpcRequest>    <RpcRequest>    dynamic_cast<RpcRequest>

第一棒:用户入口点

用户在调用registerHandler时,通过尖括号<RpcRequest>显式提供类型信息:

复制代码
dispatcher->registerHandler<RpcRequest>(MT_RPC, myHandler);

这个<RpcRequest>就是整个链条的类型种子。用户在编译时告诉系统:"我要注册一个处理RpcRequest类型消息的处理器"。

第二棒:Dispatcher的模板函数

当编译器看到registerHandler<RpcRequest>时,它实例化了一个专门针对RpcRequest的registerHandler函数:

cpp 复制代码
// 编译器生成的专有版本
void registerHandler<RpcRequest>(MType mtype, 
                                 const typename CallbackT<RpcRequest>::MessageCallback &handler)

关键点:

  • 函数模板实例化:编译器为RpcRequest类型生成了一个独立的函数
  • 类型信息保留:模板参数T(此时是RpcRequest)被"冻结"在这个函数实例中
  • 接口一致性:参数类型CallbackT<RpcRequest>::MessageCallback已经包含了类型信息

第三棒:CallbackT的类型推导

在registerHandler函数内部,创建CallbackT对象时:

cpp 复制代码
auto cb = std::make_shared<CallbackT<RpcRequest>>(handler);

这里发生了类型信息的注入:

  • 类模板实例化:编译器生成CallbackT<RpcRequest>类

  • 内部类型定义:在这个类内部,MessageCallback被定义为:

    cpp 复制代码
    std::function<void(const BaseConnection::ptr&, std::shared_ptr<RpcRequest>&)>
  • 类型绑定:这个定义将用户回调函数永久绑定到RpcRequest类型

第四棒:用户回调函数的强制匹配

由于MessageCallback已经被定义为接收std::shared_ptr<RpcRequest>&,用户的myHandler函数必须匹配这个签名:

cpp 复制代码
// 编译器强制检查
void myHandler(const BaseConnection::ptr&, std::shared_ptr<RpcRequest>&)  // ✅ 匹配
void myHandler(const BaseConnection::ptr&, std::shared_ptr<TopicRequest>&) // ❌ 不匹配

这是类型安全的核心保障:编译器确保用户提供的函数只能处理RpcRequest类型。

第五棒:哈希表存储的类型擦除

当把CallbackT<RpcRequest>对象存入哈希表时:

cpp 复制代码
_handlers.insert(std::make_pair(mtype, cb));

这里发生了一个巧妙的类型转换:

  • cb的类型是CallbackT<RpcRequest>::ptr(指向具体类型的指针)
  • 但哈希表存储的是Callback::ptr(基类指针)
  • C++的多态机制允许这个转换:向上转型
  • 这个转换似乎"丢失"了RpcRequest的类型信息,但实际上:
  • 对象本身仍然是CallbackT<RpcRequest>类型
  • 虚函数表指向CallbackT<RpcRequest>::onMessage
  • 类型信息隐藏在对象中,等待被调用

第六棒:消息分发时的类型恢复

当消息到达,调用处理器时:

cpp 复制代码
it->second->onMessage(conn, msg);  // it->second是Callback::ptr

这里发生了动态多态:

  • 调用虚函数onMessage
  • 根据对象的实际类型(CallbackT<RpcRequest>),调用CallbackT<RpcRequest>::onMessage
  • 类型信息恢复:进入具体类型的处理函数

第七棒:运行时类型转换

在CallbackT<RpcRequest>::onMessage中:

cpp 复制代码
auto type_msg = std::dynamic_pointer_cast<RpcRequest>(msg);

这是链条的终点,也是验证点:

  • dynamic_pointer_cast尝试将BaseMessage转换为RpcRequest
  • 如果转换成功,说明类型链条从头到尾都正确
  • 如果转换失败,说明系统存在不一致(理论上不应该发生)

3.3.测试

server.cpp

cpp 复制代码
#include "../../common/message.hpp"
#include "../../common/net.hpp"
#include "../../common/dispatcher.hpp"

//收到了RPC请求
void onRPCRequest(const jsonRpc::BaseConnection::ptr &conn, jsonRpc::RpcRequest::ptr &msg)
{
    //查看RPC请求里面的东西
    std::cout<<"收到了RPC请求"<<msg->method()<<std::endl;
    //注意method方法是只有RpcRequest类里面才有的
    
    // 创建一个RPC响应消息对象
    // 使用MessageFactory工厂类创建指定类型的消息对象
    auto rpc_rsp = jsonRpc::MessageFactory::create<jsonRpc::RpcResponse>();
    // 设置响应消息的ID为"11111"
    rpc_rsp->setId("11111");
    // 设置消息类型为RPC响应类型
    rpc_rsp->setMType(jsonRpc::MType::RSP_RPC);
    // 设置响应码为成功
    rpc_rsp->setRCode(jsonRpc::RCode::RCODE_OK);
    // 通过连接对象将响应消息发送回客户端
    conn->send(rpc_rsp);
}
//收到了TOPIC请求
void onTopicRequest(const jsonRpc::BaseConnection::ptr &conn, jsonRpc::TopicRequest::ptr &msg)
{

    //直接进行响应
    auto rpc_rsp = jsonRpc::MessageFactory::create<jsonRpc::TopicResponse>();
    // 设置响应消息的ID为"11111"
    rpc_rsp->setId("11111");
    // 设置消息类型为TOPIC响应类型
    rpc_rsp->setMType(jsonRpc::MType::RSP_TOPIC);
    // 设置响应码为成功
    rpc_rsp->setRCode(jsonRpc::RCode::RCODE_OK);
    // 通过连接对象将响应消息发送回客户端
    conn->send(rpc_rsp);
}

// main函数:程序入口点
int main()
{

    auto dispatcher = std::make_shared<jsonRpc::Dispatcher>();
    
    // 关键修改:添加模板参数指定消息类型
    dispatcher->registerHandler<jsonRpc::RpcRequest>(jsonRpc::MType::REQ_RPC, onRPCRequest);  
    dispatcher->registerHandler<jsonRpc::TopicRequest>(jsonRpc::MType::REQ_TOPIC, onTopicRequest); 

    // 创建一个JSON-RPC服务器实例,监听端口9090
    auto server = jsonRpc::ServerFactory::create(9090);

    auto message_cb = std::bind(&jsonRpc::Dispatcher::onMessage, dispatcher.get(), std::placeholders::_1, std::placeholders::_2);

    // 设置服务器的消息处理回调函数为jsonRpc::Dispatcher::onMessage函数
    server->setMessageCallback(message_cb);

    // 启动服务器,开始监听客户端连接和处理请求
    server->start();

    // 程序正常结束,返回0
    return 0;
}

client.cpp

cpp 复制代码
#include "../../common/message.hpp"
#include "../../common/net.hpp"
#include<thread>
#include "../../common/dispatcher.hpp"

void onRPCResponse(const jsonRpc::BaseConnection::ptr &conn, jsonRpc::RpcResponse::ptr &msg)
{
    // 将消息对象序列化为字符串格式
    std::cout<<"收到了RPC响应"<<std::endl;
}
void onTopicResponse(const jsonRpc::BaseConnection::ptr &conn, jsonRpc::TopicResponse::ptr &msg)
{
    // 将消息对象序列化为字符串格式
    std::cout<<"收到了topic响应"<<std::endl;
}

// main函数:程序入口点
int main()
{
    auto dispatcher =std::make_shared<jsonRpc::Dispatcher>();

     // 关键修改:添加模板参数
    dispatcher->registerHandler<jsonRpc::RpcResponse>(jsonRpc::MType::RSP_RPC, onRPCResponse);
    dispatcher->registerHandler<jsonRpc::TopicResponse>(jsonRpc::MType::RSP_TOPIC, onTopicResponse);

    // 创建一个JSON-RPC服务器实例,监听端口9090
    auto client = jsonRpc::ClientFactory::create("127.0.0.1", 9090);
    auto message_cb =std::bind(&jsonRpc::Dispatcher::onMessage,dispatcher.get(),std::placeholders::_1,std::placeholders::_2);

    client->setMessageCallback(message_cb);//消息到达回调函数,消息一到达就会去调用jsonRpc::Dispatcher::onMessage函数,
    //然后onMessage函数会根据消息类型去调用对应的处理函数,例如上面收到了RSP_RPC类型的消息,就会去调用onRPCResponse函数
    client->connect();
    
    // 发送RPC请求
    auto rpc_req = jsonRpc::MessageFactory::create<jsonRpc::RpcRequest>();
    rpc_req->setId("11111");
    rpc_req->setMType(jsonRpc::MType::REQ_RPC);
    rpc_req->setMethod("Add");
    
    Json::Value param;
    param["num1"] = 11;
    param["num2"] = 22;
    rpc_req->setParams(param);
    
    if (client->send(rpc_req)) {
        std::cout << "RPC请求发送成功!" << std::endl;
    } else {
        std::cout << "RPC请求发送失败!" << std::endl;
    }
    
    // 发送TOPIC请求
    auto topic_req = jsonRpc::MessageFactory::create<jsonRpc::TopicRequest>();
    topic_req->setId("22222");
    topic_req->setMType(jsonRpc::MType::REQ_TOPIC);
    if (client->send(topic_req)) {
        std::cout << "TOPIC请求发送成功!" << std::endl;
    } else {
        std::cout << "TOPIC请求发送失败!" << std::endl;
    }
    
    // 等待响应
    std::this_thread::sleep_for(std::chrono::seconds(10));

    client->shutdown();

    // 程序正常结束,返回0
    return 0;
}

非常的完美

相关推荐
独行soc2 小时前
2026年渗透测试面试题总结-10(题目+回答)
android·网络·python·安全·web安全·渗透测试·安全狮
Mcband2 小时前
OpenFeign - 底层原理揭秘:动态代理 + HTTP 客户端如何工作
网络·网络协议·http
amao99882 小时前
MIT-OS2022 lab4 Traps陷阱指令和系统调用
网络
studyForMokey2 小时前
【Android面试】Java & Kotlin语言
android·java·面试
z.q.xiao2 小时前
【镜像模式】WSL如何访问windows内网服务
linux·网络·windows·gitlab·wsl·dns
molaifeng2 小时前
万字长文解析:Redis 8.4 网络 IO 架构深度拆解
网络·redis·架构
鸣弦artha3 小时前
Flutter框架跨平台鸿蒙开发——Drawer抽屉导航组件详解
android·flutter
学烹饪的小胡桃3 小时前
WGCLOUD使用指南 - 如何监控交换机防火墙的数据
运维·服务器·网络
Howrun7773 小时前
Linux网络编程_常见API
linux·运维·网络