C++ Json-RPC框架开发:JsonCpp集成与协议工具类封装实践
1. 项目概述与核心思路在上一篇文章里我们搭建了一个基础的C网络服务框架算是把“房子”的地基和骨架搭好了。但一个完整的Json-Rpc框架核心在于“通信语言”——也就是Json数据的处理。你想想客户端发来一个请求服务端返回一个响应这些信息怎么组织总不能靠字符串拼接吧那太原始了解析起来也容易出错。所以我们得引入一个专业的Json库来帮我们处理这些结构化的数据。为什么是JsonCpp在C的Json库生态里JsonCpp算是个“老牌劲旅”了。它成熟、稳定、文档相对齐全而且很多开源项目都在用社区支持不错。虽然性能上可能不是最顶尖的比如和RapidJSON比但对于我们学习和构建一个功能完整的Json-Rpc框架来说它的易用性和可靠性是第一位的。我们先把功能跑通、逻辑理清性能优化是后面的事情。所以这一弹的核心任务就两个第一把JsonCpp库集成到我们的项目中第二基于JsonCpp封装一个我们自己的Json工具类。这个工具类不是简单地对JsonCpp做一层薄薄的包装而是要针对Json-Rpc通信中的常见操作进行抽象和简化。比如构建一个标准的Json-Rpc请求对象、解析请求中的方法名和参数、构建成功或错误的响应。把这些高频操作封装成简洁的接口能让我们的核心业务代码处理具体RPC方法的部分更清晰、更专注于逻辑本身而不是陷入Json数据结构的繁琐操作中。2. JsonCpp库的集成与环境配置集成第三方库对于C开发者来说是家常便饭但也是容易踩坑的地方。JsonCpp提供了多种集成方式我们需要根据自己项目的构建系统来选择最合适的一种。2.1 获取JsonCpp源码最推荐的方式是从其GitHub官方仓库获取源码。你可以直接克隆仓库或者下载某个稳定版本的Release压缩包。这样做的好处是源码在手调试方便并且可以灵活地选择编译选项。假设我们项目根目录下有一个third_party文件夹用来存放第三方库操作如下cd your_project_root mkdir -p third_party cd third_party git clone https://github.com/open-source-parsers/jsoncpp.git # 或者下载特定版本比如 1.9.5 # wget https://github.com/open-source-parsers/jsoncpp/archive/refs/tags/1.9.5.tar.gz # tar -xzf 1.9.5.tar.gz2.2 使用CMake集成推荐如果你的项目使用CMake这也是现代C项目的主流选择集成JsonCpp就非常优雅。我们不需要预先编译JsonCpp成静态库或动态库而是可以直接将它的源码目录作为子目录add_subdirectory加入到我们的CMake工程中。CMake会负责编译它并且自动导出头文件路径和链接库目标。在你的项目主CMakeLists.txt中可以这样写cmake_minimum_required(VERSION 3.10) project(MyJsonRpcFramework) set(CMAKE_CXX_STANDARD 11) # 将JsonCpp源码目录添加为子目录 add_subdirectory(third_party/jsoncpp) # 你的可执行文件或库 add_executable(my_rpc_server main.cpp server.cpp) # 或者 add_library(my_rpc_lib ...) # 链接JsonCpp库。注意这里链接的是CMake目标名 jsoncpp_lib 或 jsoncpp_static # JsonCpp的CMake目标名取决于其编译选项通常静态库是 jsoncpp_static动态库是 jsoncpp_lib target_link_libraries(my_rpc_server jsoncpp_static) # 包含头文件目录会自动传递通常无需手动添加 include_directories注意JsonCpp的CMake目标名Target Name可能因版本和配置而异。老版本可能是jsoncpp_lib新版本如1.9.x通过add_subdirectory引入后静态库目标通常是jsoncpp_static。最稳妥的方法是查看third_party/jsoncpp/CMakeLists.txt文件或者编译后查看CMake生成的目标列表。你也可以在add_subdirectory之前设置一个选项来强制使用静态库set(JSONCPP_WITH_STATIC_LIB ON)。2.3 使用vcpkg或Conan包管理器如果你的团队或项目已经使用了包管理器那会更方便。例如使用vcpkg# 安装JsonCpp vcpkg install jsoncpp然后在CMake中使用find_package来定位它find_package(jsoncpp REQUIRED) target_link_libraries(my_rpc_server jsoncpp_lib)这种方式更干净依赖管理清晰适合团队协作和持续集成。2.4 直接使用预编译库不推荐但需了解在一些简单的场景或者老旧的IDE项目如Visual Studio中你可能会直接下载编译好的.lib和.dll文件并手动配置包含目录和库目录。这种方法耦合性强跨平台麻烦且库的版本、编译选项如Debug/Release、MT/MD必须与你的项目严格匹配否则会导致链接错误或运行时崩溃。除非万不得已否则建议优先使用源码集成或包管理器。实操心得我强烈推荐使用CMake的add_subdirectory方式。它保证了所有开发者环境的一致性并且编译出的JsonCpp库的配置如C标准、运行时库与你的主项目完全一致避免了“DLL Hell”或链接不兼容的问题。第一次设置好后后续就一劳永逸了。3. Json工具类的设计与封装现在JsonCpp已经集成好了我们可以开始设计自己的工具类了。这个工具类我把它命名为JsonUtil。它的目标很明确让Json-Rpc框架内的其他模块如网络层、业务处理器能够以最简单、最不易出错的方式操作Json数据。3.1 类设计原则静态工具类JsonUtil不需要维护状态所有方法都设计为静态static。这样调用起来很方便JsonUtil::parseRequest(...)。职责单一每个方法只做一件事并且做好错误处理。比如解析请求就专心解析构建响应就专心构建。隐藏复杂性将JsonCpp的底层类型如Json::Value的复杂操作封装起来对外提供语义清晰的接口。强类型与错误码尽可能使用enum class来定义错误类型而不是用魔数magic number或字符串。返回值使用std::optional或std::expectedC23或者简单的输出参数布尔返回值来表示成功/失败并附带错误信息。3.2 核心数据结构定义首先我们需要定义一些Json-Rpc规范要求的数据结构。根据Json-Rpc 2.0规范一个请求Request和响应Response有固定的格式。我们在一个头文件比如json_rpc_protocol.h中定义// json_rpc_protocol.h #pragma once #include string #include optional #include vector namespace my_rpc { // Json-Rpc 2.0 错误码定义 enum class JsonRpcErrorCode : int { PARSE_ERROR -32700, INVALID_REQUEST -32600, METHOD_NOT_FOUND -32601, INVALID_PARAMS -32602, INTERNAL_ERROR -32603, // 服务端自定义错误码范围-32000 到 -32099 SERVER_ERROR_BASE -32000 }; // 错误对象结构 struct JsonRpcError { JsonRpcErrorCode code; std::string message; // 可选的附加数据 // Json::Value data; // 暂时用不到先注释 }; // 请求对象结构 struct JsonRpcRequest { std::string jsonrpc 2.0; // 固定为 2.0 std::string method; // 参数可以是数组by-position或对象by-name我们用Json::Value来灵活存储 // std::optional 表示可能没有参数 std::optionalJson::Value params; std::optionalstd::string id; // 请求ID可为字符串、数字、null。null表示通知不需要回复 }; // 响应对象结构成功 struct JsonRpcSuccessResponse { std::string jsonrpc 2.0; Json::Value result; // 方法执行结果 // ID必须和请求中的ID保持一致除了通知 std::variantstd::string, int, std::nullptr_t id; // 使用variant容纳多种类型的ID }; // 响应对象结构错误 struct JsonRpcErrorResponse { std::string jsonrpc 2.0; JsonRpcError error; std::variantstd::string, int, std::nullptr_t id; }; } // namespace my_rpc这里我们使用了C17的std::optional和std::variant让数据结构更清晰、更安全。Json::Value来自JsonCpp它是一个可以表示任何Json类型对象、数组、字符串、数字等的通用容器。3.3 JsonUtil工具类实现接下来是重头戏JsonUtil类的实现。我们把它放在json_util.h和json_util.cpp中。// json_util.h #pragma once #include json_rpc_protocol.h #include json/json.h #include string namespace my_rpc { class JsonUtil { public: // 1. 将字符串解析为通用的Json::Value static std::optionalJson::Value parseJsonString(const std::string jsonStr); // 2. 将通用的Json::Value序列化为字符串 static std::optionalstd::string jsonValueToString(const Json::Value value); // 3. 从Json::Value解析出一个JsonRpcRequest对象 // 成功返回Request失败返回错误信息 static std::pairstd::optionalJsonRpcRequest, std::string parseJsonRpcRequest(const Json::Value jsonValue); // 4. 从原始字符串直接解析出JsonRpcRequest对象组合了1和3 static std::pairstd::optionalJsonRpcRequest, std::string parseRawRequest(const std::string rawRequest); // 5. 构建一个成功的Json-Rpc响应Json::Value格式 static Json::Value buildSuccessResponse(const Json::Value result, const JsonRpcRequest req); // 6. 构建一个错误的Json-Rpc响应Json::Value格式 static Json::Value buildErrorResponse(JsonRpcErrorCode code, const std::string message, const JsonRpcRequest req); // 7. 将响应对象Json::Value序列化为字符串 static std::string responseToString(const Json::Value response); private: // 一些内部辅助函数比如验证请求格式 static bool isValidRequestFormat(const Json::Value jsonValue); }; } // namespace my_rpc头文件定义了接口现在我们来看.cpp文件中的关键实现// json_util.cpp #include json_util.h #include sstream namespace my_rpc { std::optionalJson::Value JsonUtil::parseJsonString(const std::string jsonStr) { Json::Value root; Json::CharReaderBuilder builder; builder[collectComments] false; // 不收集注释 std::unique_ptrJson::CharReader reader(builder.newCharReader()); std::string errs; const char* begin jsonStr.c_str(); const char* end begin jsonStr.length(); bool parsingSuccessful reader-parse(begin, end, root, errs); if (!parsingSuccessful) { // 在实际框架中这里可以记录日志 // LOG(ERROR) Failed to parse JSON: errs; return std::nullopt; } return root; } std::optionalstd::string JsonUtil::jsonValueToString(const Json::Value value) { Json::StreamWriterBuilder writerBuilder; writerBuilder[indentation] ; // 紧凑输出节省网络带宽 writerBuilder.settings_[precision] 6; // 浮点数精度 try { std::string jsonStr Json::writeString(writerBuilder, value); return jsonStr; } catch (const std::exception e) { // LOG(ERROR) Failed to serialize JSON: e.what(); return std::nullopt; } } bool JsonUtil::isValidRequestFormat(const Json::Value jsonValue) { // 必须是一个JSON对象 if (!jsonValue.isObject()) { return false; } // 必须包含 jsonrpc: 2.0 if (!jsonValue.isMember(jsonrpc) || jsonValue[jsonrpc] ! 2.0) { return false; } // 必须包含 method且为字符串 if (!jsonValue.isMember(method) || !jsonValue[method].isString()) { return false; } // params 是可选的但如果存在必须是数组或对象 if (jsonValue.isMember(params)) { const Json::Value params jsonValue[params]; if (!params.isArray() !params.isObject()) { return false; } } // id 是可选的但如果存在必须是字符串、数字或null if (jsonValue.isMember(id)) { const Json::Value id jsonValue[id]; if (!id.isString() !id.isInt() !id.isUInt() !id.isInt64() !id.isUInt64() !id.isDouble() !id.isNull()) { // 注意JsonCpp的isNumeric()可能判断过宽这里严格一点 return false; } } return true; } std::pairstd::optionalJsonRpcRequest, std::string JsonUtil::parseJsonRpcRequest(const Json::Value jsonValue) { if (!isValidRequestFormat(jsonValue)) { return {std::nullopt, Invalid JSON-RPC request format}; } JsonRpcRequest req; req.method jsonValue[method].asString(); // 处理 params if (jsonValue.isMember(params)) { req.params jsonValue[params]; // Json::Value 支持拷贝 } // 处理 id if (jsonValue.isMember(id)) { const Json::Value idVal jsonValue[id]; if (idVal.isString()) { req.id idVal.asString(); } else if (idVal.isNull()) { req.id std::nullopt; // 通知请求id为null } else { // 对于数字类型的ID我们统一先转换为字符串存储方便后续处理。 // 也可以选择用variant存储这里为了简化先转字符串。 std::stringstream ss; if (idVal.isInt() || idVal.isUInt() || idVal.isInt64() || idVal.isUInt64()) { ss idVal.asLargestInt(); // 获取可能的最大整数表示 } else if (idVal.isDouble()) { ss idVal.asDouble(); } req.id ss.str(); } } // 如果没有id成员req.id保持std::nullopt这也是允许的规范说可以省略但通常不建议 return {req, }; } std::pairstd::optionalJsonRpcRequest, std::string JsonUtil::parseRawRequest(const std::string rawRequest) { auto jsonOpt parseJsonString(rawRequest); if (!jsonOpt.has_value()) { return {std::nullopt, Parse error: Invalid JSON string}; } return parseJsonRpcRequest(jsonOpt.value()); } Json::Value JsonUtil::buildSuccessResponse(const Json::Value result, const JsonRpcRequest req) { Json::Value response; response[jsonrpc] 2.0; response[result] result; // 处理ID如果请求是通知id为nullopt响应不应包含id规范要求。 // 但规范也允许服务器对通知请求不返回任何响应。这里我们选择返回一个不带id的成功响应虽然不标准但有些客户端可能期望。 // 更严格的做法是对于通知请求直接返回一个空的optional让网络层决定是否发送。 // 这里为了演示如果请求id存在就设置如果不存在通知我们设置id为null。 if (req.id.has_value()) { // 我们需要知道id原来的类型字符串/数字但上面我们统一转成了字符串存储。 // 这是一个设计权衡。更精细的设计可以保留原始类型信息。 response[id] req.id.value(); } else { response[id] Json::nullValue; } return response; } Json::Value JsonUtil::buildErrorResponse(JsonRpcErrorCode code, const std::string message, const JsonRpcRequest req) { Json::Value response; response[jsonrpc] 2.0; Json::Value errorObj; errorObj[code] static_castint(code); errorObj[message] message; // errorObj[data] ...; // 可选数据 response[error] errorObj; // 错误响应必须包含id除非请求本身无法解析Parse Error或无效Invalid Request。 // 这里我们假设req是有效的至少能提取出id。对于最顶层的解析失败需要单独处理。 if (req.id.has_value()) { response[id] req.id.value(); } else { // 如果请求是通知规范说错误响应“应该不返回任何数据”但返回错误可能有助于调试。 // 这里我们选择返回一个null的id。 response[id] Json::nullValue; } return response; } std::string JsonUtil::responseToString(const Json::Value response) { auto strOpt jsonValueToString(response); if (strOpt.has_value()) { return strOpt.value(); } // 如果序列化失败返回一个预定义的错误响应字符串 // 这是一个兜底策略实际生产中应该记录日志并抛出异常或返回错误码。 return R({jsonrpc:2.0,error:{code:-32603,message:Internal error: failed to serialize response},id:null}); } } // namespace my_rpc注意事项与设计思考错误处理我们没有使用C异常而是通过std::optional和返回错误字符串的方式来传递错误。这在性能敏感的中间件中很常见。你也可以定义自己的ResultT, E类型。ID类型处理Json-Rpc规范中ID可以是字符串、数字或null。我们上面为了简化在parseJsonRpcRequest中将数字ID统一转成了字符串。这可能会丢失原始类型信息但在大多数场景下客户端和服务器对ID的类型并不敏感只要能正确匹配即可。如果你需要严格保持类型可以在JsonRpcRequest中使用std::variantstd::string, int64_t, double, std::nullptr_t来存储ID并在序列化/反序列化时做相应处理。这会增加复杂度请根据需求权衡。通知Notification即请求中id为null或不包含id的请求。服务器不应回复。我们的buildSuccessResponse和buildErrorResponse目前都生成了包含id的响应。更严谨的实现是在业务逻辑层判断如果是通知就直接跳过构建和发送响应的步骤。这个逻辑我们留到下一弹网络层和调度层去实现。性能Json::StreamWriterBuilder的配置如indentation会影响输出字符串的大小。在网络传输中我们肯定选择紧凑格式。settings_[precision]用于控制浮点数精度避免精度损失或不必要的长字符串。4. 工具类的测试与验证写完工具类不测试就等于白写。我们需要写一些单元测试来验证它的正确性和健壮性。这里我使用Google Test框架来举例你也可以用任何你熟悉的测试框架。首先创建一个测试文件test_json_util.cpp#include json_util.h #include gtest/gtest.h TEST(JsonUtilTest, ParseValidRequest) { std::string validRequest R({ jsonrpc: 2.0, method: subtract, params: [42, 23], id: 1 }); auto [reqOpt, errMsg] my_rpc::JsonUtil::parseRawRequest(validRequest); ASSERT_TRUE(reqOpt.has_value()) errMsg; auto req reqOpt.value(); EXPECT_EQ(req.method, subtract); EXPECT_TRUE(req.params.has_value()); EXPECT_TRUE(req.params-isArray()); EXPECT_EQ((*req.params)[0].asInt(), 42); EXPECT_EQ((*req.params)[1].asInt(), 23); EXPECT_TRUE(req.id.has_value()); EXPECT_EQ(req.id.value(), 1); // 注意我们转成了字符串 } TEST(JsonUtilTest, ParseNotification) { std::string notification R({ jsonrpc: 2.0, method: update, params: {status: ok} }); // 没有id字段 auto [reqOpt, errMsg] my_rpc::JsonUtil::parseRawRequest(notification); ASSERT_TRUE(reqOpt.has_value()) errMsg; auto req reqOpt.value(); EXPECT_EQ(req.method, update); EXPECT_TRUE(req.params.has_value()); EXPECT_TRUE(req.params-isObject()); EXPECT_EQ((*req.params)[status].asString(), ok); EXPECT_FALSE(req.id.has_value()); // id应为nullopt } TEST(JsonUtilTest, ParseInvalidJson) { std::string invalidJson { invalid json }; auto [reqOpt, errMsg] my_rpc::JsonUtil::parseRawRequest(invalidJson); EXPECT_FALSE(reqOpt.has_value()); EXPECT_NE(errMsg.find(Parse error), std::string::npos); } TEST(JsonUtilTest, BuildSuccessResponse) { my_rpc::JsonRpcRequest req; req.method add; req.id req-123; Json::Value result; result[sum] 65; Json::Value response my_rpc::JsonUtil::buildSuccessResponse(result, req); EXPECT_EQ(response[jsonrpc].asString(), 2.0); EXPECT_EQ(response[result][sum].asInt(), 65); EXPECT_EQ(response[id].asString(), req-123); // 测试序列化 std::string responseStr my_rpc::JsonUtil::responseToString(response); // 可以简单检查是否包含关键字段 EXPECT_NE(responseStr.find(\jsonrpc\:\2.0\), std::string::npos); EXPECT_NE(responseStr.find(\result\:{\sum\:65}), std::string::npos); // 注意紧凑格式可能没有空格 } TEST(JsonUtilTest, BuildErrorResponse) { my_rpc::JsonRpcRequest req; req.method divide; req.id 999; // 数字ID会被parse成字符串999 Json::Value response my_rpc::JsonUtil::buildErrorResponse( my_rpc::JsonRpcErrorCode::INVALID_PARAMS, Invalid parameters: divisor cannot be zero, req ); EXPECT_EQ(response[jsonrpc].asString(), 2.0); EXPECT_EQ(response[error][code].asInt(), static_castint(my_rpc::JsonRpcErrorCode::INVALID_PARAMS)); EXPECT_EQ(response[error][message].asString(), Invalid parameters: divisor cannot be zero); EXPECT_EQ(response[id].asString(), 999); }运行这些测试确保所有功能都按预期工作。测试是保证代码质量、防止后续修改引入回归错误的关键。5. 常见问题与排查技巧实录在实际集成和封装过程中你几乎一定会遇到下面这些问题。我把它们和解决方法记录下来希望能帮你节省时间。5.1 编译链接错误找不到JsonCpp头文件或库问题现象fatal error: json/json.h file not found或者undefined reference to Json::Value::Value(...)排查思路检查包含路径确保你的编译命令或CMakeLists.txt正确设置了JsonCpp的头文件目录。如果使用add_subdirectory通常不需要手动设置CMake的target_link_libraries会自动传递。检查链接库确认target_link_libraries中链接的目标名称正确。对于静态库尝试jsoncpp_static对于动态库尝试jsoncpp_lib。查看JsonCpp源码目录下的CMakeLists.txt找到add_library语句定义的目标名。检查编译选项一致性特别是在Windows下使用Visual Studio要确保你的项目Debug/Release和JsonCpp库的编译配置MT/MD一致。如果使用add_subdirectory则不存在此问题。vcpkg集成如果使用vcpkg确保你运行了vcpkg integrate install并且在CMake配置时传递了工具链文件-DCMAKE_TOOLCHAIN_FILE[path/to/vcpkg]/scripts/buildsystems/vcpkg.cmake。5.2 运行时崩溃访问Json::Value越界或类型错误问题现象程序在调用Json::Value::asString(),operator[]等函数时崩溃。根本原因JsonCpp的Json::Value在访问不存在的成员或类型不匹配时行为取决于编译设置。默认情况下operator[]会创建一个null值并返回对于对象或返回一个引用对于数组可能越界访问。而asString()等转换函数在类型不匹配时会尝试转换如果失败则返回默认值如空字符串、0通常不会崩溃。崩溃更可能源于空指针或内存损坏。排查与预防始终检查成员是否存在和类型在使用operator[]或转换函数前养成习惯。const Json::Value data ...; if (data.isMember(key) data[key].isString()) { std::string val data[key].asString(); } // 或者使用 get 方法可以指定默认值但不会进行类型检查 // std::string val data.get(key, default).asString();使用isNull()判断Json::Value默认构造是null类型。如果一个值可能不存在先判断!value.isNull()。数组访问前检查索引if (index array.size()) { ... array[index] ... }启用JsonCpp的调试断言在Debug模式下JsonCpp可能有断言检查。确保你的项目也是Debug配置以便尽早发现问题。5.3 内存泄漏JsonCpp对象管理问题现象长时间运行后内存使用量持续增长。排查思路JsonCpp的对象Json::Value在内部使用引用计数来管理内存。通常栈上的对象或作为其他对象的成员在析构时会自动释放内存。内存泄漏的常见原因循环引用JsonCpp理论上可以处理循环引用但复杂场景下仍需注意。避免创建Json::ValueA包含BB又包含A的情况。全局或静态对象全局或静态的Json::Value可能在整个程序生命周期内都不释放。如果它们持有大量数据需要考虑在适当的时候清空Json::Value::clear()。使用Json::Value指针如果你用new创建了Json::Value*务必记得delete。更推荐使用智能指针或直接在栈上使用。最佳实践尽量在函数作用域内使用Json::Value让其自动析构。对于需要长期持有的数据考虑将其转换为标准C类型如std::map,std::vector存储。5.4 性能瓶颈频繁解析与序列化问题现象在高并发RPC调用下CPU占用过高分析发现时间主要花在Json的解析和生成上。优化技巧复用Json::CharReader和Json::StreamWriterJson::CharReaderBuilder和Json::StreamWriterBuilder的创建有一定开销。对于高频服务可以在程序初始化时创建好这些builder甚至创建好reader/writer实例注意线程安全每个线程一个或加锁保护。// 全局或线程局部存储 thread_local Json::CharReaderBuilder s_readerBuilder; thread_local std::unique_ptrJson::CharReader s_reader(s_readerBuilder.newCharReader()); bool parse(const std::string str, Json::Value* root) { std::string errs; return s_reader-parse(str.data(), str.data() str.size(), root, errs); }使用Json::FastWriter替代Json::StyledWriterJson::StyledWriter会生成格式化的带缩进、换行字符串体积大。Json::FastWriter生成紧凑字符串。但注意Json::FastWriter在较新版本中已被Json::StreamWriterBuilder取代通过设置indentation为空即可达到同样效果如上文所示。减少中间转换如果可能在网络层直接处理二进制数据避免先转成std::string再解析。或者对于已知结构的请求/响应可以尝试更快的Json库如RapidJSON或使用schema预编译。异步处理将耗时的Json解析/序列化操作放到单独的IO线程或线程池中避免阻塞网络事件循环。5.5 中文等Unicode字符处理问题现象包含中文的Json字符串解析后显示乱码或者序列化后中文变成了\uXXXX形式的转义序列。原因与解决JsonCpp默认使用UTF-8编码。确保你的源代码文件保存为UTF-8编码无BOM。你传入的字符串是UTF-8编码的std::string。如果从其他编码如GBK转换而来需要先进行转码。输出时Json::Value中的字符串就是UTF-8。如果你在Windows控制台打印而控制台是GBK编码就会显示乱码。这是输出环境的问题不是JsonCpp的问题。序列化后的Json文本中的\uXXXX是合法的Json Unicode转义序列任何合规的Json解析器都能正确还原。验证你可以将序列化后的字符串写入文件然后用支持UTF-8的文本编辑器如VS Code、Notepad打开应该能看到正确的中文。6. 封装后的工具类在框架中的定位至此我们的JsonUtil工具类已经具备了基本能力。它在我们正在构建的Json-Rpc框架中将扮演一个协议层核心组件的角色。想象一下数据流网络层我们上一弹搭建的收到一个字节流将其组装成一个完整的字符串可能是一个Json-Rpc请求。网络层将这个字符串交给协议解析器这部分逻辑可以放在一个ProtocolHandler类里它会调用JsonUtil::parseRawRequest。解析成功后得到一个结构化的JsonRpcRequest对象包含了方法名、参数和ID。这个请求对象被传递给方法调度器MethodDispatcher由它根据方法名找到对应的函数并执行传入参数得到结果。执行结果或错误被传递给响应构建器调用JsonUtil::buildSuccessResponse或buildErrorResponse生成一个Json::Value格式的响应。响应再通过JsonUtil::responseToString序列化为字符串交还给网络层发送出去。JsonUtil屏蔽了底层JsonCpp的API细节提供了与Json-Rpc协议强相关的、类型更安全的接口。这使得网络层、调度层的代码可以更干净只需要关注它们自己的职责。在下一弹中我们将着手实现方法注册与调度的核心机制。我们会设计一个可以将C函数或方法、lambda注册为RPC方法的调度中心并利用刚刚封装好的JsonUtil来提取参数、调用函数、包装结果。届时这个框架的雏形将真正显现出来。