# tinyrpc **Repository Path**: ivan_ye/tinyrpc ## Basic Information - **Project Name**: tinyrpc - **Description**: No description available - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-08-27 - **Last Updated**: 2025-08-27 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ![](https://img.shields.io/github/v/release/Gooddbird/tinyrpc?color=2&label=tinyrpc&logoColor=2&style=plastic) ![GitHub repo size](https://img.shields.io/github/repo-size/Gooddbird/tinyrpc?style=plastic) ![GitHub issues](https://img.shields.io/github/issues/Gooddbird/tinyrpc?style=plastic) ![GitHub pull requests](https://img.shields.io/github/issues-pr/Gooddbird/tinyrpc?style=plastic) ![GitHub forks](https://img.shields.io/github/forks/Gooddbird/tinyrpc?style=plastic) ![GitHub Repo stars](https://img.shields.io/github/stars/Gooddbird/tinyrpc?style=plastic) ![GitHub contributors](https://img.shields.io/github/contributors/Gooddbird/tinyrpc?style=plastic) ![GitHub last commit](https://img.shields.io/github/last-commit/Gooddbird/tinyrpc) 作者:**ikerli** **2022-05-13** **使用 TinyRPC, 轻松地构建高性能分布式 RPC 服务!** - [1. 概述](#1-概述) - [1.1. TinyRPC 特点](#11-tinyrpc-特点) - [1.2. TinyRPC 支持的协议报文](#12-tinyrpc-支持的协议报文) - [1.3. TinyRPC 的 RPC 调用](#13-tinyrpc-的-rpc-调用) - [1.3.1. 阻塞协程式异步调用](#131-阻塞协程式异步调用) - [1.3.2. 非阻塞协程式异步调用](#132-非阻塞协程式异步调用) - [2. 性能测试](#2-性能测试) - [2.1. HTTP echo 测试 QPS](#21-http-echo-测试-qps) - [3. 安装 TinyRPC](#3-安装-tinyrpc) - [3.1. 安装必要的依赖库](#31-安装必要的依赖库) - [3.1.1. protobuf](#311-protobuf) - [3.1.2. tinyxml](#312-tinyxml) - [3.2. 安装和卸载 (makefile)](#32-安装和卸载-makefile) - [3.2.1. 安装 TinyRPC](#321-安装-tinyrpc) - [3.2.2. 卸载 TinyRPC](#322-卸载-tinyrpc) - [3.3. 安装和卸载 (cmake)](#33-安装和卸载-cmake) - [3.3.1. 安装 TinyRPC](#331-安装-tinyrpc) - [3.3.2. 卸载 TinyRPC](#332-卸载-tinyrpc) - [4. 快速上手](#4-快速上手) - [4.1. 搭建基于 TinyPB 协议的 RPC 服务](#41-搭建基于-tinypb-协议的-rpc-服务) - [4.1.1. 实现 Protobuf 文件接口](#411-实现-protobuf-文件接口) - [4.1.2. 准备配置文件](#412-准备配置文件) - [4.1.3. 实现业务接口](#413-实现业务接口) - [4.1.4. 启动 RPC 服务](#414-启动-rpc-服务) - [4.2. 搭建基于 HTTP 协议的 RPC 服务](#42-搭建基于-http-协议的-rpc-服务) - [4.2.1. 准备配置文件](#421-准备配置文件) - [4.2.2. 实现 Servlet 接口](#422-实现-servlet-接口) - [4.2.3. 启动 RPC 服务](#423-启动-rpc-服务) - [4.3. RPC 服务调用](#43-rpc-服务调用) - [4.3.1. 阻塞协程式异步调用](#431-阻塞协程式异步调用) - [4.3.2. 非阻塞协程式异步调用](#432-非阻塞协程式异步调用) - [4.4. TinyRPC 脚手架(tinyrpc_generator)](#44-tinyrpc-脚手架tinyrpc_generator) - [4.4.1 准备 protobuf 文件](#441-准备-protobuf-文件) - [4.4.2 生成 TinyRPC 框架](#442-生成-tinyrpc-框架) - [4.4.3 业务逻辑开发](#443-业务逻辑开发) - [4.4.4 Protobuf 接口升级怎么办?](#444-protobuf-接口升级怎么办) - [4.4.5 tinyrpc_generator 选项详解](#445-tinyrpc_generator-选项详解) - [5. 概要设计](#5-概要设计) - [5.1. 异步日志模块](#51-异步日志模块) - [5.2. 协程模块](#52-协程模块) - [5.2.1. 协程封装](#521-协程封装) - [5.2.2. m:n 线程:协程模型](#522-mn-线程协程模型) - [5.3. Reactor 模块](#53-reactor-模块) - [5.4. Tcp 模块](#54-tcp-模块) - [5.4.1. TcpServer](#541-tcpserver) - [5.4.2. TcpConnection](#542-tcpconnection) - [5.5. TinyPB 协议](#55-tinypb-协议) - [5.5.1. TinyPB 协议报文格式分解](#551-tinypb-协议报文格式分解) - [5.6. Http 模块](#56-http-模块) - [5.7. RPC 调用封装](#57-rpc-调用封装) - [6. 错误码](#6-错误码) - [6.1. 错误码判断规范](#61-错误码判断规范) - [6.2. 错误码释义文档](#62-错误码释义文档) - [7. 问题反馈](#7-问题反馈) - [8. 参考资料](#8-参考资料) # 1. 概述 ## 1.1. TinyRPC 特点 **TinyRPC** 是一款基于 **C++11** 标准开发的小型**异步 RPC** 框架。TinyRPC 的核心代码应该也就几千行样子,尽量保持了简洁且较高的易读性。 麻雀虽小五脏俱全,从命名上就能看出来,TinyRPC 框架主要用义是为了让读者能**快速地**、**轻量化**地搭建出具有较高性能的异步RPC 服务。至少用 TinyRPC 搭建的 RPC 服务能应付目前大多数场景了。 **TinyRPC** 没有实现跨平台,只支持 Linux 系统,并且必须是 64 位的系统,因为协程切换只实现了 **64** 位系统的代码,而没有兼容 **32** 位系统。这是有意的,因为作者只会 Linux 下开发,没能力做到跨平台。 **TinyRPC** 的核心思想有两个: 1. 让搭建高性能 RPC 服务变得简单 2. 让异步调用 RPC 变得简单 必须说明的是, **TinyRPC** 代码没有达到工业强度,最好不要直接用到生产环境,也可能存在一些未知 BUG,甚至 coredump。读者请自行辨别,谨慎使用! ## 1.2. TinyRPC 支持的协议报文 **TinyRPC** 框架目前支持两类协议: 1. 纯 **HTTP** 协议: TinyRPC 实现了简单的很基本的 HTTP(1.1) 协议的编、解码,完全可以使用 HTTP 协议搭建一个 RPC 服务。 2. TinyPB 协议: 一种基于 **Protobuf** 的自定义协议,属于二进制协议。 ## 1.3. TinyRPC 的 RPC 调用 TinyRPC 是一款异步的 RPC 框架,这就意味着服务之前的调用是非常高效的。目前来说,TinyRPC 支持两种RPC 调用方式:**阻塞协程式异步调用** 和 **非阻塞协程式异步调用**。 ### 1.3.1. 阻塞协程式异步调用 阻塞协程式异步调用这个名字看上去很奇怪,阻塞像是很低效的做法。然而其实他是非常高效的。他的思想是**用同步的代码,实现异步的性能。** 也就是说,**TinyRPC** 在 RPC 调用时候不需要像其他异步操作一样需要写复杂的回调函数,只需要直接调用即可。这看上去是同步的过程,实际上由于内部的协程封装实现了完全的异步。而作为外层的使用者完全不必关系这些琐碎的细节。 阻塞协程式异步调用对应 TinyPbRpcChannel 类,一个简单的调用例子如下: ```c++ tinyrpc::TinyPbRpcChannel channel(std::make_shared("127.0.0.1", 39999)); QueryService_Stub stub(&channel); tinyrpc::TinyPbRpcController rpc_controller; rpc_controller.SetTimeout(10000); DebugLog << "RootHttpServlet begin to call RPC" << count; stub.query_name(&rpc_controller, &rpc_req, &rpc_res, NULL); DebugLog << "RootHttpServlet end to call RPC" << count; ``` 这看上去跟普通的阻塞式调用没什么区别,然而实际上在 stub.query_name 这一行是完全异步的,简单来说。线程不会阻塞在这一行,而会转而去处理其他协程,只有当数据返回就绪时,query_name 函数自动返回,继续下面的操作。 这个过程的执行流如图所示: ![](./imgs/block_async_call.drawio.png) 从图中可以看出,在调用 query_name 到 query_name 返回这段时间 T,CPU 的执行权已经完全移交给主协程了,也就说是这段时间主协程可以用来做任何事情:包括响应客户端请求、执行定时任务、陷入 epoll_wait 等待事件就绪等。对单个协程来说,它的执行流被阻塞了。但对于整个线程来说是完全没有被阻塞,它始终在执行着任务。 另外这个过程完全没有注册回调函数、另起线程之类的操作,可它确确实实达到异步了。这也是 **TinyRPC** 的核心思想之一。 这种调用方式是 TinyRPC 推荐的方式,它的优点如下: 1. 代码实现很简单,直接同步式调用,不需要写回调函数。 2. 对IO线程数没有限制,**即使只有 1 个 IO 线程**,仍然能达到这种效果。 3. 对于线程来说,他是**不会阻塞线程**的。 当然,它的缺点也存在: 1. 对于**当前协程来说,他是阻塞的**,必须等待协程再次被唤醒(**RESUME**)才能执行下面的代码。 ### 1.3.2. 非阻塞协程式异步调用 **非阻塞协程式异步调用**是 TinyRPC 支持的另一种 RPC 调用方式,它解决了**阻塞协程式异步调用** 的一些缺点,当然也同时引入了一些限制。这种方式有点类似于 C++11 的 future 特性, 但也不完全一样。 非阻塞协程式异步调用对应 TinyPbRpcAsyncChannel,一个简单调用例子如下: ```c++ { std::shared_ptr rpc_req = std::make_shared(); std::shared_ptr rpc_res = std::make_shared(); AppDebugLog << "now to call QueryServer TinyRPC server to query who's id is " << req->m_query_maps["id"]; rpc_req->set_id(std::atoi(req->m_query_maps["id"].c_str())); std::shared_ptr rpc_controller = std::make_shared(); rpc_controller->SetTimeout(10000); tinyrpc::IPAddress::ptr addr = std::make_shared("127.0.0.1", 39999); tinyrpc::TinyPbRpcAsyncChannel::ptr async_channel = std::make_shared(addr); async_channel->saveCallee(rpc_controller, rpc_req, rpc_res, nullptr); QueryService_Stub stub(async_channel.get()); stub.query_age(rpc_controller.get(), rpc_req.get(), rpc_res.get(), NULL); } ``` 注意在这种调用方式中,query_age 会立马返回,协程 C1 可以继续执行下面的代码。但这并不代表着调用 RPC 完成,如果你需要获取调用结果,请使用: ```c++ async_channel->wait(); ``` 此时协程 C1 会阻塞直到异步 RPC 调用完成,注意只会阻塞当前协程 C1,而不是当前线程(其实调用 wait 后就相当于把当前协程 C1 Yiled 了,等待 RPC 完成后自动 Resume)。 当然,wait() 是可选的。如果你不关心调用结果,完全可以不调用 wait。即相当于一个**异步的任务队列**。 这种调用方式的原理很简单,会新生成一个协程 C2 去处理这次 RPC 调用,把这个协程 C2 加入调度池任务里面,而原来的协程 C1 可以继续往下执行。 新协程 C2 会在适当的时候被IO线程调度(可能是IO线程池里面任意一个 IO线程), 当 RPC 调用完成后,会唤醒原协程 C1 通知调用完成(前提是 C1 中调用了 wait 等待结果)。 这个调用链路如图: ![](./imgs/nonblock_async_call.drawio.png) 总之,非阻塞协程式异步调用的优点如下: 1. RPC 调用不阻塞当前协程 C1,C1 可以继续往下执行代码(若遇到 wait 则会阻塞)。 而缺点如下: 1. 所有 RPC 调用相关的对象,**必须是堆上的对象,而不是栈对象**, 包括 req、res、controller、async_rpc_channel。强烈推荐使用 shared_ptr,否则可能会有意想不到的问题(基本是必须使用了)。 2. 在 RPC 调用前必须调用 TinyPbRpcAsyncChannel::saveCallee(), 提前预留资源的引用计数。实际上是第1点的补充,相当于强制要求使用 shared_ptr 了。 解释一下第一点:调用相关的对象是在线程 A 中声明的,但由于是异步 RPC 调用,整个调用过程是又另外一个线程 B 执行的。因此你必须确保当线程 B 在这些 RPC 调用的时候,这些对象还存在,即没有被销毁。 那为什么不能是栈对象?想像一下,假设你在某个函数中异步调用 RPC,如果这些对象都是栈对象,那么当函数结束时这些栈对象自动被销毁了,线程 B 此时显然会 coredump 掉。因此请在堆上申请对象。另外,推荐使用 shared_ptr 是因为 TinyPbRpcAsyncChannel 内部已经封装好细节了,当异步 RPC 完成之后会自动销毁对象,你不必担心内存泄露的问题! # 2. 性能测试 TinyRPC 底层使用的是 Reactor 架构,同时又结合了多线程,其性能是能得到保障的。进行几个简单的性能测试结果如下: ## 2.1. HTTP echo 测试 QPS 测试机配置信息:Centos**虚拟机**,内存**6G**,CPU为**4核** 测试工具:**wrk**: https://github.com/wg/wrk.git 部署信息:wrk 与 TinyRPC 服务部署在同一台虚拟机上, 关闭 TinyRPC 日志 测试命令: ``` // -c 为并发连接数,按照表格数据依次修改 wrk -c 1000 -t 8 -d 30 --latency 'http://127.0.0.1:19999/qps?id=1' ``` 测试结果: | **QPS** | **WRK 并发连接 1000** | **WRK 并发连接 2000** | **WRK 并发连接 5000** | **WRK 并发连接 10000** | | ---- | ---- | ---- | ---- | ---- | | IO线程数为 **1** | **27000 QPS** | **26000 QPS** | **20000 QPS** |**20000 QPS** | | IO线程数为 **4** | **140000 QPS** | **130000 QPS** | **123000 QPS**| **118000 QPS** | | IO线程数为 **8** | **135000 QPS** | **120000 QPS**| **100000 QPS**| **100000 QPS** | | IO线程数为 **16** | **125000 QPS** | **127000 QPS** |**123000 QPS** | **118000 QPS** | ``` // IO 线程为 4, 并发连接 1000 的测试结果 [ikerli@localhost bin]$ wrk -c 1000 -t 8 -d 30 --latency 'http://127.0.0.1:19999/qps?id=1' Running 30s test @ http://127.0.0.1:19999/qps?id=1 8 threads and 1000 connections Thread Stats Avg Stdev Max +/- Stdev Latency 9.79ms 63.83ms 1.68s 99.24% Req/Sec 17.12k 8.83k 97.54k 72.61% Latency Distribution 50% 4.37ms 75% 7.99ms 90% 11.65ms 99% 27.13ms 4042451 requests in 30.07s, 801.88MB read Socket errors: connect 0, read 0, write 0, timeout 205 Requests/sec: 134442.12 Transfer/sec: 26.67MB ``` 由以上测试结果,**TinyRPC 框架的 QPS 可达到 14W 左右**。 # 3. 安装 TinyRPC ## 3.1. 安装必要的依赖库 要正确编译 **TinyRPC**, 至少要先安装这几个库: ### 3.1.1. protobuf **protobuf** 是 **google** 开源的有名的序列化库。谷歌出品,必属精品!**TinyRPC** 的 **TinyPB** 协议是基于 protobuf 来 序列化/反序列化 的,因此这个库是必须的。 其地址为:https://github.com/protocolbuffers/protobuf 推荐安装版本 **3.19.4** 及以上。安装过程不再赘述, **注意将头文件和库文件 copy 到对应的系统路径下。** ### 3.1.2. tinyxml 由于 **TinyRPC** 读取配置使用了 **xml** 文件,因此需要安装 **tinyxml** 库来解析配置文件。 下载地址:https://sourceforge.net/projects/tinyxml/ 要生成 libtinyxml.a 静态库,需要简单修改 makefile 如下: ``` # 84 行修改为如下 OUTPUT := libtinyxml.a # 194, 105 行修改如下 ${OUTPUT}: ${OBJS} ${AR} $@ ${LDFLAGS} ${OBJS} ${LIBS} ${EXTRA_LIBS} ``` 安装过程如下: ``` cd tinyxml make -j4 # copy 库文件到系统库文件搜索路径下 cp libtinyxml.a /usr/lib/ # copy 头文件到系统头文件搜索路径下 mkdir /usr/include/tinyxml cp *.h /usr/include/tinyxml ``` ## 3.2. 安装和卸载 (makefile) ### 3.2.1. 安装 TinyRPC 在安装了前置的几个库之后,就可以开始编译和安装 **TinyRPC** 了。安装过程十分简单,只要不出什么意外就好了。 **祈祷**一下一次性成功,然后直接执行以下几个命令即可: ``` git clone https://github.com/Gooddbird/tinyrpc cd tinyrpc mkdir bin && mkdir lib && mkdir obj // 生成测试pb桩文件 cd testcases protoc --cpp_out=./ test_tinypb_server.proto cd .. // 先执行编译 make -j4 // 编译成功后直接安装就行了 make install ``` 注意, make install 完成后,默认会在 **/usr/lib** 路径下安装 **libtinyrpc.a** 静态库文件,以及在 **/usr/include/tinyrpc** 下安装所有的头文件。 如果编译出现问题,欢迎提 [issue](https://github.com/Gooddbird/tinyrpc/issues/), 我会尽快回应。 ### 3.2.2. 卸载 TinyRPC 卸载也很简单,如下即可: ``` make uninstall ``` **注:如果此前已经安装过 TinyRPC, 建议先执行卸载命令后再重新 make install 安装.** ## 3.3. 安装和卸载 (cmake) ### 3.3.1. 安装 TinyRPC ```shell $ git clone https://github.com/Gooddbird/tinyrpc # 需要先生成 pb 文件 $ cd tinyrpc/testcases $ protoc --cpp_out=./ test_tinypb_server.proto $ cd .. $ mkdir bin && mkdir lib && mkdir build # 安装 $ sudo ./build.sh ``` `build.sh` 也是通过 `cmake` 安装的,当然你也可以手动通过 `cmake` 去创建 ### 3.3.2. 卸载 TinyRPC ```shell $ sudo rm -rf /usr/include/tinyrpc/ $ sudo rm -rf /usr/lib/libtinyrpc.a # 如果没有更改 makefile 中和 CMakeLists 中的 头文件 和 静态库 的存储路径的话,也可以直接执行:make uninstall ``` # 4. 快速上手 ## 4.1. 搭建基于 TinyPB 协议的 RPC 服务 ### 4.1.1. 实现 Protobuf 文件接口 TinyPB 协议基于 Protobuf 来序列化的,在搭建基于 TinyPB 协议的 RPC 服务之前,需要先定义接口文档。具体的 Protobuf 文档需要根据业务的实际功能来编写,这里给出一个例子如下: ```.c++ // test_tinypb_server.proto syntax = "proto3"; option cc_generic_services = true; message queryAgeReq { int32 req_no = 1; int32 id = 2; } message queryAgeRes { int32 ret_code = 1; string res_info = 2; int32 req_no = 3; int32 id = 4; int32 age = 5; } message queryNameReq { int32 req_no = 1; int32 id = 2; int32 type = 3; } message queryNameRes { int32 ret_code = 1; string res_info = 2; int32 req_no = 3; int32 id = 4; string name = 5; } service QueryService { // rpc method name rpc query_name(queryNameReq) returns (queryNameRes); // rpc method name rpc query_age(queryAgeReq) returns (queryAgeRes); } ``` 使用 protoc 工具生成对应的 C++ 代码: ``` protoc --cpp_out=./ test_tinypb_server.proto ``` ### 4.1.2. 准备配置文件 **TinyRPC** 读取标准的 **xml** 配置文件完成一些服务初始化设置,这个配置文件模板如下,一般只需要按需调整参数即可: ```xml ./ test_tinypb_server 5 DEBUG DEBUG 500 256 1000 20 75 8 6 10 127.0.0.1 39999 TinyPB ``` ### 4.1.3. 实现业务接口 protobuf 文件提供的只是接口说明,而实际的业务逻辑需要自己实现。只需要继承 QueryService 并重写方法即可,例如: ```c++ // test_tinypb_server.cc class QueryServiceImpl : public QueryService { public: QueryServiceImpl() {} ~QueryServiceImpl() {} void query_age(google::protobuf::RpcController* controller, const ::queryAgeReq* request, ::queryAgeRes* response, ::google::protobuf::Closure* done) { AppInfoLog << "QueryServiceImpl.query_age, req={"<< request->ShortDebugString() << "}"; response->set_ret_code(0); response->set_res_info("OK"); response->set_req_no(request->req_no()); response->set_id(request->id()); response->set_age(100100111); if (done) { done->Run(); } AppInfoLog << "QueryServiceImpl.query_age, res={"<< response->ShortDebugString() << "}"; } }; ``` ### 4.1.4. 启动 RPC 服务 TinyRPC 服务启动非常简单,只需寥寥几行代码即可: ```c++ int main(int argc, char* argv[]) { if (argc != 2) { printf("Start TinyRPC server error, input argc is not 2!"); printf("Start TinyRPC server like this: \n"); printf("./server a.xml\n"); return 0; } // 1. 读取配置文件 tinyrpc::InitConfig(argv[1]); // 2. 注册 service REGISTER_SERVICE(QueryServiceImpl); // 3. 启动 RPC 服务 tinyrpc::StartRpcServer(); return 0; } ``` 生成可执行文件 **test_tinypb_server** 后,启动命令如下: ``` nohup ./test_tinypb_server ../conf/test_tinypb_server.xml & ``` 如果没什么报错信息,那么恭喜你启动成功了。如果不放心,可以使用 ps 命令查看进程是否存在: ``` ps -elf | grep 'test_tinypb_server' ``` 或者使用 netstat 命令查看端口是否被监听: ``` netstat -tln | grep 39999 ``` 至此,基于 TinyPB 协议的 RPC 服务已经启动成功,后续我们将调用这个服务。 ## 4.2. 搭建基于 HTTP 协议的 RPC 服务 ### 4.2.1. 准备配置文件 同上,准备一个配置文件 **test_http_server.xml**: ```xml ./ test_http_server 5 DEBUG DEBUG 500 128 1000 20 75 4 3 10 127.0.0.1 19999 HTTP ``` ### 4.2.2. 实现 Servlet 接口 **TinyRPC** 提供类似 JAVA 的 **Servlet** 接口来实现 HTTP 服务。你只需要简单的继承 HttpServlet 类并实现 handle 方法即可,如一个 HTTP 的 echo 如下: ```c++ // test_http_server.cc class QPSHttpServlet : public tinyrpc::HttpServlet { public: QPSHttpServlet() = default; ~QPSHttpServlet() = default; void handle(tinyrpc::HttpRequest* req, tinyrpc::HttpResponse* res) { AppDebugLog << "QPSHttpServlet get request"; setHttpCode(res, tinyrpc::HTTP_OK); setHttpContentType(res, "text/html;charset=utf-8"); std::stringstream ss; ss << "QPSHttpServlet Echo Success!! Your id is," << req->m_query_maps["id"]; char buf[512]; sprintf(buf, html, ss.str().c_str()); setHttpBody(res, std::string(buf)); AppDebugLog << ss.str(); } std::string getServletName() { return "QPSHttpServlet"; } }; ``` ### 4.2.3. 启动 RPC 服务 将 Servlet 注册到路径下,启动 RPC 服务即可。注意这个注册路径相对于项目的根路径而言: ```c++ // test_http_server.cc int main(int argc, char* argv[]) { if (argc != 2) { printf("Start TinyRPC server error, input argc is not 2!"); printf("Start TinyRPC server like this: \n"); printf("./server a.xml\n"); return 0; } tinyrpc::InitConfig(argv[1]); // 访问 http://127.0.0.1:19999/qps, 即对应 QPSHttpServlet 这个接口 REGISTER_HTTP_SERVLET("/qps", QPSHttpServlet); tinyrpc::StartRpcServer(); return 0; } ``` 启动命令同样如下: ``` nohup ./test_http_server ../conf/test_http_server.xml & ``` 使用 curl 工具可以测试 HTTP 服务是否启动成功: ``` [ikerli@localhost bin]$ curl -X GET 'http://127.0.0.1:19999/qps?id=1'

