qidao123.com技术社区-IT企服评测·应用市场

标题: 施磊老师rpc(二) [打印本页]

作者: tsx81428    时间: 2025-5-6 07:47
标题: 施磊老师rpc(二)
protobuf使用(三)

复习

推荐用 bytes 替换 string:减少字符编码开销,进步性能。
message 支持复杂嵌套结构

注意:Protobuf 的 map 类型与 C++ STL 中的 map 是不同的,仅为消息结构的语法糖。
“语法糖”是编程语言里的一个术语,意思是:
   一种让代码更好写、更好读的语法情势,本质上没有引入新功能,只是更简洁或直观。
    [!TIP]
  Protobuf 的 map 和 C++ STL 的 map 的区别:
  雷同点:
  
  
不同点(关键):
项目Protobuf 中的 mapC++ STL 中的 map类型一种消息字段的语法糖一个真正的容器类底层实现被编译成一个隐藏的 repeated message基于均衡二叉树或哈希表功能限制不能做复杂操纵(如排序、自界说比力函数)支持全功能,如插入、排序、自界说比力等使用目标用来在消息中表达键值对结构用来在内存中管理数据关系支持语言广度统一跨语言序列化格式仅限于 C++ 标准库 Protobuf 与 RPC 的关系


为什么要用 ProtoBuf


service界说rpc

service 表示界说一个服务类;
rpc 方法名 (参数类型) returns (返回类型);
对应业务逻辑中的长途函数。
  1. // 在protobuf 定义描述 rpc类型
  2. service UserServiceRpc {
  3.     rpc Login(LoginRequest) returns (LoginResponse);
  4.     rpc GetFriendList(GetFriendListsRequest) returns (GetFriendListsResponse);
  5.   }
复制代码
必须启用 service 的生成:

只写 service 类, 举行protoc 编译, 是不会生成 service类的
  1. option cc_generic_services = true;
复制代码

开启并编译后, 将会生成 以下
  1. class UserServiceRpc_Stub;
  2. class UserServiceRpc
复制代码
protobuf代码结构–重点


以 Login 为例,会生成如下内容:
   [!TIP]
  新版messgae结构加了一个小东西—final
  1. class LoginRequest final :
  2.   public ::PROTOBUF_NAMESPACE_ID::Message{}
复制代码
在 C++ 中,final 是一个限定符(specifier),它的作用是:
     克制类被继承,或克制虚函数被重写(override)
    1. Message 类型:

  1. class LoginRequest : public google::protobuf::Message {
  2.   // 提供 GetName(), SetName(), GetPwd(), SetPwd()
  3. };
  4. class LoginResponse : public google::protobuf::Message {
  5.   // 同样提供成员变量的读写方法
  6. };
复制代码
2. Service 类型(抽象类):

  1. class UserServiceRpc_Stub;
  2. class UserServiceRpc
  3. : public ::PROTOBUF_NAMESPACE_ID::Service{public:        virtual void Login(::PROTOBUF_NAMESPACE_ID::RpcController *controller,        const ::hzhpro::LoginRequest *request,        ::hzhpro::LoginResponse *response,        ::google::protobuf::Closure *done);                        virtual void GetFriendList(::PROTOBUF_NAMESPACE_ID::RpcController *controller,        const ::hzhpro::GetFriendListsRequest *request,        ::hzhpro::GetFriendListsResponse *response,        ::google::protobuf::Closure *done);                        const ::PROTOBUF_NAMESPACE_ID::ServiceDescriptor *GetDescriptor();    .....}
复制代码
3. Stub 类型(代理类):

  1. class UserServiceRpc_Stub : public UserServiceRpc {
  2.   // 提供 Login 方法的远程代理实现
  3. };
复制代码

理解service类


先简朴了解
   [!IMPORTANT]
  这在 以后 写 rpc 项目 很有效, 是重点
  尤其是 service类
  1. getDescriptor() 方法的作用


2. 为什么必要 ServiceDescriptor


3. service 类是如何生成的


4. protobuf 中的两个核心抽象


5. RPC 方法结构统一


protobuf使用(四)


Protobuf 生成的两个核心类

  1. class PROTOBUF_EXPORT RpcChannel {
  2. public:
  3.         ....
  4.   virtual void CallMethod(const MethodDescriptor* method,
  5.                           RpcController* controller, const Message* request,
  6.                           Message* response, Closure* done) = 0;
  7.         ....
  8. private:
  9.   GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(RpcChannel);
  10. };
复制代码
RpcChannel 的作用与机制


自界说 RpcChannel 实现

