[实现Rpc] Dispatcher类的实现 | 开闭原则 | 测试 | 传gitee

news/2025/2/22 13:47:56

目录

程序设计原则

说明

Dispatcher

Callback 类

CallbackT 类

Dispatcher 类

测试

client

server

Debug

⭕参数传递错误 排查方法:

运行

记录:


(1)Dispatcher类的功能:

  • 注册消息的类型。
  • 回调函数映射关系。
  • 提供消息处理接口。

(2)具体实现:

  • 服务端会收到不同类型的请求,客户端会收到不同类型的响应。
  • 因为请求和响应都具有多样性。
  • 因此 在回调函数中,就需要判断消息类型,根据不同类型的消息做出不同的处理。
  • 如果单纯适用if语句做分支处理,是一件非常不好的事情。
程序设计原则
  • 程序设计中需要遵循一个原则:开闭原则 --- 对修改关闭,对扩展开放
  • 当后期维护代码或新增功能时:不去修改以前的代码,而是新增当前需要的代码。
说明
  • Dispatcher模块就是基于开闭原则设计的,目的就是建立消息类型与业务回调函数的映射关系。
  • 如果后期新增功能,不需要修改以前的代码,只需要增加一个映射关系即可。

Dispatcher

//调度员

#pragma once
#include <unordered_map>
#include "net.hpp"
#include "message.hpp"

class Callback
{
    public:
        using ptr=std::shared_ptr<Callback>;
        virtual void onMessage(const BaseConnection::ptr &conn,BaseMessage::ptr &msg)=0;
};

//!通过 再加 一层T,简化了 函数调用
template<typename T>
//要用 public 继承父类
class CallbackT:public Callback
{
    public:
        using ptr=std::shared_ptr<CallbackT>;
        using MessageCallback=std::function<void(const BaseConnection::ptr &conn,std::shared_ptr<T> &msg)>;

        CallbackT(const MessageCallback &handler)
            :_handler(handler)
        {}

        virtual 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;
};

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

        template<typename T>
        //! 通过 typename 强调 传入的模板函数名
        void registerHandler(MType mtype,const typename CallbackT<T>::MessageCallback &handler)
        {
            //注册
            std::unique_lock<std::mutex> lock(_mutex);

            //!! T使得handler 更易调用了
            auto cb=std::make_shared<CallbackT<T>>(handler);
            //将 mytype和cb 解耦合 加入
            _handlers.insert(std::make_pair(mtype,cb));
        }

        void onMessage(const BaseConnection::ptr &conn,BaseMessage::ptr &msg)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto iter=_handlers.find(msg->GetMType());
            //将 传递过来的 消息类型 进行查找
            if(iter!=_handlers.end())
            {
                return iter->second->onMessage(conn,msg);
            }

            //没有找到指定类型的处理回调--因为客户端和服务端都是我们自己设计的,因此不可能出现这种情况
            ELOG("收到 未知类型的消息:%d",msg->GetMType());
            conn->shutdown();
        }

    private:
        std::mutex _mutex;
        std::unordered_map<MType,Callback::ptr> _handlers;
};

0.

这段代码展示了如何使用回调(Callback)模式处理消息分发,具体来说是通过一个Dispatcher类来根据消息类型将消息分派给适当的处理器。下面是对CallbackCallbackT设计的理解:

Callback 类
  • ptr: 定义了指向Callback的智能指针类型,便于管理和传递对象。
  • virtual void onMessage(const BaseConnection::ptr &conn, BaseMessage::ptr &msg) = 0;: 纯虚函数,要求所有子类必须提供自己的实现。
CallbackT 类

CallbackT 是一个模板类,它继承自Callback,并为特定类型的消息提供了具体的处理逻辑。通过这种方式,它可以针对不同类型的BaseMessage提供不同的处理方式。

  • onMessage 函数实现了父类中的纯虚函数,首先尝试将传入的消息转换为指定类型,然后调用保存的处理函数。
Dispatcher 类

Dispatcher 负责管理不同类型消息的处理器,并根据消息类型调度到对应的处理器上。

  • registerHandler 方法允许注册特定类型的消息处理器。它接收消息类型和相应的处理器(CallbackT<T>::MessageCallback),创建一个CallbackT<T>实例,并将其添加到处理器映射表中。
  • onMessage 方法接收连接和消息,查找对应的消息类型处理器,并调用其onMessage方法。如果找不到合适的处理器,则记录错误日志并关闭连接。

