C++轻量级Json-RPC框架实现:基于muduo与JsonCpp的高性能网络通信

📅 发布时间:2026/7/16 8:05:37
C++轻量级Json-RPC框架实现:基于muduo与JsonCpp的高性能网络通信 1. 项目概述与核心价值最近在整理过往的项目代码翻到了一个几年前自己动手实现的C Json-Rpc框架。当时市面上成熟的C RPC方案不少像gRPC、Thrift功能强大但依赖复杂学习曲线陡峭。对于很多中小型项目尤其是那些对性能有要求但又希望开发简单的场景一个轻量、自包含、能快速上手的RPC框架就显得很有吸引力。这个项目就是基于这样的想法诞生的用C、JsonCpp和muduo网络库打造一个简单、易用、高性能的RPC通信框架。它的核心目标是让开发者哪怕对网络编程细节不那么熟悉也能快速构建起服务间的远程调用能力专注于业务逻辑本身。这个框架完整实现了RPC的核心流程服务注册与发现、基于JSON的序列化与反序列化、网络通信、以及同步/异步调用机制。它特别适合用在游戏服务器、物联网后台、微服务架构中的内部通信模块或者任何你需要将C单体应用拆分为多个松耦合服务进程的场景。通过这个项目你不仅能获得一个可直接集成使用的工具更能深入理解RPC框架从协议设计到网络通信的每一个技术细节这对于提升系统设计能力大有裨益。接下来我会分几个部分详细拆解这个框架的设计思路、核心实现以及那些在文档里不会写的“踩坑”经验。2. 整体架构设计与技术选型解析2.1 为什么选择Json-Rpc协议在决定实现一个RPC框架时第一个要面对的就是通信协议的选择。常见的二进制协议如Protocol Buffers、MessagePack效率极高但需要预定义IDL接口描述语言并编译生成代码在快速迭代或动态接口的场景下不够灵活。而JSON作为一种纯文本、自描述的格式虽然序列化后体积稍大、解析开销略高但其人类可读、无需编译、与多种语言天然兼容尤其是Web前端的特性在开发效率和调试便利性上优势巨大。Json-Rpc是一个基于JSON的轻量级远程过程调用协议。它规范简单一个请求通常包含jsonrpc版本、method方法名、params参数和id请求ID这几个字段响应则包含jsonrpc、result结果/error错误、id。这种清晰的格式使得协议的实现和调试都非常直观。对于内部系统、管理后台、或对极致性能非首要考量的服务间通信Json-Rpc在开发效率与性能之间取得了很好的平衡。我们的框架严格遵循Json-Rpc 2.0规范确保了与其他语言实现的互操作性。2.2 核心组件与依赖库选型一个完整的RPC框架可以抽象为几个核心层传输层负责网络字节流的收发协议层负责将字节流解析为结构化消息Json-Rpc报文序列化层负责将方法名、参数等内存对象与JSON格式互相转换代理层负责在客户端生成调用接口在服务端调用实际函数。基于这个分层模型我们做了如下技术选型网络库muduo。这是陈硕老师开发的一个基于Reactor模式的高性能C网络库。它采用one loop per thread模型充分利用多核并且完全屏蔽了底层socket、epoll等复杂细节让我们可以专注于业务逻辑。它的非阻塞IO和事件回调机制非常适合构建高并发的RPC服务器。相比于直接使用boost::asiomuduo的接口对C开发者更友好文档和社区资源也足够丰富。JSON库JsonCpp。这是一个老牌且稳定的C JSON解析与生成库。API直观如Json::Value可以方便地表示任何JSON类型Json::Reader/Json::FastWriter用于解析和生成。虽然其性能不是最快的但稳定性和易用性经过长期考验足以满足大多数场景。我们也考虑了rapidjson它性能更强但API略显晦涩且容易引发内存问题对于框架的稳定性和易用性目标JsonCpp是更稳妥的选择。核心框架标准C11及以上。充分利用了std::function、std::bind、std::unordered_map、智能指针std::shared_ptr、std::unique_ptr等现代C特性来管理回调、服务注册和资源生命周期让代码更安全、更简洁。整个架构的工作流程简述如下客户端通过生成的Stub存根调用一个像本地函数一样的方法Stub将调用信息方法名、参数序列化为Json-Rpc请求报文通过muduo TcpConnection发送到服务器服务器端的Dispatcher分发器根据方法名在注册表中找到对应的服务和方法反序列化参数后执行再将结果或异常序列化为Json-Rpc响应报文传回客户端收到响应后反序列化结果并返回给调用者或执行预设的回调函数。2.3 同步与异步调用模型设计为了适应不同的业务场景框架必须支持两种调用模式同步调用客户端发起调用后当前线程会阻塞直到收到服务器响应或超时。这是最直观的模式逻辑简单适用于顺序执行的业务流。异步调用客户端发起调用后立即返回不会阻塞当前线程。当服务器响应返回时再通过预先注册的回调函数Callback来处理结果。这对于高并发、IO密集型的应用至关重要可以避免线程无谓等待极大提升吞吐量。在实现上同步调用可以通过在异步调用的基础上结合std::future和std::promise或者简单的条件变量来实现“等待-通知”机制。而异步调用的核心则是利用std::function来保存用户传入的回调函数并与每个请求唯一的id关联起来存放在一个全局的PendingCalls映射表中。当网络线程收到对应id的响应时就从表中取出并执行对应的回调。注意异步回调的执行线程环境需要仔细设计。通常网络IO线程muduo的EventLoop线程不应该执行可能耗时的用户回调否则会阻塞其他网络事件的处理。最佳实践是在网络线程中只完成响应的解析和回调的查找然后将实际的回调函数对象通过EventLoop::queueInLoop方法投递到专门的业务线程或调用方指定的线程中去执行实现线程安全。3. 核心模块实现细节拆解3.1 网络通信层封装与连接管理网络层基于muduo构建其核心是RpcServer和RpcClient两个类。RpcServer内部封装了一个muduo::net::TcpServer在构造函数中设置连接建立和消息到达的回调。class RpcServer { public: RpcServer(muduo::net::EventLoop* loop, const muduo::net::InetAddress listenAddr) : server_(loop, listenAddr, RpcServer) { server_.setConnectionCallback( std::bind(RpcServer::onConnection, this, _1)); server_.setMessageCallback( std::bind(RpcServer::onMessage, this, _1, _2, _3)); } void start() { server_.start(); } void registerService(std::shared_ptrRpcService service); // 服务注册 private: void onConnection(const muduo::net::TcpConnectionPtr conn); void onMessage(const muduo::net::TcpConnectionPtr conn, muduo::net::Buffer* buf, muduo::Timestamp time); muduo::net::TcpServer server_; std::unordered_mapstd::string, std::shared_ptrRpcService services_; // 服务名到服务的映射 };onMessage是处理网络字节流的关键。由于TCP是流式协议一次read可能收到半个、一个或多个完整的Json-Rpc报文。因此我们需要在应用层设计一个简单的“解包”协议。这里采用了常见的“长度内容”的格式即在每个JSON报文前加一个4字节的头部表示后续JSON内容的长度网络字节序。void RpcServer::onMessage(const muduo::net::TcpConnectionPtr conn, muduo::net::Buffer* buf, muduo::Timestamp) { while (buf-readableBytes() kHeaderLen) { // kHeaderLen 4 const void* data buf-peek(); int32_t be32 *static_castconst int32_t*(data); // 从网络缓冲区读取长度 const int32_t len muduo::net::sockets::networkToHost32(be32); // 转换为主机字节序 if (len 65536 || len 0) { // 简单的长度校验防止恶意数据 // 错误处理关闭连接 conn-shutdown(); break; } else if (buf-readableBytes() len kHeaderLen) { buf-retrieve(kHeaderLen); // 跳过头部长度字段 muduo::string message(buf-peek(), len); // 取出JSON字符串 // 将message交给协议层处理 handleRpcRequest(conn, message); buf-retrieve(len); // 从缓冲区移除已处理数据 } else { break; // 数据还不够一个完整包等待下次数据到达 } } }RpcClient的实现类似也需要处理粘包拆包并且维护一个到服务器的TcpConnection。此外客户端还需要一个重要的组件CallManager用于管理所有已发出但未收到响应的请求PendingCalls将请求id与对应的回调函数或同步等待器关联起来。3.2 协议编解码与序列化实现协议层的工作是将网络层交付的原始字符串与内存中的RpcMessage结构体进行转换。我们定义了一个RpcMessage类包含jsonrpc,method,params,id,result,error等字段。编解码器Codec的核心是两个静态方法serialize和parse。serialize将一个RpcMessage对象通过JsonCpp的Json::Value构建成JSON对象再用Json::FastWriter转换成字符串最后在前面拼上4字节的长度头。parse将收到的字符串已去掉长度头交给Json::Reader解析成Json::Value然后逐一提取字段填充到RpcMessage对象中并进行基本的格式校验例如检查jsonrpc字段是否为“2.0”。序列化/反序列化的一个难点在于处理C复杂类型与JSON的映射。对于基础类型int, double, bool, string和它们的std::vector可以编写特化的模板函数。例如templatetypename T typename std::enable_ifstd::is_arithmeticT::value, void::type convertToJson(Json::Value jsonVal, const T cppVal) { jsonVal Json::Value(cppVal); } templatetypename T void convertToJson(Json::Value jsonVal, const std::vectorT cppVec) { jsonVal Json::Value(Json::arrayValue); for (const auto item : cppVec) { Json::Value elem; convertToJson(elem, item); // 递归处理元素类型 jsonVal.append(elem); } } // 还需要对应的从Json::Value转换回C类型的convertFromJson系列函数。对于自定义结构体可以通过宏或模板特化要求用户为其提供专门的转换函数。这是框架易用性上的一个权衡点完全自动化的序列化需要复杂的反射机制而手动指定则增加了用户负担但实现简单。本框架目前采用后者为常见标准库容器提供了支持自定义结构需用户适配。3.3 服务注册、发现与调用分发机制服务端的核心是RpcService基类和ServiceRegistry服务注册表。任何希望被远程调用的服务都需要继承自RpcService并实现一个dispatch方法。用户需要在服务实现中将方法名与对应的std::function进行绑定。class CalculatorService : public RpcService { public: CalculatorService() { // 注册方法 addMethod(add, std::bind(CalculatorService::add, this, _1, _2)); addMethod(multiply, std::bind(CalculatorService::multiply, this, _1, _2)); } void add(const Json::Value params, RpcResponseCallback done) { double a params[0].asDouble(); double b params[1].asDouble(); Json::Value result a b; done(result); // 调用回调返回结果 } // ... multiply方法类似 };ServiceRegistry是一个单例或由RpcServer持有的对象它维护了一个std::mapstd::string, MethodCallback。当RpcServer的handleRpcRequest收到一个请求后它会解析出method字段例如Calculator.add然后以服务名Calculator为键从ServiceRegistry中找到对应的CalculatorService实例再让该实例根据方法名add去调用具体的函数。客户端的Stub生成是另一个关键。理想情况下我们希望有一个IDL编译器根据接口定义自动生成客户端的代理类代码。在这个轻量级框架中我们采用了一种“半自动”的方式通过宏或模板辅助用户快速编写Stub类。Stub类内部持有一个RpcChannel通信通道的指针每个远程方法都被实现为一个封装了序列化和网络发送的成员函数。class CalculatorServiceStub { public: explicit CalculatorServiceStub(RpcChannel* channel) : channel_(channel) {} // 同步调用 double add(double a, double b) { Json::Value params(Json::arrayValue); params.append(a); params.append(b); Json::Value result channel_-callMethodSync(Calculator.add, params); return result.asDouble(); } // 异步调用 void addAsync(double a, double b, const ResponseCallbackdouble done) { Json::Value params(Json::arrayValue); params.append(a); params.append(b); channel_-callMethodAsync(Calculator.add, params, [done](const Json::Value response) { done(response.asDouble()); }); } private: RpcChannel* channel_; };RpcChannel是客户端通信的抽象它封装了与RpcClient的交互负责生成唯一的请求id、管理PendingCalls、以及处理超时。对于异步调用callMethodAsync会立即返回对于同步调用callMethodSync内部会创建一个std::promise等待对应的future在收到响应时被设置值或者超时抛出异常。4. 关键问题排查与性能优化实践4.1 粘包拆包与协议健壮性如前所述TCP粘包拆包是网络编程的必考题。我们采用了“长度头”的方案这是最通用和清晰的方法。但在实际中还需要注意字节序长度头必须在网络传输前转换为网络字节序大端在接收端转换回主机字节序。我们使用muduo::net::sockets::hostToNetwork32和networkToHost32函数。缓冲区管理muduo的Buffer类已经帮我们做了高效的内存管理。关键在于onMessage中的循环必须持续处理直到缓冲区中的数据不足以构成一个完整报文。恶意数据防护必须对长度字段len进行合理性校验。比如限制最大长度如1MB防止对方发送一个巨大的长度值耗尽服务器内存。一旦校验失败应立即断开连接。4.2 线程模型与并发安全muduo默认是one loop per thread即每个EventLoop对象运行在一个独立的线程中。通常我们会为RpcServer创建一个主EventLoop通常绑定主线程用于接受新连接Acceptor而使用一个EventLoopThreadPool事件循环线程池来处理已建立连接的IO事件。这样可以利用多核。线程安全规则需要明确RpcServer的registerService必须在服务器启动前在创建它的线程通常是主线程中完成。启动后不应再修改服务注册表。服务方法的执行当IO线程EventLoop线程调用到用户注册的服务方法如CalculatorService::add时这个方法必须保证是线程安全的或者其执行不会阻塞IO线程。如果服务方法涉及共享数据或耗时操作最佳实践是在服务方法内部将实际计算任务和done回调封装成一个Task对象。通过EventLoop::queueInLoop或一个专门的线程池将这个Task抛到其他线程去执行。计算完成后在那个线程中调用done回调。但注意done回调最终需要通过网络线程发送数据所以可能需要再次queueInLoop回IO线程或者确保回调里只做非IO操作。客户端的PendingCalls这是一个被多个线程用户调用线程、网络IO线程共享的数据结构。对其的插入发送请求时和查找删除收到响应时操作必须加锁。可以使用std::mutex或者更高效地为每个EventLoop维护一个独立的PendingCalls映射确保每个连接上的请求-响应都在同一个IO线程内处理从而避免锁。4.3 超时、重试与错误处理一个健壮的RPC框架必须处理网络的不确定性。超时机制每个发出的请求都应该关联一个定时器。在RpcChannel::callMethodSync/Async中启动一个Timermuduo提供了定时器支持。如果超时前未收到响应则从PendingCalls中移除该请求并触发超时错误回调或抛出超时异常。定时器的生命周期管理需要小心避免悬空指针。错误处理Json-Rpc协议本身定义了错误对象error字段包含code、message和可选的data。框架需要将底层错误如网络断开、解析失败、超时、服务未找到、方法未找到、参数无效等映射为标准的或自定义的错误码通过error字段返回给客户端。服务端用户也可以在业务逻辑中抛出特定的异常由框架捕获并转换为Json-Rpc错误响应。重试策略对于非幂等的操作如转账框架层面不应自动重试。可以在RpcChannel或更上层的业务代码中根据错误类型如网络超时、连接断开和业务语义实现可配置的重试逻辑。简单的策略可以是“指数退避”重试。4.4 性能优化点实测JSON序列化优化JsonCpp的Json::FastWriter比Json::StyledWriter快很多因为它输出紧凑格式无空格换行。在发布版本中务必使用FastWriter。如果性能瓶颈确实在JSON解析可以考虑集成rapidjson但要做好API封装隔离变化。内存池与对象复用频繁地创建和销毁Json::Value、RpcMessage、甚至网络缓冲区会带来内存分配开销。可以考虑使用对象池Object Pool复用这些对象。例如维护一个Json::Value的池子用于反序列化参数用完后重置并放回池中。零拷贝优化在理想情况下序列化好的JSON字符串应该直接写入muduo的Buffer避免中间的内存拷贝。muduo的Buffer支持append字符串内部可能仍有拷贝。对于极致性能场景可以探索使用Buffer的prepend和append直接组装报文或者使用writev系统调用合并发送长度头和JSON内容。压缩如果传输的JSON报文很大可以考虑在应用层增加压缩如gzip。但这会增加CPU开销需要权衡。可以在RpcMessage中增加一个可选的压缩标志位。5. 框架使用示例与集成指南5.1 快速开始构建一个计算器服务让我们通过一个完整的例子看看如何使用这个框架。第一步定义服务接口头文件虽然框架没有强制IDL但良好的实践是先定义接口。calculator_service.h:// 这是一个抽象接口用于说明服务提供哪些方法 class CalculatorServiceInterface { public: virtual ~CalculatorServiceInterface() default; virtual double add(double a, double b) 0; virtual double multiply(double a, double b) 0; };第二步实现服务服务端calculator_service_impl.h/cpp:#include “rpc_service.h” #include “calculator_service.h” class CalculatorServiceImpl : public RpcService, public CalculatorServiceInterface { public: CalculatorServiceImpl() { // 将RPC方法名绑定到实现函数 addMethod(“add”, std::bind(CalculatorServiceImpl::rpc_add, this, _1, _2)); addMethod(“multiply”, std::bind(CalculatorServiceImpl::rpc_multiply, this, _1, _2)); } // 实现本地接口可选用于本地测试或直接调用 double add(double a, double b) override { return a b; } double multiply(double a, double b) override { return a * b; } private: // RPC处理函数 void rpc_add(const Json::Value params, RpcResponseCallback done) { if (params.size() ! 2 || !params[0].isNumeric() || !params[1].isNumeric()) { // 返回参数错误 done(RpcError(INVALID_PARAMS, “Invalid parameters”)); return; } double a params[0].asDouble(); double b params[1].asDouble(); double result a b; // 这里可以调用本地接口实现 done(Json::Value(result)); } void rpc_multiply(const Json::Value params, RpcResponseCallback done) { // … 类似实现 } };第三步编写服务器主程序server.cpp:#include “rpc_server.h” #include “calculator_service_impl.h” int main() { muduo::net::EventLoop loop; muduo::net::InetAddress listenAddr(9981); RpcServer server(loop, listenAddr, “CalculatorServer”); // 创建并注册服务 std::shared_ptrCalculatorServiceImpl calcService std::make_sharedCalculatorServiceImpl(); server.registerService(“Calculator”, calcService); // “Calculator” 是服务名 server.start(); loop.loop(); // 进入事件循环 return 0; }第四步生成客户端存根Stub并调用client.cpp:#include “rpc_client.h” #include “calculator_service_stub.h” // 需要手动或通过工具生成 int main() { muduo::net::EventLoop loop; muduo::net::InetAddress serverAddr(“127.0.0.1”, 9981); RpcClient client(loop, serverAddr); client.connect(); // 建立连接 // 创建通信通道和存根 RpcChannel channel(client); CalculatorServiceStub stub(channel); // 同步调用 try { double sum stub.add(3.14, 2.86); std::cout “3.14 2.86 “ sum std::endl; // 输出 6.0 } catch (const RpcException e) { std::cerr “RPC call failed: “ e.what() std::endl; } // 异步调用 stub.addAsync(5, 7, [](double result) { std::cout “Async result: 5 7 “ result std::endl; }); // 保持事件循环运行以接收异步响应 loop.runAfter(5.0, std::bind(muduo::net::EventLoop::quit, loop)); // 5秒后退出 loop.loop(); return 0; }5.2 项目构建与依赖管理这个框架依赖于muduo和JsonCpp。建议使用CMake进行构建管理。安装依赖JsonCpp: 可以通过系统包管理器安装如sudo apt-get install libjsoncpp-dev或从源码编译。muduo: 需要从github克隆并编译安装。注意muduo依赖C11且其CMake配置可能需要调整安装路径。CMakeLists.txt示例cmake_minimum_required(VERSION 3.10) project(JsonRpcFramework) set(CMAKE_CXX_STANDARD 11) # 查找依赖 find_package(JsonCpp REQUIRED) find_package(Threads REQUIRED) # muduo需要pthread # 假设muduo安装在 /usr/local include_directories(/usr/local/include) link_directories(/usr/local/lib) # 添加框架源码 add_library(json_rpc_framework STATIC src/rpc_server.cpp src/rpc_client.cpp src/rpc_channel.cpp src/codec.cpp src/service_registry.cpp ) target_link_libraries(json_rpc_framework ${JSONCPP_LIBRARIES} muduo_net muduo_base ${CMAKE_THREAD_LIBS_INIT} ) # 添加示例程序 add_executable(calculator_server examples/server.cpp) target_link_libraries(calculator_server json_rpc_framework) add_executable(calculator_client examples/client.cpp) target_link_libraries(calculator_client json_rpc_framework)5.3 进阶服务发现与负载均衡在微服务架构中服务端地址可能动态变化。基础的RpcClient只连接一个固定地址。要实现服务发现可以抽象出一个ServiceDiscovery接口。客户端启动时从配置中心如ZooKeeper、Etcd、Consul或DNS获取某个服务名对应的所有实例地址列表并监听变化。RpcChannel内部持有一个ServiceDiscovery对象和一个负载均衡器如随机、轮询、最少连接。每次调用时通过负载均衡器选择一个实例地址然后从连接池中获取或创建一个到该地址的RpcClient进行通信。这部分可以设计为可插拔的模块让框架的适用范围更广。6. 调试技巧与常见问题实录在实际开发和集成这个框架的过程中会遇到各种各样的问题。这里记录一些典型的排查经验和技巧。问题1客户端连接成功但调用后收不到任何响应服务器也没报错。排查思路抓包分析使用tcpdump或Wireshark抓取localhost或对应网卡9981端口的流量。这是最直接有效的方法。查看客户端是否发出了数据发出的数据格式是否正确长度头JSON服务器是否回复了回复的数据是什么日志调试在框架的关键路径添加详细日志特别是在RpcServer::onMessage收到原始数据后、Codec::parse解析后、ServiceRegistry::dispatch分发时、以及服务方法被调用时。对比客户端发送的请求JSON和服务端解析后的method、params是否一致。检查粘包处理确认客户端发送的报文格式是否严格符合“4字节长度头JSON内容”。服务器端的kHeaderLen定义是否正确长度字段的字节序转换是否正确常见原因长度头计算错误客户端计算JSON字符串长度时可能使用了strlen对含中文等多字节字符可能不准或忽略了字符串末尾的\0。应该使用std::string::size()。JSON格式错误手动拼接的JSON字符串可能有语法错误导致服务端Json::Reader解析失败。使用Json::FastWriter生成JSON字符串可以避免此问题。方法名不匹配客户端调用Calculator.add但服务端注册的是add或calculator.add。确保服务名和方法名的大小写、分隔符完全一致。问题2异步调用时回调函数永远不会被执行。排查思路检查EventLoop是否在运行异步回调的执行依赖于muduo的EventLoop。确保在发起异步调用后主线程或某个线程调用了EventLoop::loop()。检查请求ID映射在RpcChannel::callMethodAsync中打印生成的请求id。在服务器响应回来后在RpcClient的响应处理函数中打印收到的响应id。看两者是否能匹配上。匹配不上说明PendingCalls映射可能被错误地清理了比如在超时回调中删除后正常响应才到达。检查超时设置是否设置了过短的超时时间在超时回调中是否正确地调用了用户的错误回调有时超时回调被触发了但用户错误回调没被调用导致用户以为没反应。实操心得为PendingCalls映射的插入和删除操作添加详细的日志记录id、时间点和线程ID对于调试并发问题非常有帮助。问题3服务端在处理高并发请求时响应变慢甚至无响应。排查思路监控系统资源使用top、htop查看CPU、内存使用率。使用vmstat或iostat查看IO等待。可能是某个服务方法本身是CPU密集型或阻塞式IO操作拖慢了整个IO线程。分析线程模型确认服务方法是否在IO线程中执行耗时操作。如果是必须将其转移到业务线程池。检查锁竞争如果服务方法或框架内部如日志模块、某些全局状态使用了锁在高并发下可能成为瓶颈。使用性能分析工具如perf、gprof查找热点。优化建议使用线程池处理业务逻辑IO线程只做编解码和轻量派发。避免在RPC处理函数中调用可能阻塞的系统调用如同步文件读写、某些数据库查询。对于ServiceRegistry这类读多写少的结构可以考虑使用读写锁std::shared_mutexC17或并发容器如folly::ConcurrentHashMap。问题4内存缓慢增长疑似内存泄漏。排查思路使用Valgrindvalgrind --leak-checkfull ./your_server是检测C内存泄漏的黄金标准。重点关注Json::Value、std::shared_ptr循环引用、以及自定义对象池的释放逻辑。检查回调生命周期在异步调用中如果回调函数捕获了通过lambda表达式某些对象的shared_ptr可能会意外延长对象的生命周期。确保回调被执行或超时后被及时清理。检查muduo Buffermuduo的Buffer内部会预留空间不会每次retrieve后都收缩。这是性能优化不是泄漏。但如果连接长期空闲可以考虑在连接上设置一个空闲超时并关闭。个人体会在框架开发中最容易被忽略的是“异常安全”。如果在序列化或网络发送过程中抛出异常一些动态分配的资源可能无法被正确释放。务必使用RAII技术资源获取即初始化或者用智能指针管理所有资源确保异常发生时也能自动清理。这个自研的C Json-Rpc框架虽然功能上比不过工业级的方案但“麻雀虽小五脏俱全”完整走一遍设计实现流程对理解网络编程、并发模型、协议设计有极大的提升。把它用在实际的非核心链路中也完全能够胜任。最大的收获不是代码本身而是在解决上述一个个具体问题过程中积累的经验这些才是往后设计更复杂系统时最宝贵的财富。如果后续有精力可以考虑为其添加IDL编译器、更完善的服务治理特性熔断、限流或者尝试集成更快的序列化方案那又将是一个新的挑战了。