# rpc-backend-cpp **Repository Path**: git729970897/rpc-backend-cpp ## Basic Information - **Project Name**: rpc-backend-cpp - **Description**: C++ RPC框架 - **Primary Language**: C++ - **License**: MIT - **Default Branch**: v0.3.0-alpha - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 10 - **Created**: 2022-04-20 - **Last Updated**: 2022-04-20 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## RPC C++ 框架 基于[RPC Frontend](https://gitee.com/dennis-kk/rpc-frontend)产生的配置生成RPC框架,框架本身不包含socket通信相关代码,但可以快速与已有的网络通信框架集成。 ![RPC调用流程](https://images.gitee.com/uploads/images/2020/0116/124315_ba7438e2_467198.png "屏幕截图.png") ## 使用 包含rpc.h,在框架主循环内调用rpc::update(), 并实现Transport接口,Transport接口作为RPC协议的输入流,当有RPC协议到来的时候调用rpc::onMessage()方法。 Transport接口如下: ``` class Transport { public: virtual int send(const char* data, int size) = 0; virtual int peek(char* data, int size) = 0; virtual int recv(char* data, int size) = 0; virtual int skip(int size) = 0; virtual int size() = 0; virtual bool isClose() = 0; virtual void close() = 0; }; ``` 在逻辑主循环内调用: ``` #include "rpc.h" ... rpc::update(); ``` 当有RPC协议到来时调用: ``` // transportPtr为用户实现的RPC数据流包装器智能指针实例 rpc::onMessage(tranportPtr); ``` ## IDL定义 请参考[RPC Frontend](https://gitee.com/dennis-kk/rpc-frontend) ## 生成RPC代码 代码生成流程如下: 1. 使用[RPC Frontend](https://gitee.com/dennis-kk/rpc-frontend)将IDL转换为两个JSON文件,譬如test1.idl使用将转换为:test1.idl.cpp.json和test1.idl.protobuf.json 2. 运行如下脚本: ``` python cppgen.py test1.idl.cpp.json python cppgen_pb_layer.py test1.idl.cpp.json test1.idl.protobuf.json ``` ### 文件命名规则 由框架生成的代码文件命名规则如下: 1. 所有文件名由IDL文件名作为起始 2. 单个IDL文件所有引用到的所有struct被生成在单独的文件内 由下文所述的test1.idl,其中文件名为test1, 服务名为Service,生成的代码文件包含: | 文件名 | 描述 | | --- | --- | | test1.struct.h | 所有struct定义 | | test1.service.Service.h | 用户接口定义 | | test1.service.Service.proxy.h(.cpp) | 代理头文件(实现文件) | | test1.service.Service.stub.h(.cpp) | 服务桩头文件(实现文件) | | test1.service.Service.proxy.serializer.h | 代理序列化相关头文件 | | test1.service.Service.stub.serializer.h | 服务桩序列化相关头文件 | 以下文件为protobuf序列化层,主要由.proto文件及protobuf的C++头文件和实现文件,protobuf序列化实现,这样实现的原因是形成序列化上层和下层,上层可以保持代码不更改的情况下使用不同的序列化方案实现。 | 文件名 | 描述 | | --- | --- | |test1.service.proto | protobuf定义文件 | |test1.service.pb.h | protobuf生成的头文件 | |test1.service.pb.cc | protobuf生成的实现文件 | |test1.service.Service.proxy.serializer.cpp | 使用protobuf实现的代理序列化下层 | |test1.service.Service.stub.serializer.cpp | 使用protobuf实现的桩序列化下层 | 调用方编译所需文件为: | 文件名 | 描述 | | --- | --- | | test1.struct.h | 所有struct定义 | | test1.service.Service.proxy.h(.cpp) | 代理头文件(实现文件) | | test1.service.Service.proxy.serializer.h | 序列化相关头文件 | |test1.service.Service.proxy.serializer.cpp | 使用protobuf实现的序列化下层 | |test1.service.pb.h | protobuf生成的头文件 | |test1.service.pb.cc | protobuf生成的实现文件 | 服务提供方编译所需文件为: | 文件名 | 描述 | | --- | --- | | test1.struct.h | 所有struct定义 | | test1.service.Service.h | 用户接口定义 | | test1.service.Service.stub.h(.cpp) | 服务桩头文件(实现文件) | | test1.service.Service.stub.serializer.h | 序列化相关头文件 | |test1.service.Service.stub.serializer.cpp | 使用protobuf实现的序列化下层 | |test1.service.pb.h | protobuf生成的头文件 | |test1.service.pb.cc | protobuf生成的实现文件 | ### 类命名规则 如下文的test1.idl定义内的服务Service,生成的代理类名为ServiceProxy,生成的用户需实现的接口为ServiceImpl,这个类是用户实现的服务功能 ## 使用 假设有如下IDL文件, test1.idl ``` struct Dummy { i32 field1 string field2 seq field3 set field4 dict field5 bool field6 float field7 double field8 } struct Data { i32 field1 string field2 seq field3 set field4 dict field5 bool field6 float field7 double field8 Dummy field9 seq field10 set field11 dict field12 } service Service multiple=16 { oneway void method1(Data,string) string method2(i8,set,ui64) dict method3(i8,set,dict, ui64) void method4(void) void method5() } ``` 通信代码生成后,可以按如下方法使用: ### 获取服务 服务发现和建立网络连接由使用者负责建立。 ``` #include "test1.service.Service.proxy.h" // trans为用户提供的Transport auto service = rpc::createProxy(trans); ``` 1. 异步调用方式 ``` service->method2(1, {"2", "3"}, 4, [&](const std::string& ret) { ...... } ``` 2. 协程调用方式 ``` co ( auto ret = service->method2(1, {"2", "3"}, 4); ...... ) ``` ## 错误处理 1. 获取服务
getService当服务找不到时会返回空 2. 在协程内调用代理服务时远端服务异常
当远端服务在调用中发生异常则代理服务也将抛出异常,异常类型为rpc::RemoteMethodException 3. 在协程内调用代理服务时,由于版本不匹配,方法未找到
通过代理服务调用将抛出异常,异常类型为rpc::MethodNotFound 4. 在协程内调用代理服务时调用超时
通过代理服务调用将抛出异常,异常类型为rpc::RemoteMethodTimeout 5. 在co(...)外调用协程方法
在co(...)外调用协程方法将抛出异常,异常类型为rpc::ProxyCoroMethodException ## 编写服务 通过脚本生成代码后就需要开发实际的服务功能了,用户除了需要实现自己定义的服务方法外,还需要实现如下几个被框架调用的方法: ``` virtual bool onAfterFork() override; virtual bool onBeforeDestory() override; ``` 如本文中的例子,ServiceImpl的构造函数不允许有参数,个性化构造通过实现onAfterFork方法来实现,当服务实例被销毁前框架会调用onBeforeDestory,在这个方法可以做清理及数据保存工作。 服务在编译和使用方式上分为两类: 1. 静态服务
静态服务即服务的代码被编译到调用程序中,可以以源代码的方式或者静态库的方式 2. 动态服务
动态服务是被编译成共享对象的方式(.so, .dll),通过运行时动态加载的方式使用 同一个服务的静态形式和动态形式不能同时存在,只能二选其一. 动态服务有几个额外的特性: 1. 动态服务可以被热更新
动态服务可以在运行时更新,假设有一个动态服务test1.0.so, 这个服务的热更新包是test1.1.so,可以在不卸载test1.0.so的情况下加载test1.1.so, 加载后所有原有由test1.0.so产生的服务将缓存新到来的调用请求,当这些服务执行完正在调用的请求时会尝试卸载test1.0.so,直到所有引用test1.0.so的服务都不再使用时test1.0.so会被卸载,如果加载test1.1.so后因为某个原因启动失败,则升级过程终止,继续使用test1.0.so来进行服务 2. 因为动态服务是被独立编译的,可以把那些代码相对独立的功能模块隔离开发,设计者在设计时会更多的考虑如何减少与其他系统的耦合 ## 通信协议 为了与库的RPC兼容,其他系统或语言实现接入时需要实现既定的通信协议格式,协议格式如下: | 协议头| 协议体 | | --- | --- | ### 协议头 RpcMsgHeader | 协议长度(包含消息头)| 协议类型 |协议体| | --- | --- | --- | | 32位 | 32位 | 协议类型如下:
0 无效类型
1 调用请求
2 调用返回
3 非RPC协议
### 调用请求 RpcCallHeader | 消息头(RpcMsgHeader) |服务UUID|绑定的服务器实例ID|代理调用ID|方法ID|协议体| | --- | --- | --- | --- | --- | --- | | 64位 | 64位 | 32位 | 32位 | 32位 | ### 调用返回 RpcCallRetHeader | 消息头(RpcMsgHeader)|绑定的服务器实例ID|代理调用ID|错误码|协议体| | --- | --- | --- | --- | --- | | 64位 | 32位 | 32位 | 32位 | ### 代理调用请求 RpcProxyCallRequestHeader | 消息头(RpcMsgHeader)|服务UUID|绑定的服务器实例ID|代理调用ID|外部连接标识ID|是否是oneway调用|协议体| | --- | --- | --- | --- | --- | --- | --- | | 64位 | 64位 | 32位 | 32位 | 32位 | 16位 | ### 代理调用返回 RpcProxyCallRetHeader | 消息头(RpcMsgHeader)|绑定的服务器实例ID|代理调用ID|错误码|外部连接标识ID|协议体| | --- | --- | --- | --- | --- | --- | | 64位 | 32位 | 32位 | 32位 | 32位 | 错误码如下:
1 成功
2 服务或方法未发现
3 服务发生异常
4 调用超时