这种设计使得系统能够灵活地处理不同种类的消息,同时保持代码的清晰和模块化。

每个消息类型都有其专门的处理器,这样可以更容易地扩展新的消息类型及其处理逻辑。

1.typename

前文回顾:6.C++模板(超全)

2.

public 忘了,报错像做梦一样

3.

就像 我们架构 当中的 一层不行,那就再套一层


测试

client
#include "../../common/net.hpp"
#include "../../common/message.hpp"
#include "../../common/dispatcher.hpp"
#include <thread>
#include <unistd.h>

void onRpcRespond(const BaseConnection::ptr &conn,RpcResponse::ptr &msg)
{
    std::cout<<"收到RPC响应";
    std::string body=msg->serialize();//序列化
    std::cout<<body<<std::endl;
}

void onTopicRespond(const BaseConnection::ptr &conn,TopicResponse::ptr &msg)
{
    std::cout<<"收到Topic响应";
    std::string body=msg->serialize();
    std::cout<<body<<std::endl;
}

int main()
{
    auto dispatcher=std::make_shared<Dispatcher>();
    dispatcher->registerHandler<RpcResponse>(MType::RSP_RPC,onRpcRespond);
    dispatcher->registerHandler<TopicResponse>(MType::REQ_TOPIC,onTopicRespond);

    auto client=ClientFactory::create("127.0.0.1",8080);
    auto message_cb=std::bind(&Dispatcher::onMessage,dispatcher.get(),std::placeholders::_1,std::placeholders::_2);
    client->setMessageCallback(message_cb);
    client->connect();

    auto rpc_req=MessageFactory::create<RpcRequest>();
    rpc_req->setMethod("Add");
    rpc_req->SetMType(MType::REQ_RPC);
    rpc_req->SetId("1111111");

    Json::Value param;
    param["num1"]=11;
    param["num2"]=22;
    rpc_req->setParams(param);
    //调用 接口
    client->send(rpc_req);

    sleep(5);
    client->shutdown();

    return 0;
}

note

#include <unistd.h> 是一个在C和C++编程语言中使用的预处理指令,用于包含POSIX操作系统API的声明。这个头文件定义了各种与系统调用相关的函数、类型和宏,它们主要用于执行低级别的操作系统功能。

以下是一些由unistd.h提供的典型功能和函数:

  • 进程控制:包括创建新进程(fork())、执行程序(exec系列函数)、获取进程ID(getpid())等。
  • 文件访问:虽然大多数文件操作可以通过stdio.h中的函数完成,但一些底层的操作如读写特定字节(read(), write())、更改文件当前读写位置(lseek())等需要使用unistd.h中定义的函数。
  • 文件和目录操作:例如获取工作目录(getcwd())、改变工作目录(chdir())等。
  • 设备I/O控制:提供了对设备进行输入输出控制的功能。
  • 符号链接操作:提供创建和读取符号链接的功能。
  • 用户、组和其他系统信息:允许获取和设置用户ID、组ID以及查询系统信息。
  • 睡眠:可以让当前进程暂停执行一段时间(sleep())。
  • 多线程和同步:尽管更高级别的接口可能通过其他库提供,但是这里也可以找到一些基本的同步原语。

简单来说,当你在编写需要直接与操作系统交互的代码时,比如 处理文件、管理进程或执行网络通信等任务时,可能会需要用到unistd.h中定义的函数。它为开发者提供了访问操作系统服务的一种方式,是Unix-like系统编程的重要组成部分

请注意,这个头文件主要是针对类Unix系统设计的,在Windows平台上并不直接适用,不过可以通过兼容层(如Cygwin)或者对应的Windows API来实现类似的功能。


server
#include "../../common/net.hpp"
#include "../../common/message.hpp"
#include "../../common/dispatcher.hpp"
#include <thread>

void onRpcRequest(const BaseConnection::ptr &conn,RpcRequest::ptr &msg)
{
    std::cout<<"收到RPC请求"<<msg->method();
    std::string body=msg->serialize();
    std::cout<<body<<std::endl;

    auto rpc_req=MessageFactory::create<RpcResponse>();
    rpc_req->SetId("111111");
    rpc_req->SetMType(MType::RSP_RPC);
    rpc_req->setRCode(RCode::RCODE_OK);
    rpc_req->setResult(33);
    conn->send(rpc_req);
}