Welcome to TinyRPC, just enjoy it!

QPSHttpServlet Echo Success!! Your id is,1

``` ## 4.3. RPC 服务调用 这一节将使用 test_http_server 服务调用 test_rpc_server,前面说过,TinyRPC 支持两种 RPC 调用方式:**阻塞协程式异步调用** 和 **非阻塞协程式异步调用** ### 4.3.1. 阻塞协程式异步调用 这种调用方式适用于我们依赖 RPC 调用结果的场景,必须等待 RPC 调用返回后才能进行下一步业务处理。BlockHttpServlet 即属于这种调用方式: ```c++ class BlockCallHttpServlet : public tinyrpc::HttpServlet { public: BlockCallHttpServlet() = default; ~BlockCallHttpServlet() = default; void handle(tinyrpc::HttpRequest* req, tinyrpc::HttpResponse* res) { AppDebugLog << "BlockCallHttpServlet get request "; AppDebugLog << "BlockCallHttpServlet success recive http request, now to get http response"; setHttpCode(res, tinyrpc::HTTP_OK); setHttpContentType(res, "text/html;charset=utf-8"); queryAgeReq rpc_req; queryAgeRes rpc_res; AppDebugLog << "now to call QueryServer TinyRPC server to query who's id is " << req->m_query_maps["id"]; rpc_req.set_id(std::atoi(req->m_query_maps["id"].c_str())); // 初始化 TinyPbRpcChannel 对象 tinyrpc::TinyPbRpcChannel channel(std::make_shared("127.0.0.1", 39999)); QueryService_Stub stub(&channel); // 初始化 TinyPbRpcController 对象, 设置超时时间等 tinyrpc::TinyPbRpcController rpc_controller; rpc_controller.SetTimeout(5000); AppDebugLog << "BlockCallHttpServlet end to call RPC"; // 进行 RRC 调用, 这一步会阻塞当前协程,直到调用完成返回 // 当然阻塞的只是当前协程,对线程来说完全可以去执行其他的协程,因此不会影响性能 stub.query_age(&rpc_controller, &rpc_req, &rpc_res, NULL); AppDebugLog << "BlockCallHttpServlet end to call RPC"; // 判断是否有框架级错误 if (rpc_controller.ErrorCode() != 0) { AppDebugLog << "failed to call QueryServer rpc server"; char buf[512]; sprintf(buf, html, "failed to call QueryServer rpc server"); setHttpBody(res, std::string(buf)); return; } if (rpc_res.ret_code() != 0) { std::stringstream ss; ss << "QueryServer rpc server return bad result, ret = " << rpc_res.ret_code() << ", and res_info = " << rpc_res.res_info(); AppDebugLog << ss.str(); char buf[512]; sprintf(buf, html, ss.str().c_str()); setHttpBody(res, std::string(buf)); return; } std::stringstream ss; ss << "Success!! Your age is," << rpc_res.age() << " and Your id is " << rpc_res.id(); char buf[512]; sprintf(buf, html, ss.str().c_str()); setHttpBody(res, std::string(buf)); } std::string getServletName() { return "BlockCallHttpServlet"; } }; ``` 注册此 Servlet, 然后重启 **test_http_server** ``` REGISTER_HTTP_SERVLET("/block", BlockCallHttpServlet); ``` 使用 curl 测试 ``` [ikerli@localhost bin]$ curl -X GET 'http://127.0.0.1:19999/block?id=1'

Welcome to TinyRPC, just enjoy it!

Success!! Your age is,100100111 and Your id is 1

``` ### 4.3.2. 非阻塞协程式异步调用 这种调用方式适用于我们不依赖 RPC 调用结果的场景,即我们可以继续业务处理,而不关心何时 RPC 调用成功。NonBlockHttpServlet 即属于这种调用方式: ```c++ class NonBlockCallHttpServlet: public tinyrpc::HttpServlet { public: NonBlockCallHttpServlet() = default; ~NonBlockCallHttpServlet() = default; void handle(tinyrpc::HttpRequest* req, tinyrpc::HttpResponse* res) { AppInfoLog << "NonBlockCallHttpServlet get request"; AppDebugLog << "NonBlockCallHttpServlet success recive http request, now to get http response"; setHttpCode(res, tinyrpc::HTTP_OK); setHttpContentType(res, "text/html;charset=utf-8"); // 注意所有调用相关的对象都必须是堆对象,强烈推荐使用 shared_ptr 智能指针 std::shared_ptr rpc_req = std::make_shared(); std::shared_ptr rpc_res = std::make_shared(); AppDebugLog << "now to call QueryServer TinyRPC server to query who's id is " << req->m_query_maps["id"]; rpc_req->set_id(std::atoi(req->m_query_maps["id"].c_str())); std::shared_ptr rpc_controller = std::make_shared(); rpc_controller->SetTimeout(10000); AppDebugLog << "NonBlockCallHttpServlet begin to call RPC async"; tinyrpc::IPAddress::ptr addr = std::make_shared("127.0.0.1", 39999); // 注意区别,这是使用的是 TinyPbRpcAsyncChannel, 而不是 TinyPbRpcChannel tinyrpc::TinyPbRpcAsyncChannel::ptr async_channel = std::make_shared(addr); auto cb = [rpc_res]() { printf("call succ, res = %s\n", rpc_res->ShortDebugString().c_str()); AppDebugLog << "NonBlockCallHttpServlet async call end, res=" << rpc_res->ShortDebugString(); }; std::shared_ptr closure = std::make_shared(cb); // 调用前必须提前保存对象,否则可能会引发段错误 async_channel->saveCallee(rpc_controller, rpc_req, rpc_res, closure); QueryService_Stub stub(async_channel.get()); // rpc 调用, 当前协程会继续往下执行,不依赖 RPC 调用返回 stub.query_age(rpc_controller.get(), rpc_req.get(), rpc_res.get(), NULL); AppDebugLog << "NonBlockCallHttpServlet async end, now you can to some another thing"; // 若需要等待 RPC 结果,可以使用 wait(). 当调用 wait 后,当前协程会阻塞知道 RPC 调用返回 // async_channel->wait(); // AppDebugLog << "wait() back, now to check is rpc call succ"; // if (rpc_controller->ErrorCode() != 0) { // AppDebugLog << "failed to call QueryServer rpc server"; // char buf[512]; // sprintf(buf, html, "failed to call QueryServer rpc server"); // setHttpBody(res, std::string(buf)); // return; // } // if (rpc_res->ret_code() != 0) { // std::stringstream ss; // ss << "QueryServer rpc server return bad result, ret = " << rpc_res->ret_code() << ", and res_info = " << rpc_res->res_info(); // AppDebugLog << ss.str(); // char buf[512]; // sprintf(buf, html, ss.str().c_str()); // setHttpBody(res, std::string(buf)); // return; // } std::stringstream ss; ss << "Success!! Your age is," << rpc_res->age() << " and Your id is " << rpc_res->id(); char buf[512]; sprintf(buf, html, ss.str().c_str()); setHttpBody(res, std::string(buf)); } std::string getServletName() { return "NonBlockCallHttpServlet"; } }; ``` 注册此 Servlet, 然后重启 **test_http_server** ``` REGISTER_HTTP_SERVLET("/nonblock", NonBlockCallHttpServlet); ``` 使用 curl 测试 ``` [ikerli@localhost bin]$ curl -X GET 'http://127.0.0.1:19999/nonblock?id=1'

Welcome to TinyRPC, just enjoy it!

Success!! Your age is,0 and Your id is 0

``` ## 4.4. TinyRPC 脚手架(tinyrpc_generator) TinyRPC 提供了代码生成工具,简单到只需要一个 protobuf 文件,就能生成全部框架代码,作为使用者只需要写业务逻辑即可,不必关心框架的原理,也不用再去写繁琐的重复代码,以及考虑如何链接 tinyrpc 库的问题。接下来用一个实例来说明如何使用 `tinyrpc_generator`. ### 4.4.1 准备 protobuf 文件 例如我们需要搭建一个订单服务: `order_server`. 它的提供一些简单的订单操作:查询订单、生成订单、删除订单等。 首先定义 `order_server.proto` 如下: ``` syntax = "proto3"; option cc_generic_services = true; message queryOrderDetailReq { int32 req_no = 1; // 请求标识,一般是唯一id string order_id = 2; // 单号 } message queryOrderDetailRsp { int32 ret_code = 1; // 返回码,0代表响应成功 string res_info = 2; // 返回信息, SUCC 代表成功,否则为失败的具体信息 int32 req_no = 3; string order_id = 4; // 单号 string goods_name = 5; // 货物名称 string user_name = 6; // 用户名称 } message makeOrderReq { int32 req_no = 1; string user = 2; string goods_name = 3; // 货物名称 string pay_amount = 4; // 支付金额 } message makeOrderRsp { int32 ret_code = 1; string res_info = 2; int32 req_no = 3; string order_id = 4; // 订单号 } message deleteOrderReq { int32 req_no = 1; // 请求标识,一般是唯一id string order_id = 2; // 单号 } message deleteOrderRsp { int32 ret_code = 1; string res_info = 2; int32 req_no = 3; string order_id = 4; // 订单号 } service OrderService { // 查询订单 rpc query_order_detail(queryOrderDetailReq) returns (queryOrderDetailRsp); // 生成订单 rpc make_order(makeOrderReq) returns (makeOrderRsp); // 删除订单 rpc delete_order(deleteOrderReq) returns (deleteOrderRsp); } ``` ### 4.4.2 生成 TinyRPC 框架 这一步很简单,简单到只需要一行命令: ``` tinyrpc/generator/tinyrpc_generator.py -o ./ -i order_server.proto -p 12345 ``` 这里先不介绍各个选项的含义,你可以观察到在当前目录下 `./` 已经生成了项目 `order_server`, 其项目结构如下: ``` order_server: 根目录 - bin: 可执行文件目录 - run.sh: 启动脚本 - shutdown.sh: 停止脚本 - order_server: 可执行文件 - conf: 配置文件目录 - order_server.xml: TinyRPC 配置文件 - log: 日志目录,存放运行时产生的日志文件 - obj: 库文件目录,存放编译过程的中间产物 - order_server: 源代码文件目录 - comm: 公共文件 - interface: 接口定义文件目录,每一个RPC方法会在此处定义一个接口 - pb: 由 protoc 生成的文件,以及源protobuf文件 - service: 接口转发层,将每个 RPC 方法跳转到对应的 interface 接口 - order_server.cc - order_server.h - main.cc: main 文件,TinyRPC 服务的 main 函数在此 - makefile: TinyRPC 工程的 makefile 文件,直接执行 make 即可 - test_client: 测试工具目录,每一个 interface 下的接口,在此处都会有一个对应的 cleint 工具,可以简单测试 RPC 通信 ``` OK, 你唯一需要做的就是进入 `order_server/order_server` 目录,执行 `make -j4` 即可,整个项目就完成构建了。 接下来,进入 `order_server/bin` 目录下,执行: ``` sh run.sh order_server ``` 不出意外的话,你的 TinyRPC 服务已经成功的运行起来了。接下来简单测试一下,进入 `order_server/test_client` 目录,执行客户端测试工具,如: ``` ./test_query_order_detail ``` 如果 TinyRPC 服务启动成功,你会看到以下输出: ``` [ikerli@localhost test_client]$ ./test_query_order_detail_client Send to tinyrpc server 0.0.0.0:12345, requeset body: Success get response frrom tinyrpc server 0.0.0.0:12345, response body: res_info: "OK" ``` 否则,你会看到失败的具体原因,请根据错误码自行排查。例如这里错误显示为 peer closed,多半是服务没有启动,导致该端口没人监听。 ``` [ikerli@localhost test_client]$ ./test_query_order_detail_client Send to tinyrpc server 0.0.0.0:12345, requeset body: Failed to call tinyrpc server, error code: 10000000, error info: connect error, peer[ 0.0.0.0:12345 ] closed. ``` ### 4.4.3 业务逻辑开发 `tinyrpc_geneator` 为 Protobuf 文件中的每一个 rpc 方法生成了一个接口(interface), 这些接口位于 `order_server/interface/` 目录下. 例如这里的 `test_query_order_detail` 方法, 我们可以在 `interface` 目录下找到这两个文件: `query_order_detail.cc` 和 `query_order_detail.h` ```c++ // interface/query_order_detail.cc #include "tinyrpc/comm/log.h" #include "order_server/interface/query_order_detail.h" #include "order_server/pb/order_server.pb.h" namespace order_server { QueryOrderDetailInterface::QueryOrderDetailInterface(const ::queryOrderDetailReq& request, ::queryOrderDetailRsp& response) : m_request(request), m_response(response) { // m_request: 客户端请求的结构体,从中可以取出请求信息 // m_response: 服务端响应结构体,只需要将结果设置到此即可,TinyRPC 会负责会送给客户端结果 } QueryOrderDetailInterface::~QueryOrderDetailInterface() { } void QueryOrderDetailInterface::run() { // // Run your business at here // m_reponse.set_ret_code(0); // m_reponse.set_res_info("Succ"); // } ``` 那么写业务逻辑就非常简单了,只需要实现具体的 `QueryOrderDetailInterface::run()` 方法即可,其他任何逻辑完全不需要关心,TinyRPC 已经处理好了一切。 ### 4.4.4 Protobuf 接口升级怎么办? 当需要升级接口的时候,即修改 protobuf 文件,要怎么重新生成项目呢?因为你在 `interface` 目录下实现了业务逻辑,会不会重新生成项目之后,之前的代码被覆盖了? 完全不用担心,`tinyrpc_generator` 已经考虑到了这种情况,你可以放心大胆的修改 protobuf 文件,然后重新执行生成命令: ``` tinyrpc/generator/tinyrpc_generator.py -o ./ -i order_server.proto -p 12345 ``` `tinyrpc_generator` 会智能的判断哪些文件需要更新,哪些文件无需更新。规则如下: - interface: 下所有的接口定义文件,如果同名文件存在则不会更新,否则生成新文件 - service: 该目录下的文件每次都会被更新,因为 protobuf 文件修改意味着接口有变化,比如新增或者删除接口之类的,需要重新生成文件以便能对新增的接口进行转发 - makefile: 不存在时生成,存在则不更新 - main.cc: 不存在时生成,存在则不更新 - test_client: 不存在时生成,存在则不更新 - pb: 每次都会更新(这是必然的,比较 protobuf 文件都变了) ### 4.4.5 tinyrpc_generator 选项详解 `tinyrpc_generator` 是用 python 语言实现的简单脚本,其提供了几个简单的命令行入参选项,你也可以使用 `-h` 或者 `--help` 选项获取帮助文档: ``` Options: -h, --help 打印帮助文档 -i xxx.proto, --input xxx.proto 指定源 protobuf 文件,注意只支持 porotbuf3 -o dir, --output dir 指定项目生成路径 -p port, --input port 指定 TinyRPC 服务监听的端口(默认是 39999) -h x.x.x.x, --host x.x.x.x 指定 TinyRPC 服务绑定的 IP 地址(默认是 0.0.0.0) ``` # 5. 概要设计 **TinyRPC** 框架的主要模块包括:异步日志、协程封装、Reactor封装、Tcp 封装、TinyPb协议封装、HTTP 协议封装、以及 RPC封装模块等。 ## 5.1. 异步日志模块 设计初期,**TinyRPC** 的日志主要参考了 (**sylar**),并精简后实现了最基础的打印日志。 在开发到一定程度后,发现同步日志或多或少有些影响性能,毕竟每次写入文件的磁盘IO还是比较耗时的。遂改为异步日志。TinyRPC 的异步日志实现非常简单,只是额外启动了一个线程来负责打印日志罢了。 当然,**TinyRPC** 的日志做到了了以下几点: - **异步日志**:日志异步打印,不阻塞当前线程。生产者只需要将日志信息放入buffer即可,消费者线程会按照一定时间频率自动将日志同步到磁盘文件中。 - **日志级别**:日志分级别打印,**当设定级别高于待打印日志的级别时,日志打印是个空操作**,无性能消耗。 - **文件输出**:日志支持可以输出到文件中,特别是在生产环境上,把日志打印到控制台可不是一个好方法。 - **滚动日志**:日志文件会自行滚动,当**跨天**或者**单个文件超过一定大小**后,会自动建立新的文件写入日志信息。 - **崩溃处理**:TinyRPC 的日志库处理了**程序突然崩溃**的情况,简单来说就是当程序崩溃退出前先将日志信息同步到磁盘文件上。这是非常重要的,如果缺失了崩溃那一瞬间的日志内容,那就很难排查具体原因。 - **日志分类**:TinyRPC 提供了两类日志类型,**RPC 框架日志**以及 **APP 应用日志**。RPC 框架日志以 rpc 后缀结尾,是 TinyRPC 框架在运行中打印的日志信息,通常用来监控框架本身的运行状态。APP 应用日志以 **app** 后缀结尾 专门用来处理用户请求,对于每一个客户端请求,APP 日志会打印出请求的 msg 作为标识。总的来说,如果你只是使用 TinyRPC,关注APP日志即可。 你可以分别使用宏定义 **DebugLog** 和 **AppDebugLog** 打印这两种日志: ``` DebugLog << "11"; AppDebugLog << "11"; ``` ## 5.2. 协程模块 ### 5.2.1. 协程封装 TinyRPC 的协程底层切换使用了腾讯的开源协程库 [libco](https://github.com/Tencent/libco),即协程上下文切换那一块,而协程切换的本质不过是寄存器切换罢了。 除了协程切换之外,TinyRPC 提供了一些基本函数的 hook,如 read、write、connect 等函数。 更多协程的介绍请移步我的知乎文章: [C++实现的协程网络库tinyrpc(一)-- 协程封装](https://zhuanlan.zhihu.com/p/466349082) [C++实现的协程网络库tinyrpc(二)-- 协程Hook](https://zhuanlan.zhihu.com/p/474353906) [协程篇(一)-- 函数调用栈](https://zhuanlan.zhihu.com/p/462968883) ### 5.2.2. m:n 线程:协程模型 最初设计中 TinyRPC 框架是 **1:n** 线程:协程模型的,即一个线程对于 n 个协程。每个线程有单独的协程池,线程只会 Resume 属于它自己协程池里面的协程,各个 IO 线程之前的协程互相不干扰。 然而 **1:n** 模型可能会增加请求的时延。例如当某个 IO 线程在处理请求时,耗费了太多的时间,导致此 IO 线程的其他请求得不到及时处理,只能阻塞等待。 因此 TinyRPC 框架使用 **m:n 线程:协程**模型进行了重构。所谓 **m:n** 即 m 个线程共同调度 n 个协程。由于 m 个线程共用一个协程池,因此协程池里的就绪任务总会尽快的被 **Resume**。 一般来说,每一个客户端连接对象 **TcpConnection**, 对应一个协程。对客户端连接的 **读数据、业务处理、写数据**这三步,其实都在这个协程中完成的。对于 **m:n 协程模型** 来说,一个 **TcpConnection**对象所持有的协程,可能会来回被多个不同的**IO线程**调度。 举个例子,协程 A 可能先由 IO线程1 Resume,然后协程 A Yield后,下一次又被 IO线程2 Resume 唤醒。 因此,在实现业务逻辑的时候,要特别谨慎使用**线程局部变量(thread_local)**。因为对当前协程来说,可能执行此协程的线程都已经变了,那对于的线程局部变量当然也会改变。 当然,**一个协程任一时刻只会被一个线程来调度,不会存在多个 IO 线程同时 Resume 同一个协程的情况**。这一点由 TinyRPC 框架保证。 不过,m:n 模型也引入了更强的**线程竞争条件**,所以对协程池加**互斥锁**是必须的。 ## 5.3. Reactor 模块 可移步知乎文章: [C++实现的协程网络库tinyrpc(四)-- Reactor 实现](https://zhuanlan.zhihu.com/p/503323714) [Reactor模式介绍](https://zhuanlan.zhihu.com/p/428693405) ## 5.4. Tcp 模块 ### 5.4.1. TcpServer TcpServer 的运行逻辑如下: ![](imgs/tcp_server.drawio.png) 原理可参考文章: [C++实现的协程异步 RPC 框架 TinyRPC(五)-- TcpServer 实现](https://zhuanlan.zhihu.com/p/523947909) ### 5.4.2. TcpConnection TcpConnection 运行逻辑如下: ![](imgs/input.drawio.png) 原理可参考文章: [C++实现的协程异步 RPC 框架 TinyRPC(六)-- TcpConnection 实现](https://zhuanlan.zhihu.com/p/524517895) ## 5.5. TinyPB 协议 TinyPB 是 TinyRPC 框架自定义的一种轻量化协议类型,它是基于 google 的 protobuf 而定制的,读者可以按需自行对协议格式进行扩充。 ### 5.5.1. TinyPB 协议报文格式分解 **TinyPb** 协议包报文用 c++ 伪代码描述如下: ```c++ /* ** min of package is: 1 + 4 + 4 + 4 + 4 + 4 + 4 + 1 = 26 bytes ** */ char start; // 代表报文的开始, 一般是 0x02 int32_t pk_len {0}; // 整个包长度,单位 byte int32_t msg_req_len {0}; // msg_req 字符串长度 std::string msg_req; // msg_req,标识一个 rpc 请求或响应。 一般来说 请求 和 响应使用同一个 msg_req. int32_t service_name_len {0}; // service_name 长度 std::string service_full_name; // 完整的 rpc 方法名, 如 QueryService.query_name int32_t err_code {0}; // 框架级错误代码. 0 代表调用正常,非 0 代表调用失败 int32_t err_info_len {0}; // err_info 长度 std::string err_info; // 详细错误信息, err_code 非0时会设置该字段值 std::string pb_data; // 业务 protobuf 数据,由 google 的 protobuf 序列化后得到 int32_t check_num {0}; // 包检验和,用于检验包数据是否有损坏 char end; // 代表报文结束,一般是 0x03 ``` 注释信息已经很完整了。另外几个需要特殊说明的字段如下: **err_code**: err_code 是框架级别的错误码,即代表调用 RPC 过程中发生的错误,如对端关闭、调用超时等。err_code 为0 代表此次 RPC 调用正常,即正常发送数据且接收到回包。非 0 值代表调用失败,此时会设置 err_info 为详细的错误信息。 **service_full_name** : 是指的调用的完整方法名。即 servicename.methodname。一般来说,一个 **TinyPB**协议的**TinyRPC** 服务需要注册至少一个 **Service** (这里的 Service 指的继承了google::protobuf::Service 的类),而一个 Service 下包含多个方法。 **pk_len**: pk_len 代表整个协议包的长度,单位是1字节,且包括 **[strat]** 字符 和 **[end]** 字符。 **TinyPb** 协议报文中包含了多个 len 字段,这主要是为了用空间换时间,接收方在提前知道长度的情况下,更方便解码各个字段,从而提升了 decode 效率。 另外,**TinyPb** 协议里面所有的 int 类型的字段在编码时都会先转为**网络字节序**! ## 5.6. Http 模块 TinyRPC 的 HTTP 模块实际上有点模仿 Java 的 Servlet 概念,每来一个 HTTP 请求就会找到对应的 HttpServlet 对象,执行其提前注册好的业务逻辑函数,用于处理 Http 请求,并回执 Http 响应。 ## 5.7. RPC 调用封装 --建设中,敬请期待-- # 6. 错误码 ## 6.1. 错误码判断规范 **TinyPB** 协议使用错误码来标识 RPC 调用过程的那些不可控的错误。这些错误码是框架级错误码,当出现这些错误码时,说明是 RPC 调用的链路出了问题。自然,这次 RPC 调用是失败的。 一般来说,在调用 RPC 时,需要判断两个错误码,例如: ```c++ stub.query_name(&rpc_controller, &rpc_req, &rpc_res, NULL); // 判断框架级别错误码 if (rpc_controller.ErrorCode() != 0) { ErrorLog << "failed to call QueryServer rpc server"; // .... return; } // 判断业务错误码 if (rpc_res.ret_code() != 0) { // ... return; } ``` rpc_controller.ErrorCode 是 RPC **框架级错误码**,即这个文档里面锁描述的东西。该错误码的枚举值已经被定义好如下表格,一般情况下不会变更。当此错误码不为0时,请检查 RPC 通信链路是否有问题,网络连接是否有异常。当然,TinyPB 协议里面的 err_info 字段也会详细的描述错误信息。 另一个错误码是**业务错误码**,通常他被定义在 RPC 方法返回结构体的第一个字段中。出现这个错误码一般是对端在进行业务处理时出现了非预期的结果,此时将返回对应的错误码和错误信息。这个错误码的枚举值应由 RPC 通信双方自行约定。 ## 6.2. 错误码释义文档 err_code 详细说明如下表: | **错误码** | **错误代码** | **错误码描述** | | ---- | ---- | ---- | | ERROR_PEER_CLOSED | 10000000 | connect 时对端关闭,一般是对端没有进程在监听此端口 | | ERROR_FAILED_CONNECT | 10000001 | connect 失败| | ERROR_FAILED_GET_REPLY | 10000002 | RPC 调用未收到对端回包数据 | | ERROR_FAILED_DESERIALIZE | 10000003 | 反序列化失败,这种情况一般是 TinyPb 里面的 pb_data 有问题 | | ERROR_FAILED_SERIALIZE | 10000004 | 序列化失败| | ERROR_FAILED_ENCODE | 10000005 | 编码失败 | | ERROR_FAILED_DECODE | 10000006 | 解码失败| | ERROR_RPC_CALL_TIMEOUT | 10000007 | 调用 RPC 超时, 这种情况请检查下 RPC 的超时时间是否太短 | | ERROR_SERVICE_NOT_FOUND | 10000008 | Service 不存在,即对方没有注册这个 Service | | ERROR_METHOD_NOT_FOUND | 10000009 | Method 不存在,对方没有这个 方法| | ERROR_PARSE_SERVICE_NAME | 10000010 | 解析 service_name 失败| | ERROR_NOT_SET_ASYNC_PRE_CALL | 10000011 | 非阻塞协程式 RPC 调用前没保存对象 | # 7. 问题反馈 - 交流群:**260423934** - 邮箱地址:**1753009868@qq.com** - 知乎:知乎搜索 **ikerli** 如果您愿意支援一点电费,不胜感激! ![](./imgs/mine/wechatpay.png)(wechat pay)   ![](./imgs/mine/alipay.png)(alipay) # 8. 参考资料 libco: https://github.com/Tencent/libco sylar: https://github.com/sylar-yin/sylar muduo: https://github.com/chenshuo/muduo tinyxml: https://github.com/leethomason/tinyxml2 protobuf: https://github.com/protocolbuffers/protobuf