为了真正执行 RPC 长途调用,必要开发者自己实现一个类
  1. class MyRpcChannel : public RpcChannel {
  2. public:
  3.     void CallMethod(...) override {
  4.         // 1. 对 request 序列化
  5.         // 2. 构造 RPC 请求包
  6.         // 3. 发送网络请求
  7.         // 4. 接收响应并反序列化到 response
  8.     }
  9. };
复制代码
该类实现了 CallMethod 的逻辑,真正完成 RPC 通讯。
调用流程总结

理解的重点发起


   [!TIP]
  此时 再去 看 rpc那个 理论图, 会很清晰, 会明白 什么是代理类(stub)
    [!IMPORTANT]
  最好有项目实践, 不然很难理解
  本地服务发布rpc(一)

项目目标


开发方式


项目结构说明

  1. 项目根目录/
  2. ├── bin/            # 存放最终生成的可执行文件(服务提供者/消费者)
  3. ├── build/          # CMake 构建过程中的中间文件
  4. ├── example/        # 业务代码:使用框架的服务端/客户端示例
  5. │   ├── callee/     # 服务提供者:提供 RPC 方法(如 login)
  6. │   └── caller/     # 服务消费者:远程调用 RPC 方法
  7. ├── lib/            # 框架本身编译后的动态库
  8. ├── src/            # 框架核心代码
  9. ├── test/           # 示例代码(如 protobuf、zookeeper 测试)
  10. ├── CMakeLists.txt  # 顶级构建脚本
  11. ├── README.md       # 项目说明
  12. └── autogen.ac      # 自动化构建脚本
复制代码
CMakeLists.txt

  1. # 设置cmake最低版本 和 项目名称
  2. cmake_minimum_required(VERSION 3.0)
  3. project(mprpc-hzh)
  4. # 设置项目可执行文件输出的路径
  5. set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
  6. # 设置项目库文件输出的路径
  7. set(LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib)
  8. # 设置编译头文件搜索路径 -I
  9. include_directories(${PROJECT_SOURCE_DIR}/src)
  10. # 设置项目库文件搜索路径 -L
  11. link_directories(${PROJECT_SOURCE_DIR}/lib)
  12. add_subdirectory(src)
  13. # 放 使用 rpc服务的 使用者 caller和 消费者callee
  14. add_subdirectory(example)   
复制代码
业务示例 - UserService

把一个原本本地类的方法 UserService:ogin(name, pwd) 变成一个可以长途调用的 RPC 方法
  1. #include <iostream>
  2. #include <string>
  3. /*
  4. UserService 原来是一个 本地服务, 提供了 两个进程内的本地方法, Login和GetFriendLists
  5. */
  6. class UserService
  7. {
  8.     public:
  9.     bool  Login(const std::string username, const std::string password)
  10.     {
  11.         std::cout << "doing local service : Login" << std::endl;
  12.         std::cout<<"username: " << username << " password: " << password << std::endl;
  13.     }
  14. };
  15. int main(int argc, char* argv[])
  16. {
  17.    
  18.     return 0;
  19. }
复制代码
RPC 方法界说(Protobuf)

example/user.proto
  1. syntax = "proto3";package example;option cc_generic_services = true;
  2. // 生成服务类和rpc方法描述, 默认不生成message ResultCode{    int32 errcode = 1;    bytes errmsg = 2;}// 界说登录请求消息类型 name pwdmessage LoginRequest{    bytes name = 1;    bytes pwd = 2;}// 界说登录相应消息类型message LoginResponse{    ResultCode result = 1;    bool success = 3;}service UserServiceRpc {
  3.     rpc Login(LoginRequest) returns (LoginResponse);
  4. }
复制代码
  编译---->在cmake 加 头文件搜刮路径
  example/CMakeLists.txt
  1. add_subdirectory(callee)
复制代码
example/callee/CMakeLists.txt
  1. set(SRC_LIST userservice.cc ../user.pb.cc)
  2. add_executable(provider ${SRC_LIST})
复制代码
修改业务代码-加rpc

  1. #include <iostream>
  2. #include <string>
  3. #include "user.pb.h"
  4. /*
  5. UserService 原来是一个 本地服务, 提供了 两个进程内的本地方法, Login和GetFriendLists
  6. */
  7. class UserService : public UserServiceRpc
  8. {
  9.     public:
  10.     bool  Login(const std::string username, const std::string password)
  11.     {
  12.         std::cout << "doing local service : Login" << std::endl;
  13.         std::cout<<"username: " << username << " password: " << password << std::endl;
  14.     }
  15. };
  16. int main(int argc, char* argv[])
  17. {
  18.     return 0;
  19. }
复制代码
错误-1:

add_subdirectory(src) , src内里必须有 cmake配置文件, 哪怕是空的
add_subdirectory() 会递归调用指定目录下的 CMakeLists.txt,如果这个文件不存在,CMake 配置过程就会报错,提示找不到构建脚本。
错误-2:

   [!WARNING]
  proto 相干的 命名空间!!!
  1. #include <iostream>
  2. #include <string>
  3. #include "user.pb.h"
  4. // using namespace hzhrpc;
  5. /*
  6. UserService 原来是一个 本地服务, 提供了 两个进程内的本地方法, Login和GetFriendLists
  7. */
  8. class UserService : public hzhrpc::UserServiceRpc
  9. {
  10.     public:
  11.     bool  Login(const std::string username, const std::string password)
  12.     {
  13.         std::cout << "doing local service : Login" << std::endl;
  14.         std::cout<<"username: " << username << " password: " << password << std::endl;
  15.         return true;
  16.     }
  17. };
  18. int main(int argc, char* argv[])
  19. {
  20.     return 0;
  21. }
复制代码
重写基类的虚函数(部门)

  1. // 重写  UserServiceRpc 里的 Login方法
  2.     void Login(::google::protobuf::RpcController* controller,   // 这里命名空间不写hzhrpc::, 直接写google::protobuf   特别注意
  3.         const ::hzhrpc::LoginRequest* request,
  4.         ::hzhrpc::LoginResponse* response,
  5.         ::google::protobuf::Closure* done);
复制代码
各参数理解









增补说明:

命名空间为什么用 ::google::protobuf:: 而不是 hzhrpc::
这是因为:

本地服务发布rpc(二)

配景说明


两个rpc类->proto后

UserServiceRpc:服务端实现继承这个类,重写 RPC 方法。
UserServiceRpc_Stub:客户端通过它调用 RPC 方法。
再次回首四个参数

参数作用controller控制器对象,可用于传递额外的控制信息(如超时、取消、错误处置惩罚等,暂不关注)request已经被框架反序列化好的请求对象(从客户端发来的 LoginRequest)response框架提前创建的相应对象,业务方填写相应结果即可done回调对象,框架用于发送相应的关键机制:调用 done->Run() 触发序列化 + 网络发送 把本地服务变成长途服务流程-总结

   1. 界说 proto 文件

  
  举例:
  1. service UserServiceRpc {
  2.     rpc Login(LoginRequest) returns (LoginResponse);
  3. }
复制代码
2. 从生成的服务类中继承

  
  3. 业务处置惩罚逻辑

  
  4. 发送相应数据-run实现

  
  callee代码团体

注意一下, Login 函数有两个, 名字没起好, 要明白 各个Login的含义
  1. #include <iostream>
  2. #include <string>
  3. #include "user.pb.h"
  4. // using namespace hzhrpc;
  5. /*
  6. UserService 原来是一个 本地服务, 提供了 两个进程内的本地方法, Login和GetFriendLists
  7. */
  8. class UserService : public hzhrpc::UserServiceRpc  // 这个rpc类使用在 rpc服务发布端(rpc服务提供者)
  9. {
  10.     public:
  11.     bool  Login(const std::string username, const std::string password)
  12.     {
  13.         std::cout << "doing local service : Login" << std::endl;
  14.         std::cout<<"username: " << username << " password: " << password << std::endl;
  15.         return true;
  16.     }
  17.     // 重写  UserServiceRpc 里的 Login方法
  18.     // 1. caller ====> 发起请请求 Login(request)  ==> 通过 muduo  ==> callee
  19.     // 2. callee ====> 收到请求 Login(request)  ==> 交到下面重写的 Login方法
  20.     void Login(::google::protobuf::RpcController* controller,   // 这里命名空间不写hzhrpc::, 直接写google::protobuf   特别注意
  21.         const ::hzhrpc::LoginRequest* request,
  22.         ::hzhrpc::LoginResponse* response,
  23.         ::google::protobuf::Closure* done)
  24.         {
  25.             // 1. 框架给业务上报了请求参数LoginRequest, 应用 获取相应数据做本地业务
  26.             std::string username = request->name();
  27.             std::string password = request->pwd();
  28.             // 2. 做本地业务
  29.             bool login_result = Login(username, password);
  30.             // 3. 业务处理完毕, 设置响应参数
  31.             // 包括 设置返回码, 提示信息, 以及业务处理结果
  32.             hzhrpc::ResultCode* code = response->mutable_result();  // 可修改的
  33.             code->set_errcode(0);  // 0表示成功
  34.             code->set_errmsg("success");  // 成功的提示信息
  35.             response->set_success(login_result);  // 设置成功与否的标志
  36.             // 4. 执行回调函数
  37.             // 通过查看 Closure 类, run是个 纯虚函数, 即回调函数, 需要重写
  38.             // 这里的done是个回调函数, 由框架提供, 业务上不需要关心
  39.             // 任务: 执行响应对象数据的序列化和网络发送 (都是由 框架来完成的)
  40.            done->Run();  
  41.         }
  42. };
  43. int main(int argc, char* argv[])
  44. {
  45.     return 0;
  46. }
