json-rpc框架的总结
-
项目架构概述
- 整体采用分层设计,包括协议层、网络层、业务层等。
- 基于muduo库实现底层网络通信并采用事件驱动模型
- 支持同步和异步调用模式
-
项目分三层:
- 抽象层
- BaseMessage(基于Json)
- BaseServer(基于moduo网络库)
- BaseClient(基于moduo网络库)
- BaseConnection(基于moduo网络库)
- BaseProtocol(基于LV风格)
- 实现层
- dispatcher
- JsonMessage
- MuduoServer
- MuduoClient
- MuduoConnection
- LVProtocol
- 业务层
- 用户直接访问
- 抽象层
-
核心组件:
- 通信层:采用BaseConnection、muduoConnection封装底层网络连接。
- 协议层:基于JSON的消息序列化和反序列化以及采用(--length--mtype--idlength--id--body--)进行对一条消息的完整描述。
- 消息分发:Dispatcher模块实现对消息的路由和分发处理。
- RPC调用:RpcClient(联合RpcCaller、Requestor)和RpcServer(联合RpcRouter)实现远程方法的调用。
- 服务注册与发现:RegisterServer(注册中心)、RegisterClient(注册客户端)、DiscoveryClient(发现客户端)对本功能进行实现。
- 发布订阅:TopicServer(联合TopicManager(作为服务端对主题对象的管理))和TopicClient对本功能进行实现。
-
消息定义
- BaseMessage
Mtype _mtype//表示是什么类型的请求/响应
string uuid(32个16进制数字字符组成,以连字号分为五段,形式为8-4-4-4-12,
如550e8400-e29b-41d4-a716-446655440000。对于uuid的生成,采用8个随机数字+8字节的序号、一共16字节即32个16进制数的组合形式生成确保其唯一性)//保证请求和响应一一对应- JsonMessage
这里先统一用Json对消息进行封装- JsonRequest
- RpcRequestMessage
std::string _method
Json::Value _params
std::string _body(存放的是LV协议中的body部分) - ServiceRequestMesage
std::string _servicemethod//注册/请求方法名称
宏_servicemtype//标注当前是服务注册还是服务发现
pair<std::string, int>_host//若是注册请求,则要带上主机信息 - TopicRequestMessage
宏_topicmtype//为了区分当前请求是消息推送//订阅/取消订阅/创建/删除主题
string _message//若是消息推送则要带上推送的消息
- RpcRequestMessage
- JsonResponse
Rcode _rcode- RpcResponseMessage
- ServiceReponseMessage
vector<pair<string, int>>进行服务发现后带回来的所有主机信息 - TopicResponseMessage
- JsonRequest
- JsonMessage
- BaseMessage
-
功能支持:
-
RPC远程调用:本地凭借函数头和参数可调用远端服务器的指定方法。
-
服务注册与发现:服务注册端在注册中心注册服务后,服务发现端在发现服务后可以对方法进行调用。
-
消息发布订阅:消息发送端向服务端的指定主题发送数据,服务端对各个主题收到的消息及进行处理(如将消息转发给本主题的其它的订阅者)。
-
-
功能实现:
-
RPC远程调用
-
消息内部成员
-
请求消息的内部成员(RpcRequestMessage)
- 方法名称(_method)
- 方法参数(_params(Json::Value类型(双方要提前约定好参数类型以便对于参数的提取)))
-
响应消息的内部成员(RpcResponseMessage)
- 响应状态码(_rcode(考虑设计为宏))
- 请求执行结果(_result(具体是什么类型、需要看请求标注返回值是什么类型、进而对执行结果(_result)的反序列化进行特殊形式的提取))
-
-
核心组件:
RpcClient 是客户端调用的入口;RpcServer 负责接收和处理客户端请求;RpcResponse 封装了响应结果(包含返回值和状态码);RpcRequest 定义了请求格式(包括方法名和参数);Dispatcher 模块负责消息分发;Requestor 用于存储已发送但未响应的请求及其回调函数;RpcCaller 处理请求发送和响应接收;RpcRouter 作为服务端路由,管理注册的服务方法并处理请求。
-
RPC调用流程:
-
客户端发起调用:
- 通过
Call方法发起远程请求 - 传入方法名、参数及对应的回调函数
- 通过
-
请求处理阶段:
- RpcCaller将消息ID、方法名、参数等信息封装到RpcRequest中
- 将RpcRequest发送至服务端并等待响应
-
服务端处理流程:
- 由muduo::net::Buffer接收数据
- LVProtocol验证请求消息完整性
- 通过预设回调函数将数据分发至dispatcher模块
- dispatcher根据消息类型路由到RpcRouter
- RpcRouter根据方法名定位对应服务方法并执行
- 生成RpcResponse作为响应消息返回客户端
-
客户端响应处理:
- muduo::net::Buffer接收响应数据
- LVProtocol验证响应完整性
- dispatcher将响应路由至Requestor::OnResponse
- Requestor根据消息ID匹配原始请求
- 调用对应的响应处理方法(回调或future返回结果)
- RpcCaller处理最终结果并返回给调用者
-
-
-
-
服务注册与发现
-
消息内部成员
-
服务请求(ServiceRequestMessage)
- 辨别当前请求是服务注册还是服务发现,需要宏_servicemtype
- 注册方法/请求方法名称(_servicemethod)
- 若是注册请求,则要带上主机地址(address(pair<std::string, int>)类型的_host)
-
服务响应(ServiceReponseMessage)
- 响应状态码(_rcode(宏))
- 进行服务发现后带回来的所有主机信息(_result(Json::Value))
-
-
核心组件:
- 服务中心组件RegistryServer:提供核心服务功能
- 服务注册接口:供RegistryClient使用
- 服务发现接口:供DiscoveryClient使用
- 客户端组件:
- RegistryClient:服务注册客户端
- DiscoveryClient:服务发现客户端
- 服务管理组件:
- Provider:封装服务注册信息(包含服务名称、服务提供者列表)
- ProviderManager:管理服务注册信息
- 发现管理组件:
- Discoverer:封装服务发现信息
- DiscovererManager:管理服务发现信息
- 统一管理组件:
- PDManager:整合管理ProviderManager和DiscovererManager
- 通信描述组件:
- ServiceRequest:描述服务请求信息
- ServiceResponse:描述服务响应信息
- 服务中心组件RegistryServer:提供核心服务功能
-
服务注册流程:
- 服务提供方初始化时创建RegistryClient实例(负责与注册中心通信)
- 服务提供方调用RegisterMethod时,同步触发RegistryClient::RegisterMethod向注册中心发起注册请求
- 注册中心接收请求后:
- Dispatcher将请求路由至PDmanager::OnServiceResponse
- 建立服务方法与主机信息的映射关系并存储
- 向所有订阅该服务的发现者推送服务上线通知
- 最终生成ServiceResponse消息返回注册端,确认服务注册成功
-
服务发现流程:

