基于 C、JsonCpp、muduo 网络库实现一个简单、易用的 RPC 通信框架涉及基础的网络开发知识它实现了同步调用、异步 callback 调用、异步 futrue 调用、服务注册 / 发现服务上线 / 下线以及发布订阅等功能设计。一、JsonRpc框架项目准备1.1 Rpc介绍RPC 是 Remote Procedure Call 的缩写翻译成中文是远程过程调用是一种计算机通信协议它可以让程序像调用本地函数 / 方法一样调用另一台计算机上的函数 / 方法不需要开发者手动处理底层的网络通信细节。工作流程需实现的功能框架RPC与HTTP 接口的区别对比维度RPCHTTP 接口调用方式像调用本地方法更简洁需要手动构造请求、处理响应通常使用 HTTP 的 GET/POST 等方法性能通常使用更高效的序列化协议如Protobuf和传输协议如 TCP性能更高基于 HTTP 协议序列化通常用JSON性能相对较低适用场景多用于微服务架构内部的服务之间通信追求性能和开发效率多用于对外提供的接口比如给前端、第三方系统调用兼容性更强1.2选择合适的实现方案问题1RPC 的两种实现方案一client 和 server 继承公共接口1.上图中根据IDL接口描述语言定义公共接口2.编写代码生成器根据 IDL 语言生成相关的 C、Java 代码3. 然后我们的客户端和服务器程序共同向上继承公共接口即可4. 比如我们常用的Protobuf、json可以定义 IDL 接口并生成 RPC 相关的代码缺点使用Protobuf因为生成一部分代码protoc编译器会自动生成 C 代码消息类、序列化 / 反序列化方法、RPC 服务桩代码方便但不了解其生成的代码细节所以对理解不够友好如果是json定义 IDL 语言需要自己编写代码生成器难度大暂不考虑这种方案二实现一个通用的远程调用接口call使用者仅需传入函数名、参数等信息即可完成 RPC 接口调用。无需让客户端 / 服务端继承公共接口、也无需依赖 IDL接口描述语言和代码生成器降低了使用门槛更适配 “简单、易用” 的框架定位。这类方案的典型实现逻辑通常包括参数封装将函数名、参数序列化为统一格式如 JSON通过call接口传输服务端路由服务端接收请求后根据函数名找到对应的实现方法执行后返回结果通用性接口call是统一入口可适配同步、异步等不同调用方式兼容性较强。问题2将参数和返回值映射到对应的 RPC 接口上网络传输的参数和返回值怎么映射到对应的 RPC 接口上一使用 protobuf 的反射机制需深入掌握 Protobuf 的 C 反射 API理解 “消息描述符 - 字段映射 - 服务桩” 的底层逻辑对不懂 Protobuf 内核的开发者不友好。需手动编写反射逻辑绑定 “接口名 - 服务方法”反射调用需处理大量类型校验如字段类型不匹配、必填字段缺失增加调试成本。二使用 C 模板、类型萃取、函数萃取等机制需精通 C模板元编程TMP、SFINAE、类型萃取、函数签名萃取、需理解 “编译期类型推导” 与 “运行时网络传输” 的衔接逻辑需手写大量模板工具类模板萃取代码通常晦涩难懂如嵌套的typename、decltype、模板特化调试困难。三使用 JSON 类型设计好参数和返回值协议即可仅需理解 JSON 的基础结构对象、数组、字符串 / 数字无需掌握任何 C 高级特性或 Protobuf 内核只需按约定的 JSON 协议封装参数、解析返回值像使用普通接口一样调用 RPC完全屏蔽底层映射逻辑接口参数 / 返回值结构变更时仅需修改 JSON 协议解析逻辑无需重构框架核心代码客户端可基于任意语言的 JSON 库如 Java 的 Gson、Go 的 encoding/json调用你的 RPC 接口轻松实现跨语言通信与 muduo 网络库无缝集成仅需将 JSON 字符串作为网络传输载体muduo 负责字节流收发无需适配复杂的类型映射可无缝支撑同步 / 异步调用服务注册 / 发现、发布订阅等功能均可通过扩展 JSON 协议字段如服务元信息、订阅 topic实现扩展成本低调试时无需借助 Protobuf 解析工具。前两种技术难度和学习成本较高我们使用第三种方式。问题3网络传输怎么做一使用原生 socket需手动处理 TCP连接建立、数据收发、粘包 / 拆包、超时重传、异常断开等网络细节需从零实现异步 IO如 select/poll/epoll、线程池等网络基础组件原生 socket代码耦合网络逻辑与业务逻辑后续扩展难需深入理解网络协议栈、IO 多路复用等底层知识。二使用Boost asio 库的异步通信需引入 Boost 库体积大项目编译 / 部署复杂度增加Boost asio 的异步模型回调 / 协程设计复杂Boost asio 的接口较为冗余代码可读性相对较差与轻量型 RPC 框架的定位不匹配。三使用muduo 库 学习开发成本较低是专为 C 服务端设计的轻量网络库与 “简单 RPC 框架”定位完全匹配接口设计简洁封装了 epoll、线程池等底层细节上手极快开箱即用的异步 IO 模型无需手动实现网络基础组件内置Buffer类自动处理粘包 / 拆包网络层与业务层解耦后续扩展如 SSL、UDP成本低。基于 epoll 的高效 IO 复用性能接近原生 socket同时避免了原生开发的复杂度。muduo 是当前场景下的最优选择既匹配技术栈、降低学习成本又能高效实现网络传输完全支撑 “简单、易用” 的 RPC 框架设计目标。1.3 环境搭建Ubuntu22.04CentOS主要使用的是Ubuntu示例如下ceontos7由于老化等原因不好连接VScodeUbuntuCentOS注意事项1如果需要使用VSCode与虚拟机进行网络连接可用如下操作获得虚拟机IP地址后再与VSCode相连接也可跳过此步骤不用VSCode后续直接在虚拟机环境中操作1安装Wget2更新软件源换成国内的否则下载速度会慢注意事项2上述命令是CentOS 7 系统中替换 yum 软件源为阿里云镜像源的操作3安装scl与epel软件源阿里可能没有的就从这两处下载4安装lrzsz传输工具用于从虚拟机到主机传输文件5安装高版本的编译器注意事项36make/gdb/git的安装常规开发工具的安装同上述的Ubuntumake是编译构建工具用于执行Makefile脚本自动化编译、链接等项目构建流程是 C/C 等项目开发的基础工具之一。gdb是程序调试工具可用于断点调试、查看内存 / 变量、跟踪程序执行流程等是定位 C/C 程序 bug 的核心工具。git用于代码版本管理、协同开发可实现代码的提交、分支管理、远程仓库同步如 GitHub/GitLab等功能。7JsonCpp库的安装注意事项4序列化与反序列化的需要jsoncpp-devel包含 JsonCpp 的头文件和编译依赖是 C 项目中解析 / 生成 JSON 数据的常用库安装后可在代码中通过#include json/json.h使用。/usr/include/jsoncpp/json/是 JsonCpp 头文件的默认安装目录执行ls命令可验证头文件如assertions.h、autolink.h、config.h等是否成功安装确保后续编译项目时能找到 JsonCpp 的依赖文件。8安装cmakeCMake 是跨平台的项目构建工具用于生成 Makefile、Visual Studio 工程等构建文件适用于多平台、大型C/C 项目的编译管理是替代手动编写 Makefile的常用工具。9安装MuduoBoost库muduo 是一款基于 Reactor 模式的C 网络库专注于Linux 服务端高性能网络通信开发常被用于构建 RPC 框架、服务器程序等场景与要搭建的 RPC 框架技术栈高度匹配。克隆源码后通常需要编译、安装才能在项目中链接使用其网络通信能力。Reactor 模式是一种事件驱动的并发编程模型核心是 “通过一个事件循环监听事件再分发到对应的处理逻辑”boost安装 Boost 库的运行时组件提供程序运行时依赖的基础功能boost-devel安装 Boost 库的开发包包含头文件、静态库等编译依赖用于基于 Boost 开发 C 程序。Boost 是 C 的通用库集合提供了智能指针、线程、网络、序列化等丰富功能常被用于增强 C 程序的开发效率与功能完整性 本次的RPC 框架需用到线程同步、数据序列化等能力会依赖 Boost用git获取muduo方案可能会比较慢用wget可能下不完整也可用下方gitee中的压缩包下载解压获取后记得运行下述命令编译、安装./build.sh仅编译 muduo 库不安装、生成静态库 / 共享库文件但仅保留在源码目录的编译产物目录中不拷贝到系统全局路径执行./build.sh时脚本会在muduo目录外创建build目录存放编译过程的临时文件、最终的库产物如libmuduo_base.a。。./build.sh install编译并安装 muduo 库全局可用将 muduo 的头文件、库文件拷贝到系统全局路径让所有项目可直接链接使用。1.4 JsonJsonCpp介绍可嵌套使用如下其主要通过以下3个类实现序列化与反序列化Json::ValueJSON 数据的核心载体类作用表示任意 JSON 数据对象、数组、字符串、数字等是 Jsoncpp 中最基础的类用于存储 JSON 结构的内容。使用场景序列化时通过Json::Value构建 JSON 对象 / 数组再转换为字符串反序列化时解析 JSON 字符串后得到的结果会存储在Json::Value中再从中提取数据。Json::Value类的核心成员函数说明涵盖了 JSON 数据的赋值、操作、类型转换等功能上述接口是在 RPC 框架中封装请求参数、解析响应数据的核心工具 ——通过operator[]构建 JSON 对象通过append处理数组再通过asXXX()将 JSON 值转为业务代码所需的 C 类型完成数据的序列化与反序列化。Json::WriterJSON 序列化类作用将Json::Value对象转换为 JSON 格式的字符串即序列化。这些类是 Jsoncpp 提供的更灵活的序列化接口可通过StreamWriterBuilder配置序列化格式如缩进、空格更适配复杂场景下的 RPC 数据封装。Json::ReaderJSON 反序列化类作用将 JSON 格式的字符串解析为Json::Value对象即反序列化。使用场景接收网络传输的 JSON 字符串如你的 RPC 框架的请求参数后通过Json::Reader解析为Json::Value再提取其中的字段值。序列化与反序列案例1.5Muduo网络库介绍muduo 是一款基于 Reactor 模式的C 网络库专注于Linux 服务端高性能网络通信开发有一个多路转接模型进行 socket 事件监控触发 IO 事件后进行 IO 处理的这种通信处理模型就叫做Reactor 模型,核心是“通过一个事件循环监听事件再分发到对应的处理逻辑”.核心组件(类与函数)Muduo 网络库基于 Reactor 模型的核心组件承担 TCP 通信中的不同职责EventLoopTcpServerTcpClientTcpConnectionBuffer4.Buffer类数据缓冲类作用解决 TCP“粘包 / 拆包” 问题提供安全的字节流读写接口基于 “可读 / 可写”缓冲区的设计。函数作用size_t readableBytes()获取缓冲区中可读取的数据长度用于判断是否有数据待处理。const char* peek()获取缓冲区中数据的起始地址仅读取、不修改缓冲区用于临时查看数据。int32_t peekInt32() const从缓冲区读取 4 字节数据转换为网络字节序→主机字节序的整数数据不删除仅 “预览”。void retrieveInt32()将缓冲区的读取位置后移 4 字节本质是 “逻辑删除” 起始 4 字节数据。int32_t readInt32()组合peekInt32()和retrieveInt32()读取 4 字节整数并删除对应数据。string retrieveAllAsString()取出缓冲区中所有数据转为 string并清空缓冲区。string retrieveAsString(size_t len)取出缓冲区中指定长度len的数据转为 string并删除对应数据。CountDownLatch核心是 “你定义逻辑库决定调用时机”是异步编程的核心机制使用案例服务器端客户端MakeFile文件补充说明std::bind是 “适配器”让你的成员函数能适配 Muduo 库的回调接口实现 “你定义逻辑库触发调用” 的异步回调机制。回调注册函数想调用成员函数调用前要看参数格式对不对发现缺少隐式参数thisbind可以绑定this使得参数格式正确所以要用bind绑定。1.6 异步操作future介绍与async搭配案例对比运行效果先输出into add!异步任务启动1 秒后输出分隔线和结果33。运行效果先输出分隔线再输出into add!get()触发任务执行最后输出结果33。与packaged_task配合案例与promise搭配使用案例实际使用时可能会需要线程池三种方式的对比实际应用时的注意事项二、 项目设计2.1项目预期功能功能结构图如下RPC远程调用服务注册与发现/上下线通知消息的发布与订阅2.2服务端模块设计1. 网络通信NetWork该模块复杂庞大主要依据Muduo库搭建实现底层的网络通信功能。2. 应用层协议通信ProtocolLV格式3.Dispatcher:流程补充本次项目主要有3种消息类型,分别对应后面的RpcRouter模块/Publish-Subscribe模块/Registry-Discovery模块4.RpcRouter模块该模块包含一个存储 “方法名 - 服务描述” 的映射的哈希表其中服务描述包含回调函数、方法名称、参数字段格式、参数校验接口等进行检验。5.Publish-Subscribe6. Registry-Discovery补充之所以在服务端设计该模块是为了让每个服务器都有能力变为注册中心以防一个注册中心坏掉而造成服务瘫痪。网络请求与回应格式如下7.serve上述6大模块的整合2.3 客户端模块设计Requestor 对客户端每一条请求进行管理补充注意其有同步、异步、回调3种处理方式RpcCaller模块也对应要实现3种RpcCaller模块2.4 框架设计抽象出的类三、 项目实现展示核心部分完整源码会存在gitee3.1常用零碎功能类设计(detail.hpp)除上述提及的类还有些在很多其他项目也会用到的通用类的设计日志宏/JSON/UUID日志用于记录、输出项目信息JSON序列化、UUID通用唯一识别码一种用于在分布式系统中唯一标识信息的数字表示形式为32 个十六进制数字以连字符分隔为 5 段格式为8-4-4-4-12共 36 个字符在这里采用生成 8 字节随机数字加上 8字节序号共 16 字节数组生成 128 位 16 进制字符即32个16进制数的组合形式来确保全局唯一的同时能够根据序号来分辨数据 。注意1字节8Bit通常4Bit表示一个16进制即1字节2个16进制16字节32个3.2 项目消息类型字段定义(fields.hpp)该文件是 RPC 框架的公共定义头文件提供了框架通用的常量、枚举和工具函数是框架各模块间通信的“协议约定”。大量使用强类型枚举提升代码安全性使用宏定义统一常量使用无序映射实现高效错误查询体现了良好的 C 编码规范。功能上覆盖了RPC 通信的消息类型、状态码、请求类型、主题操作、服务操作等核心维度为后续的消息序列化、服务治理、主题通信提供了基础支撑。包含以下6个类型的定义3.3 抽象层设计(abstract.hpp)用虚函数、抽象类为后面的具像层要具体实现的功能提供接口函数模块间关系如下3.4 消息抽象的实现message.hpp继承上述的BaseMessage抽象类实现对消息对象处理的具体功能解决了 JSON 请求与响应消息/RPC请求与响应消息/Topic请求与响应消息/Service请求与响应消息的编解码、合法性校验等检测并用工厂模式将消息对象的创建逻辑集中管理。3.5 网络相关抽象的实现net.hpp依据Muduo库实现了缓冲区、消息处理协议连接服务器客户端所有具体实现类MuduoBuffer、LVProtocol 等均通过工厂类创建降低耦合便于扩展。3.6 消息分发的实现消息回调处理与消息分发核心模块负责注册不同类型消息的处理函数并在收到消息时完成精准分发与类型转换3.7服务端RpcRouter模块RPC远程过程调用服务端的核心路由与服务管理模块主要实现了 RPC 方法的注册、查询、参数校验、业务调度及响应反馈等功能。补充3.8客户端Requestor模块是 RPC客户端的核心请求管理组件通过 rid 消息的id关联请求与响应支持三种请求模式保证线程安全和内存安全。补充3.9客户端RpcCaller模块客户端的RPC请求模块发送RPC请求。补充promis是函数内的局部变量出了函数就失效用智能指针管理起来可保证出了函数后想用其时还能有效。3.10服务注册与发现服务端实现
阅读完成 · 觉得有帮助?