复制代码
阶段大总结-实现流程

目标

将业务层的本地方法(如用户登录)发布成 RPC 服务,供长途客户端调用。
实现步调

   1. 编写 .proto 文件

  
  2. 使用 protoc 生成代码

  
  3. 从 UserServiceRpc 继承并重写方法

  
  4. 框架负责调用逻辑

  
  后续将举行如何发布

mprpc框架基础类设计

目标

将本地 UserService:ogin() 方法发布成一个长途可调用的 RPC 方法,形成一个“可独立部署、可多实例扩展”的微服务节点。
初始化框架


创建 服务发布 对象


业务代码中使用框架发布服务

从业务入手, 看框架
直接看框架 是学不明白的
  1. int main(int argc, char* argv[])
  2. {
  3.     // 1. 初始化框架
  4.     MprpcApplication::Init(argc, argv);
  5.     // 2. 创建 provider 并注册服务
  6.     RpcProvider provider;
  7.     provider.NotifyService(new UserService());  // 注册并发布服务, 这里的UserService是个本地服务, 不是rpc服务
  8.     // 3. 启动rpc服务发布 节点
  9.     provider.Run();  // 启动服务, 这里的Run是阻塞状态, 一直监听rpc调用请求
  10.     return 0;
  11. }
复制代码
为什么这么设计?


框架目录结构

  1. src/
  2. ├── include/
  3. │   ├── mprpcapplication.h    // 初始化类头文件
  4. │   └── rpcprovider.h         // 服务发布类头文件
  5. ├── mprpcapplication.cc
  6. ├── rpcprovider.cc
复制代码
CMake 配置中记得加上:
  1. include_directories(${PROJECT_SOURCE_DIR}/src/include)
复制代码
框架类为什么设计成单例?

   1. 只必要初始化一次

  
  2. 全局访问方便

  
  3. 确保状态同等

  
    网络服务模块可以直接通过单例获取配置,如绑定地址、端口。
  日志体系可以读取框架统一配置,决定是否输出调试信息。
  RPC 调用封装中可以依靠框架统一的超时时间、协议设置等。
  框架类.h-code

回首单例 设计模式!!
  1. #pragma once// mprpc框架的基础类, 负责框架的初始化工作class MprpcApplication {    public:        static void Init(int argc, char **argv);
  2.         static MprpcApplication& GetInstance();               private:        MprpcApplication() {}                         // 克制外部构造        MprpcApplication(const MprpcApplication&) = delete; // 克制拷贝构造        MprpcApplication& operator=(const MprpcApplication&) = delete; // 克制赋值构造        };   
复制代码
RpcProvider:RPC 服务发布者

   [!TIP]
  由于 这是一个 服务类, 就会被许多人 请求这个 rpc调用
  因此, 必须是 高并发的, 所以要使用 muduo 网络库
    [!IMPORTANT]
  不要写死 框架类!!
    在 RpcProvider 中直接写 UserService* 这样的指针,是严重不合理的。
  因为:
  
    框架应该是通用的服务容器

  
    通过 查察 pb.h
  UserSeviceRpc 源于
  1. class UserServiceRpc : public ::PROTOBUF_NAMESPACE_ID::Service{}
复制代码
  1. Service--->google::protobuf::Service
复制代码
设计理念总结


服务发布者类.h-code

  1. #pragma one
  2. #include "google/protobuf/service.h" // 这个头文件里有 RpcController, Closure, Service
  3. // 框架提供的专门服务发布 rpc 服务的类对象
  4. class RpcProvider {
  5.     public:
  6.     // 这里是框架提供给外部使用的, 可以发布rpc方法的函数接口
  7.         void NotifyService(google::protobuf::Service *server); // 注册服务  
  8.         // 传参那边是 new UserService() 这个对象, 这里是个指针, 传递给框架, 不能用引用
  9.         // 启动rpc服务发布 节点, 开始提供rpc远程网络调用服务
  10.         void Run();
  11.     };
  12.    
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 qidao123.com技术社区-IT企服评测·应用市场 (https://dis.qidao123.com/) Powered by Discuz! X3.4