- 创建DiscoveryClient实例,该实例负责与注册中心建立通信
- 当RpcClient需要调用远程服务时:
- 首先检查本地是否缓存了该服务的提供者信息
- 如未缓存,则通过DiscoveryClient的ServiceDiscovery方法发起服务发现请求
- 当RpcClient需要调用远程服务时:
- 服务中心处理:
- 接收请求数据
- 通过dispatcher将请求分发至PDmanager::OnServiceResponse函数
- PDmanager::OnServiceResponse处理逻辑:
- 识别为服务发现请求
- 查询该服务的可用主机信息
- 将查询结果作为响应返回客户端
- 客户端处理:
- 接收响应后建立方法-主机映射关系
- 将映射关系存储在本地缓存中
- 实际调用时:
- 采用轮询算法从可用服务提供者中选择一个
- 执行远程方法调用
- 创建DiscoveryClient实例,该实例负责与注册中心建立通信
-
服务上线流程:
- 注册中心收到新服务的合法注册请求后
- 处理请求并将服务信息持久化存储
- 向订阅该服务的发现者推送上线通知
- 服务发现者接收通知并更新本地服务列表
-
服务下线流程:
- 服务注册者断开连接时触发预设回调
- 注册中心执行以下操作:
- 清除该服务的注册信息
- 向相关订阅者推送下线通知
- 服务发现者收到通知后移除对应服务记录
-
-
消息发布订阅
-
消息内部成员
-
消息推送请求(TopicRequestMessage)
- 为了区分当前请求是消息推送/订阅/取消订阅/主题创建/主题删除所用的_topicmtype
- 若是消息推送请求,则要用message(std::string)表示想要推送的消息
-
主题创建成功失败发回的响应(TopicReponseMessage)(主题删除、订阅、取消订阅、消息推送请求不需要响应)
- 表示响应结果状态码rcode
-
-
核心组件:
-
Topic 用于管理单个主题的相关操作(包括创建、删除和发布消息等),而 Subscribe 则处理单个订阅者的订阅行为(如添加或取消订阅主题)。
Server::TopicManager 负责管理和维护多个主题,同时协调主题与订阅者之间的关系。
Client::TopicManager 封装了客户端的主题操作,而 TopicClient 在 TopicClient 的基础上进一步封装,统一管理客户端与服务端的连接等信息。
TopicRequest 用于定义主题操作请求,TopicResponse 则用于描述主题操作的响应结果。
-
-
主要功能流程:
-
主题创建流程:
-
TopicClient客户端调用Create方法发起主题创建请求
-
服务端通过预置回调函数将消息传递至dispatcher模块
-
dispatcher识别请求类型并调用OnTopicResponse回调
-
处理TOPIC_CREATE请求,创建新Topic对象并持久化存储
-
-
主题删除流程:
-
TopicClient客户端调用Remove方法发起删除请求
-
服务端通过回调函数将消息转发至dispatcher模块
- dispatcher识别请求类型并调用OnTopicResponse回调
- 处理TOPIC_REMOVE请求,删除指定Topic对象及相关本地数据
-
-
主题订阅流程:
-
TopicClient客户端调用Subscribe方法发起订阅请求
-
服务端通过回调函数将消息传递至dispatcher模块
-
dispatcher识别请求类型并调用OnTopicResponse回调
-
处理TOPIC_SUBSCRIBE请求,为指定Topic添加订阅者信息并本地存储
-
-
主题取消订阅流程:
-
TopicClient客户端调用Cancel方法发起取消订阅请求
-
服务端通过回调函数将消息转发至dispatcher模块
-
dispatcher识别请求类型并调用OnTopicResponse回调
-
处理TOPIC_CANCEL请求,移除指定Topic中的订阅者信息
-
-
主题消息推送流程:
-
TopicClient客户端调用Publish方法发起消息推送
-
服务端通过回调函数将消息传递至dispatcher模块
-
dispatcher识别请求类型并调用OnTopicResponse回调
-
处理TOPIC_PUBLISH请求,向指定Topic推送消息
-
Topic对象自动将消息广播给所有订阅者(可通过自定义回调函数实现)
-
-
-
设计模式
- 桥接模式(分离网络实现和业务逻辑)
- 工厂模式(MessageFactory、ClientFactory等)
- 观察者模式(当主题收到消息时,会往它的订阅者进行消息的推送)
- 模版方法模式(MessageCallback定义的 通过Dispatcher::SerMessageCallback函数注册的 OnMessage纯虚函数 交由子类实现)
细枝末节
- 知识点:
- 客户端一共四个线程,一个是主线程,负责程序的启动、各个客户端的初始化以及程序的最终关闭;一个是负责连接注册中心的线程(长连接),以便注册中心向其发送服务上线下线请求以及本地发起服务发现请求;一个是负责向注册端发起连接进而执行rpc请求的线程(短链接,在每次收到响应后将本链接关闭);一个是负责和订阅中心进行长连接的线程,负责发送订阅请求以及接收订阅中心发来的消息推送。
- 输出内容全部使用日志式输出,方便debug
- 客户端和服务端统一用BaseClient和BaseServer抽象类,抽象类底层实现可以随时替换,增强可维护性
- 将各个消息类型特殊元素序列化后放置在body中,在dispatcher将message给到目标回调函数后,目标回调函数将body进行反序列化,根据Json::Value的特性将一个个元素按照协议取出。
- 对于服务注册与发现模块:服务调用客户端通过服务发现拿到的是提供该服务的主机地址(如果每次进行服务调用都是向注册中心进行访问,而后注册中心对该请求进行转接,则注册中心压力太大)。
- 致命:
- 为防止多线程模式下程序运行产生二义性,用锁将共享资源保护起来
- 对于muduo::net::EventLoopThread _loopthread;和muduo::net::EventLoop *_loop;:_loop拿到的应该(不是概率的意思)是_loopthread的内部维护的具有内部唯一性的指针,EventLoop 封装的是epoll,epoll调用起来后会占用一个线程,EventLoopThread 的意思就是另开一个线程启动epoll进行事件监控,如果此时有多个客户端用的是同一个EventLoopThread 的内部指针,那么EventLoopThread 内部那“唯一”的指针会根据谁的消息来了调用谁设置进去的回调函数SetOnMessage();
- 如果同时用智能指针对_loopthread和_loop进行管理,此时会造成二次释放。
- 对于muduo::CountDownLatch _downlatch: 当触发了客户端设置进去的OnConnect()回调函数,触发对_downlatch的CountDown(),然后主线程等待客户端那里就不会继续再阻塞(因为客户端调用成员函数connect()后是直接返回的(异步处理,此时可能出现连接未建立但是执行到了后续的代码这样的异常情况)),而是继续运行。
项目链接:https://gitee.com/starfish-filling-xinghai/Linux/tree/master/2025_10_21
更多推荐




所有评论(0)