void onTopicRequest(const BaseConnection::ptr &conn, TopicRequest::ptr &msg)
{
    //序列化 信息检查
    std::cout << "收到Topic请求";
    std::string body = msg->serialize();
    std::cout << body << std::endl;

    //处理 回应消息
    auto rsp_req = MessageFactory::create<TopicResponse>();
    rsp_req->SetMType(MType::RSP_TOPIC);
    rsp_req->setRCode(RCode::RCODE_OK);
    rsp_req->SetId("22222");
    conn->send(rsp_req);
}

int main()
{
    auto dispatcher=std::make_shared<Dispatcher>();
    dispatcher->registerHandler<RpcRequest>(MType::REQ_RPC,onRpcRequest);
    //使用 调度器 处理
    dispatcher->registerHandler<TopicRequest>(MType::REQ_TOPIC,onTopicRequest);

    auto server=ServerFactory::create(8080);
    auto message_cb=std::bind(&Dispatcher::onMessage,dispatcher.get(),std::placeholders::_1,std::placeholders::_2);
    server->setMessageCallback(message_cb);
    server->start();

    return 0;
}

Debug

1.

类型转化,ELOG("收到 未知类型的消息:%d",(int)msg->GetMType());

2.

⭕参数传递错误 排查方法:

查找所有引用,检查 参数顺序即可~


运行

客户端

服务端

记录:

0.

在GDB(GNU调试器)中,btbacktrace 命令的缩写。使用这个命令可以显示当前执行上下文的回溯跟踪,即程序到达当前位置所经过的所有函数调用列表。这对于了解程序崩溃时的状态或者调试运行时错误非常有用。

当遇到程序崩溃(例如段错误)、死循环或者其他异常情况时,可以通过以下步骤使用 bt 命令来获取更多的信息:

  1. 启动GDB并加载你的程序:你可以直接在GDB中启动你的程序,也可以附加到一个已经运行的进程上。
  2. 让程序运行:通过 run 命令启动程序,或让它继续执行直到遇到断点或异常。
  3. 当程序暂停时(如遇到崩溃或达到断点),输入 bt:这将打印出从开始到现在所有的函数调用栈帧,包括每个调用的参数值和位置信息。这对于确定问题发生的根源非常有帮助。

例如,执行 bt 后你可能会看到类似下面的信息:

#0  0x00007ffff7a6c9f0 in ?? () from /lib64/libc.so.6
#1  0x00000000004005d8 in main () at my_program.c:10

这里的每一行代表了调用栈中的一个层次,通常最上面的是最近被调用的函数(可能是导致崩溃的地方),而越往下则是更早之前调用的函数。通过分析这些信息,你可以更好地理解程序执行流程,并找出潜在的问题所在。

此外,GDB还提供了其他一些与 bt 相关的命令,比如 updown,用于在调用栈帧之间移动,从而检查不同层次上的局部变量和表达式值。


1.

之前忘了,后面用 n_total_len 作范围,Debug 才注意到

  • h_total_len:用于本地计算和逻辑处理。在构造最终要发送的数据包时,你可能需要知道未经过任何转换的原始长度信息来进行一些内部操作或验证。
  • n_total_len:准备实际发送到网络上的数据。由于网络协议规定使用大端字节序(网络字节序)来传输数据,所以需要将主机上的数值(可能是小端字节序或其他格式)转换为网络字节序后再进行传输。

2.

数据量不足 的理解

网络查询:

在Linux和类Unix系统中,使用netstat -anptu | grep [port]命令可以查看与特定端口相关的网络连接信息。

  • netstat:这是一个非常实用的网络工具,用于展示网络连接、路由表、接口统计等网络相关信息。
  • -a:该选项表示显示所有连接和监听端口。它涵盖了正在监听的套接字以及处于连接状态的所有套接字等信息。
  • -n:此选项指示以数字形式显示地址和端口号,不尝试将它们解析为域名和服务名。这不仅能加快命令执行速度,还可以避免由于域名解析可能引起的问题。
  • -p:显示与网络连接关联的进程ID(PID)和进程名称。这有助于了解是哪个进程建立了相应的网络连接。
  • -t:仅显示TCP协议相关的连接信息。
  • -u:仅显示UDP协议相关的连接信息。
  • | (管道符):用于将一个命令的输出作为另一个命令的输入。在这里,它是将netstat -anptu命令的输出传递给grep命令作为其输入。
  • grep [port]grep是一个用于搜索文件中符合模式的行的工具。在此上下文中,它被用来从netstat的输出中筛选出包含指定端口信息的行。这里的[port]应当替换为具体的端口号或端口范围等,以便准确地过滤出所需的信息。

