一. RpcRouter模块介绍
首先,我们先回答一个问题
还记得Dispatcher类是怎么进行设计的吗
- Dispatcher内部维护一个 "消息类型-处理函数"映射表 (通常实现为
std::unordered_map或类似哈希结构)。 - 框架的使用者 可以提前向Dispatcher模块注册不同消息类型 对应的业务回调函数的注册。事实上,我们的RpcRouter模块就会写好消息类型是RPC请求 的业务处理回调函数,然后在Dispatcher模块里面统一进行注册的。
- 当一条消息抵达时,Dispatcher通过其类型标识快速查找映射表,获取对应的回调函数并执行,完成消息的处理闭环。
注意注册的这个过程,其实不是在我们这个模块里面实现的,而是在Dispatcher模块里面统一进行注册的。
在RPC(远程过程调用)架构中,服务端作为服务提供方,往往会对外提供多种功能各异的过程或方法,例如加法运算、文本翻译、数据查询等。
为了高效、可靠地处理来自客户端的海量并发请求,服务端必须首先建立一套完整的内部服务治理体系,其核心就是对自身所能提供的所有服务进行系统化的登记与管理。
这套管理机制通常通过一个中心化的服务注册表或路由器 来实现。在服务启动或动态扩展时,每一个可用的服务都会将其方法名称 以及对应的实际处理的回调函数注册到这个中心目录中。这就构成了服务端的"能力清单",是服务端进行请求分发的根本依据。
当服务端接收到一个RPC请求时,其处理流程如下:
-
解析与路由 :服务端网络层会首先解析请求包,提取出客户端意图调用的服务名称(例如,方法名"translate")。
-
服务查找 :服务端的核心调度器会将该标识符作为键,去查询内部的服务注册表。这是一个关键的决策点。
-
能力判断与分发:
-
服务存在 :如果在注册表中找到了匹配的服务条目,调度器会立即将请求参数、上下文等信息,传递给该条目所绑定的具体业务处理接口。随后,业务逻辑被执行,生成计算结果。
-
服务不存在 :如果查询失败,意味着客户端请求了一个服务端未定义、未注册或已下线的服务。此时,服务端不应尝试调用任何业务逻辑,而必须构造一个明确的错误响应(如包含"SERVICE_NOT_FOUND"错误码和描述信息的异常),并返回给客户端。这保证了系统的明确性和健壮性,避免了因无效请求导致的不可预知行为。
-
那么我们的RpcRouter模块就是来实现这么一整个过程的。
RpcRouter模块
- 存在的意义:向Dispatcher模块提供一个 Rpc请求 类型对应的业务回调函数。
- 这个业务处理回调函数主要负责解析传入的RPC请求,根据请求的方法名称找到对应的业务处理函数,执行并返回结果。
具体来说,这个业务处理回调函数内部需要实现的关键功能包括:
- 准确识别客户端请求的服务
- 验证参数有效性
- 触发相应的业务逻辑
- 并最终组织响应数据
写好这个业务处理回调函数之后,我们后续在编写这个Dispatcher模块时就会进行对这个业务处理回调函数注册。
现在我们就来研究一下RPC请求的核心数据:
一次完整的RPC调用,其请求中必须包含两个最关键的要素:
-
请求方法名称:指明需要调用的具体服务。
-
请求参数信息:提供执行该方法所需的输入数据。
我们从哪里获取RCP请求,这些数据存在于哪里?
无论是客户端发送的请求数据(方法名和参数),还是服务端返回的结果,都承载于我们在通信协议(Protocol)中定义的Body字段内。
在RPC流程中,客户端首先建立到服务端的通信连接,随后将方法名和参数信息封装并发送。
服务端的RpcRouter模块在接收到请求后,负责解析、校验并处理,最后将执行结果返回给客户端。
无论是客户端发送的请求数据(方法名和参数),还是服务端返回的结果,都承载于我们在通信协议(Protocol)中定义的
Body字段内。这句话什么意思呢?我们上面不是已经定义了一个协议了吗?为什么这个所有发布订阅相关的请求与响应均采用JSON格式在协议
Body字段中承载?在整个通信过程中,无论是客户端发送的请求(包含方法名和参数),还是服务端返回的响应(包含结果或状态),其核心的业务数据都统一承载于我们自定义应用层协议的 Body 字段中。
理解这一点,需要明确我们设计中的两个关键层次:传输协议层与业务数据层。
- 我们首先定义的传输协议,其核心目的是解决网络通信中的基础问题:如何确保一个完整的数据包能被准确、无误地发送与接收。它规定了数据包的通用结构(例如包含标识、长度、序列号、Body 等固定字段),类似于为运输货物设计了一个标准的、带有标签和尺寸说明的"集装箱"。这个"集装箱"保证了数据在网络传输层面的可靠性与完整性。
- 然而,这个"集装箱"(即协议)内部的"货物"如何摆放、如何解读,则由业务数据层来决定。在我们的系统中,所有与发布订阅相关的具体信息,都采用结构化的JSON格式进行组织,并最终作为一串字符序列(字符串)放入Body字段。
因此,一个完整的通信流程包含了两个紧密衔接的解析阶段:
- 协议解析阶段: 接收方首先根据自定义协议的规则(也就是我们上面Protocol模块里面讲的那个通信协议),**从网络字节流中识别出一个完整的数据包,并准确地提取出其中的 Body 字段内容。**此时,Body 被视为一个不透明的二进制字符串或字节数组。
- 业务数据解析(反序列化)阶段: 在此之后,我们需要对这个 Body 字符串进行进一步的"拆封"和解读。具体而言,就是按照 JSON 的语法规则对Body 字符串进行解析 ,从中分离出结构化的键值对,例如 "method": "subscribe"、"topic": "news" 等。随后,**将这些解析出的数据赋值到程序内部易于操作的数据结构(例如一个 Json::Value 对象)**中,从而将原始的字符串转换为内存中有意义的数据对象,供业务逻辑代码使用。
因为我们的数据都是以JSON格式进行封装传递的。 无论是客户端发送的请求数据(方法名和参数),还是服务端返回的结果,都承载于我们在通信协议(Protocol)中定义的
Body字段内。
基于当前实现JSON-RPC的考虑,我们初步采用JSON格式进行封装,定义如下:
请求与响应格式示例:
javascript
// RPC 请求(Request)
{
"method": "Add",
"parameters": {
"num1": 11,
"num2": 22
}
}
// RPC 成功响应(Response)
{
"rcode": "OK",
"result": 33
}
// RPC 错误响应(Response)
{
"rcode": "ERROR_INVALID_PARAMETERS"
"reason": "无效的参数信息!"
}
参数是如何传递给回调函数的?
参数是通过 Json::Value 对象直接传递给回调函数的。
为什么这样子设定呢?
其实根本原因就是我们传输的内容就是JSON格式。那么使用json::Value,我们就能更加方便的去传递给我们的回调函数。
让我详细解释这个传递过程:
回调函数的定义
首先,定义回调函数的类型:
javascript
using ServiceCallback = std::function<void(const Json::Value&, Json::Value &)>;
这是一个接受两个参数的函数:
- const Json::Value&:输入参数(只读)
- Json::Value &:输出结果(可修改)
我们就一个作为输入参数,另外一个就作为函数返回值,
参数检验
RpcRouter模块 向Dispatcher模块里面注册好了 Rpc请求类型对应的业务回调函数。
服务端的Dispatcher模块会将 RPC请求类型的消息 路由至RpcRouter进行处理。
RpcRouter模块在调用具体的业务回调函数时,仅会将parameters字段的数据传入。
然而,在RpcRouter模块在调用具体的业务回调函数之前,我们必须对传入的参数进行事前校验,以确认其名称、类型、数量是否符合该服务的要求,只有校验通过后才能提取数据执行业务逻辑。
需要注意的是,
在服务端,当接收到这么⼀条消息后,Dispatcher模块会找到 该Rpc请求类型 的回调处理函数进⾏业务处理,但是在进⾏业务处理的时候,也是只会将parameters参数字段传⼊回调函数中进⾏处理。
然⽽,对服务端来说,应该从传⼊的Json::Value对象中 ,有什么样的参数,以及参数信息是否符合⾃⼰所提供的服务的要求(其实就是想要调用的那个存在于服务器的一个函数的参数类型,返回值 匹不匹配的问题),都应该有⼀个检测,是否符合要求,符合要求了再取出指定字段的数据进⾏处理。
因此,服务端在注册服务 时,必须提供一份清晰的服务描述(其实就是将服务器上存在那些作为一个服务的函数的参数类型,返回值这些信息保存起来)。
以Add方法为例,其描述应包含:
-
服务名称 :
Add -
参数列表 :参数
num1(整型)、参数num2(整型) -
返回值类型:整型
这份描述是RpcRouter实现强类型校验 和安全调用的基础。借助它,RpcRouter可以在执行业务逻辑前,确保调用方传递的数据是合法且完整的,从而大幅提升系统的健壮性。
二.服务描述类------ServiceDescribe 类
我们都说我们是调用了我们服务器上面的RPC服务,那么我们的RPC服务到底是什么样子的?
那么我们就需要去用一个类去描述好它来。
那么一个这个RPC服务类里面应该有什么样的数据结构?
- RPC服务名
- RPC服务实际的处理函数
- 函数参数类型及其描述
- 函数的返回值
cpp
// 值类型枚举,用于描述JSON参数的类型
enum class VType
{
BOOL = 0, // 布尔类型
INTEGRAL, // 整数类型
NUMERIC, // 数字类型(包括整数和浮点数)
STRING, // 字符串类型
ARRAY, // 数组类型
OBJECT, // 对象类型
};
// 服务描述类,用于描述一个RPC服务的元信息
class ServiceDescribe
{
public:
// 智能指针类型定义
using ptr = std::shared_ptr<ServiceDescribe>;
// 服务回调函数类型定义,参数为请求参数和响应结果
using ServiceCallback = std::function<void(const Json::Value &, Json::Value &)>;
// 参数描述类型定义,包含参数名称和参数类型
using ParamsDescribe = std::pair<std::string, VType>;
......
private:
std::string _method_name; // 方法名称
ServiceCallback _callback; // 实际的业务回调函数
std::vector<ParamsDescribe> _params_desc; // 参数字段格式描述
VType _return_type; // 返回结果类型描述
};
有的人可能好奇了,为什么这里需要函数参数描述?我只需要一个函数参数类型不行吗,我们传递的时候按顺序将这个函数参数类型搞起来不就好了吗?
首先,JSON-RPC协议中,参数传递有两种方式:位置参数和命名参数。
-
位置参数:参数按照固定顺序放在一个数组里,例如:{"params": [1, 2]}
-
命名参数:参数按照名称放在一个对象里,例如:{"params": {"a": 1, "b": 2}}
很显然,我们这里采用的其实是命名参数版本的!!!
那么,为什么需要参数名称呢?
因为使用命名参数有以下好处:
-
可读性:调用时,参数的含义更清晰。
-
灵活性:可以忽略参数的顺序,并且可以方便地省略某些可选参数(如果支持的话)。
-
易于扩展:未来添加新的参数时,不影响已有的调用。
但是,如果只使用参数类型,那么只能使用位置参数。
位置参数的缺点:
-
必须严格按照顺序传递参数,如果顺序错了,结果就会错误,而且很难调试。
-
当参数较多时,调用方容易混淆顺序。
所以,在我们的RPC框架中,为了支持命名参数,就需要参数名称。同时,为了进行参数校验,也需要参数名称来查找对应的值。
同样的,如果说我们的调用方想要调用这个RPC服务,那么它在调用的时候,这个方法名称必须和服务端的保持一致。
那么我们很快就能写出这个代码
cpp
// 值类型枚举,用于描述JSON参数的类型
enum class VType
{
BOOL = 0, // 布尔类型
INTEGRAL, // 整数类型
NUMERIC, // 数字类型(包括整数和浮点数)
STRING, // 字符串类型
ARRAY, // 数组类型
OBJECT, // 对象类型
};
// 服务描述类,用于描述一个RPC服务的元信息
class ServiceDescribe
{
public:
// 智能指针类型定义
using ptr = std::shared_ptr<ServiceDescribe>;
// 服务回调函数类型定义,参数为请求参数和响应结果
using ServiceCallback = std::function<void(const Json::Value &, Json::Value &)>;
// 参数描述类型定义,包含参数名称和参数类型
using ParamsDescribe = std::pair<std::string, VType>;
// 构造函数,初始化服务描述信息
ServiceDescribe(std::string &&mname,
std::vector<ParamsDescribe> &&desc,
VType vtype,
ServiceCallback &&handler) : _method_name(std::move(mname)),
_callback(std::move(handler)),
_params_desc(std::move(desc)),
_return_type(vtype)
{
}
// 获取方法名称
const std::string &method()
{
return _method_name;
}
// 参数校验方法,检查传入的JSON参数params是否符合描述
bool paramCheck(const Json::Value ¶ms)
{
// 遍历这个RPC服务里面的所有参数描述,检查每个参数
for (auto &desc : _params_desc)
{
// 检查参数是否存在
if (params.isMember(desc.first) == false)//如果客户端调用RPC服务的时候函数参数名和服务端的函数参数名对不上
{
ELOG("参数字段完整性校验失败!%s 字段缺失!", desc.first.c_str());
return false;
}
// 检查参数类型是否匹配
if (check(desc.second, params[desc.first]) == false)//如果客户端调用RPC服务的时候函数参数类型和服务端的函数参数类型对不上
{
ELOG("%s 参数类型校验失败!", desc.first.c_str());
return false;
}
}
return true;
}
// 调用服务回调函数,并校验返回结果
bool call(const Json::Value ¶ms, Json::Value &result)
{
_callback(params, result);//直接调用实际的事件处理回调函数,将params作为参数传递进去,将result作为结果
// 校验返回结果类型
if (rtypeCheck(result) == false)
{
ELOG("回调处理函数中的响应信息校验失败!");
return false;
}
return true;
}
private:
// 返回类型校验
bool rtypeCheck(const Json::Value &val)
{
return check(_return_type, val);//检查JSON值是否符合我们这个RPC服务的返回值
}
// 通用类型校验方法,检查JSON值是否符合指定的VType
bool check(VType vtype, const Json::Value &val)
{
switch (vtype)
{
case VType::BOOL:
return val.isBool();
case VType::INTEGRAL:
return val.isIntegral();
case VType::NUMERIC:
return val.isNumeric();
case VType::STRING:
return val.isString();
case VType::ARRAY:
return val.isArray();
case VType::OBJECT:
return val.isObject();
}
return false;
}
private:
std::string _method_name; // 方法名称
ServiceCallback _callback; // 实际的业务回调函数
std::vector<ParamsDescribe> _params_desc; // 参数字段格式描述
VType _return_type; // 返回结果类型描述
};
返回类型检测
一、什么是返回类型检测?
返回类型检测是RPC框架中确保服务端返回的数据格式与预先承诺的格式完全一致的验证机制。想象一下,你去餐馆点了一份牛排,服务员送来了一个蛋糕------虽然都是食物,但完全不是你期待的。返回类型检测就是防止这种"货不对板"的质检员。
二、为什么要做这个检测?
- 维护接口契约的严肃性
每个RPC服务在注册时就明确声明了"我会返回什么类型的数据"。这是一个对外的承诺:
-
加法服务承诺:"我会返回一个整数"
-
用户查询服务承诺:"我会返回一个对象(包含用户信息的JSON)"
-
数据列表服务承诺:"我会返回一个数组"
这个检测确保服务提供方遵守自己的承诺。
- 防止意外错误
程序员在编写业务函数时可能会犯错误:
-
本应返回一个对象,却误返回了一个字符串
-
本应返回一个整数,却返回了一个浮点数
-
本应返回一个数组,却因为处理异常返回了null
没有检测的话,这些错误会直接传递给客户端,导致客户端解析失败或逻辑错误。
- 提供清晰的错误定位
当返回类型错误时,系统能立即知道:
-
哪个服务出了问题
-
期望的类型是什么
-
实际返回的类型是什么
这大大简化了调试过程。
三、检测流程的三层结构
第一层:执行与初步判断(call函数)
这个函数的核心任务是:
-
调用业务函数:把客户端的参数交给实际的业务处理函数执行
-
获取执行结果:业务函数将处理结果填充到指定的结果变量中
-
启动验证:将得到的结果交给下一层进行类型验证
关键在于:它不管结果是什么类型,只负责获取结果并启动验证流程。
第二层:专用验证器(rtypeCheck函数)
这个函数扮演"专业质检员"的角色:
-
查阅规格书:它知道这个服务应该返回什么类型(_return_type)
-
调用检验工具:它自己不直接检查,而是把"规格要求"和"实际产品"交给通用的检验工具
-
传达检验结果:把通用检验工具的结果原样返回
它的存在让代码更清晰:执行流程和验证流程分离,每个函数职责单一。
第三层:通用检验工具(check函数)
这是真正的"检测仪器",能够执行具体的检测:
-
如果要求的是布尔值,就检查实际值是不是true或false
-
如果要求的是整数,就检查实际值是不是没有小数部分的数字
-
如果要求的是数字(含小数),就检查实际值是不是数字类型
-
如果要求的是字符串,就检查实际值是不是文本类型
-
如果要求的是数组,就检查实际值是不是用方括号表示的列表
-
如果要求的是对象,就检查实际值是不是用花括号表示的键值对集合
这个工具被设计成通用的,既可用于检查返回类型,也可用于检查参数类型。
四、检测的具体内容是什么?
检测的是JSON值的具体类型,而不是内容或结构:
正确示例:
-
服务承诺返回"整数",实际返回
42→ 通过 -
服务承诺返回"字符串",实际返回
"处理成功"→ 通过 -
服务承诺返回"对象",实际返回
{"status": "ok"}→ 通过
错误示例:
-
服务承诺返回"整数",实际返回
"42"(字符串)→ 失败 -
服务承诺返回"对象",实际返回
"用户信息"(字符串)→ 失败 -
服务承诺返回"数组",实际返回
{"items": []}(对象)→ 失败
注意:检测的是最外层类型 ,不深入检查内部结构。如果承诺返回对象,实际返回{}(空对象)也是通过的。
三.服务工厂类------SDescribeFactory类
为了统一构建出这个服务描述类,我们就有必要来专门写一个工厂类来
cpp
// 服务描述工厂类,用于构建ServiceDescribe对象
class SDescribeFactory
{
public:
// 设置方法名称
void setMethodName(const std::string &name)
{
_method_name = name;
}
// 设置返回类型
void setReturnType(VType vtype)
{
_return_type = vtype;
}
// 添加参数描述
void setParamsDesc(const std::string &pname, VType vtype)
{
_params_desc.push_back(ServiceDescribe::ParamsDescribe(pname, vtype));
}
// 设置回调函数
void setCallback(const ServiceDescribe::ServiceCallback &cb)
{
_callback = cb;
}
// 构建ServiceDescribe对象
ServiceDescribe::ptr build()
{
return std::make_shared<ServiceDescribe>(std::move(_method_name),
std::move(_params_desc),
_return_type,
std::move(_callback));
}
private:
std::string _method_name;
ServiceDescribe::ServiceCallback _callback; // 实际的业务回调函数
std::vector<ServiceDescribe::ParamsDescribe> _params_desc; // 参数字段格式描述
VType _return_type; // 返回结果类型描述
};
这样子
四.服务管理器类
服务管理器类,管理所有注册的RPC服务
所谓管理呢,就是构建了一张哈希表<方法名称,服务描述>
- 注册方法时,将方法名称和服务描述组合到一起,保存在这个服务管理器类里面的哈希表里面
- 移除服务的时候,就将这个方法名称和服务描述从哈希表里面移除
- 然后可以根据方法名查询并获取到对应的服务描述
思想非常的简单。
cpp
// 服务管理器类,管理所有注册的RPC服务
class ServiceManager
{
public:
using ptr = std::shared_ptr<ServiceManager>;
// 插入服务描述
void insert(const ServiceDescribe::ptr &desc)
{
std::unique_lock<std::mutex> lock(_mutex);//加锁
_services.insert(std::make_pair(desc->method(), desc));//
}
// 根据方法名查询服务描述
ServiceDescribe::ptr select(const std::string &method_name)
{
std::unique_lock<std::mutex> lock(_mutex);//加锁
auto it = _services.find(method_name);//根据方法名查找这个服务描述
if (it == _services.end())
{
return ServiceDescribe::ptr();//返回一个空指针
}
return it->second;
}
// 移除服务描述
void remove(const std::string &method_name)
{
std::unique_lock<std::mutex> lock(_mutex);
_services.erase(method_name);//根据方法名移除掉这个服务描述
}
private:
std::mutex _mutex; // 互斥锁,保证线程安全
std::unordered_map<std::string, ServiceDescribe::ptr> _services; // 服务映射表,<方法名,服务描述>,实际上的服务描述就是<参数名,参数类型>的集合
};
四.RpcRouter类
到现在,我们就算是能真正的来描述我们的这个RpcRouter模块了。
onRpcRequest() - RPC请求处理入口
这是路由器最核心的函数,Dispatcher模块收到请求后就调用它。整个处理流程像一条生产线:
第一步:查找服务(检查能否提供服务)
-
操作:根据客户端请求中的方法名,在服务管理器中根据这个请求的方法名来查找对应的服务描述,所谓的服务描述就是请求的这个RPC服务的 方法参数名和参数类型的集合
-
结果处理:
-
如果找不到:立即向客户端返回"服务未找到"的错误响应
-
如果找到:继续下一步处理
-
-
意义:这是第一道筛选,确保只处理自己支持的服务
第二步:参数校验(检查请求是否合规)
-
操作 :调用服务描述的
paramCheck方法,验证客户端传来的参数 -
验证内容:
-
参数个数是否齐全
-
每个参数的类型是否正确
-
参数名称是否匹配
-
-
结果处理:
-
验证失败:立即返回"参数无效"的错误响应
-
验证通过:继续下一步处理
-
-
意义:确保只有格式正确的请求才能进入业务处理,防止垃圾数据影响系统
第三步:执行业务逻辑(核心处理)
-
操作:
-
创建一个空的JSON容器用于存放结果
-
调用服务描述的
call方法执行实际业务逻辑 -
该方法会验证返回结果的类型是否符合约定
-
-
结果处理:
-
调用失败(返回类型不匹配):返回"内部错误"的响应
-
调用成功:进入最后一步
-
-
注意:这里的"调用失败"特指业务函数执行后,返回结果类型不符合服务描述中声明的类型
第四步:发送响应(完成闭环)
-
操作:将业务处理得到的结果,包装成标准的RPC响应,发送回客户端
-
特点:同时附带成功状态码,表示整个请求处理圆满完成
registerMethod()
-
作用:将一个RPC服务描述注册到路由器中
-
调用时机:通常在服务器启动阶段,各个业务模块将自己的服务注册进来
-
工作方式 :将服务描述交给内部的
_service_manager进行存储管理 -
重要性:只有注册过的服务才能被客户端调用,这是服务的"上户口"过程
response()
这是一个私有辅助函数,专门负责构建和发送响应消息。
构建响应的五个关键步骤:
-
创建响应消息对象:使用工厂模式创建一个标准的RPC响应消息
-
设置请求ID:将响应ID设置为与请求ID相同,确保"谁问的,回答谁"
-
设置消息类型:明确这是RPC响应类型
-
设置响应码:表示处理结果(成功、失败、参数错误等)
-
设置结果数据:将业务处理结果放入响应中
发送响应:
将构建好的完整响应消息通过连接对象发送回客户端,完成一次完整的RPC交互。
cpp
// RPC路由器类,处理RPC请求的路由和分发
class RpcRouter
{
public:
using ptr = std::shared_ptr<RpcRouter>;
// 构造函数,构建出一个服务管理器
RpcRouter() : _service_manager(std::make_shared<ServiceManager>()) {}
// RPC请求处理函数,由Dispatcher模块调用
void onRpcRequest(const BaseConnection::ptr &conn, RpcRequest::ptr &request)
{
// 1. 查询客户端请求的方法描述--判断当前服务端能否提供对应的服务
auto service = _service_manager->select(request->method());//根据请求的方法名去查找对应的服务描述
if (service.get() == nullptr)//没找到
{
ELOG("%s 服务未找到!", request->method().c_str());
return response(conn, request, Json::Value(), RCode::RCODE_NOT_FOUND_SERVICE);
//返回一个响应码是RCode::RCODE_NOT_FOUND_SERVICE的响应,表示未找到请求的服务
}
//找到了对应的服务
// 2. 进行参数校验,确定能否提供服务
if (service->paramCheck(request->params()) == false)//参数校验失败
{
ELOG("%s 服务参数校验失败!", request->method().c_str());
return response(conn, request, Json::Value(), RCode::RCODE_INVALID_PARAMS);
//返回一个响应码是RCode::RCODE_INVALID_PARAMS的响应,表示请求参数无效或者不合法
}
// 3. 调用业务回调接口进行业务处理
Json::Value result;
bool ret = service->call(request->params(), result);//调用业务处理函数并进行返回值类型校验
if (ret == false)//调用失败,返回值类型对不上
{
ELOG("%s 服务参数校验失败!", request->method().c_str());
return response(conn, request, Json::Value(), RCode::RCODE_INTERNAL_ERROR);
}
// 4. 处理完毕得到结果,组织响应,向客户端发送
return response(conn, request, result, RCode::RCODE_OK);
//返回一个响应码是RCode::RCODE_OK的响应,表示操作成功
}
// 注册RPC方法
void registerMethod(const ServiceDescribe::ptr &service)
{
return _service_manager->insert(service);
}
private:
// 发送RPC响应
void response(const BaseConnection::ptr &conn,
const RpcRequest::ptr &req,
const Json::Value &res, RCode rcode)
{
auto msg = MessageFactory::create<RpcResponse>();
msg->setId(req->rid()); // 设置请求ID
msg->setMType(jsonRpc::MType::RSP_RPC); // 设置消息类型为RPC响应
msg->setRCode(rcode); // 设置响应码
msg->setResult(res); // 设置结果数据
conn->send(msg); // 发送响应
}
private:
ServiceManager::ptr _service_manager; // 服务管理器
};
这里就很明确了我们的思路。