例如,如果想要查看与80端口相关的网络连接信息,可以使用如下命令:

netstat -anptu | grep 80

这条命令将会列出所有与80端口相关的TCP和UDP连接,以及对应的进程信息。通过这种方式,用户能够方便地监控和管理特定端口上的网络活动。


今天对 gitee 仓库进行了一个整理:

Q:把Linux下写好的部分 上传到gitee

A:

最后 git push 即可


http://www.niftyadmin.cn/n/5862342.html

相关文章

二叉树层序遍历的三种情况(总结)

这道题就是一个比较简单的层序遍历&#xff0c;只需要利用队列存放二叉树结点&#xff0c;队列的size代表每层的节点数也就是平均值的除数&#xff0c;利用一个结果数组记录每层平均值&#xff0c;最后返回。 需要注意的是&#xff0c;平均值定义成double类型。 代码如下&…

通信系统中物理层与网络层联系与区别

在通信系统中&#xff0c;物理层和网络层是OSI&#xff08;开放系统互连&#xff09;模型中的两个重要层次&#xff0c;分别位于协议栈的最底层和第三层。它们在功能、职责和实现方式上有显著的区别&#xff0c;但同时也在某些方面存在联系。以下是物理层与网络层的联系与区别的…

进程(2)

1.进程的消亡 &#xff08;1&#xff09;进程的退出 &#xff08;2&#xff09;进程资源的回收 僵尸进程&#xff1a;进程已经结束&#xff0c;但是未被其父进程回收。 如何避免僵尸进程&#xff1a; 2.函数 &#xff08;1&#xff09;void exit(int status) (2)pid_t wait…

记录:Docker 安装记录

今天在安装 ollama 时发现无法指定安装目录&#xff0c;而且它的命令行反馈内容很像 docker &#xff0c;而且它下载的模型也是放在 C 盘&#xff0c;那么如果我 C 盘空间不足&#xff0c;就装不了 deepseek-r1:70b &#xff0c;于是想起来之前安装 Docker 的时候也遇到过类似问…

AI汽车新风向:「死磕」AI底盘,引爆线控底盘新增长拐点

2025开年&#xff0c;DeepSeek火爆出圈&#xff0c;包括吉利、东风汽车、上汽、广汽、长城、长安、比亚迪等车企相继官宣接入&#xff0c;掀起了“AI定义汽车”浪潮。 而这股最火的AI汽车热潮&#xff0c;除了深度赋能智能座舱、智能驾驶等AI竞争更白热化的细分场景&#xff0…

区块链中的递归长度前缀(RLP)序列化详解

文章目录 1. 什么是RLP序列化&#xff1f;2. RLP的设计目标与优势3. RLP处理的数据类型4. RLP编码规则详解字符串的编码规则列表的编码规则 5. RLP解码原理6. RLP在以太坊中的应用场景7. 编码示例分析8. 总结 1. 什么是RLP序列化&#xff1f; 递归长度前缀&#xff08;RLP&…

解决 Plugin ‘org.springframework.boot:spring-boot-maven-plugin:‘ not found

idea显示如下报错 加上版本号 2.3.4.RELEASE 刷新依赖&#xff0c;报错即可消除

网络运维学习笔记 017HCIA-Datacom综合实验01

文章目录 综合实验1实验需求总部特性 分支8分支9 配置一、 基本配置&#xff08;IP二层VLAN链路聚合&#xff09;ACC_SWSW-S1SW-S2SW-Ser1SW-CoreSW8SW9DHCPISPGW 二、 单臂路由GW 三、 vlanifSW8SW9 四、 OSPFSW8SW9GW 五、 DHCPDHCPGW 六、 NAT缺省路由GW 七、 HTTPGW 综合实…