施磊老师rpc(二)

打印 上一主题 下一主题

主题 1930|帖子 1930|积分 5792

protobuf使用(三)

复习

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


  • 基本类型(string、int32、bool…);
  • 组合对象(message 嵌套 message);
  • 列表类型:repeated;
  • 映射表类型:map<key_type, value_type>(注意不是 C++ STL 的 map);
  • 枚举类型(enum);
  • RPC 服务描述(service):这是本节新增重点。
注意:Protobuf 的 map 类型与 C++ STL 中的 map 是不同的,仅为消息结构的语法糖。
“语法糖”是编程语言里的一个术语,意思是:
   一种让代码更好写、更好读的语法情势,本质上没有引入新功能,只是更简洁或直观。
    [!TIP]
  Protobuf 的 map 和 C++ STL 的 map 的区别:
  雷同点:
  

  • 都是键值对的数据结构。
  • 都能用来表示:key -> value 的映射关系。
  • 都可以通过 key 查找对应的 value。
  
不同点(关键):
项目Protobuf 中的 mapC++ STL 中的 map类型一种消息字段的语法糖一个真正的容器类底层实现被编译成一个隐藏的 repeated message基于均衡二叉树或哈希表功能限制不能做复杂操纵(如排序、自界说比力函数)支持全功能,如插入、排序、自界说比力等使用目标用来在消息中表达键值对结构用来在内存中管理数据关系支持语言广度统一跨语言序列化格式仅限于 C++ 标准库 Protobuf 与 RPC 的关系



  • Protobuf 本身不具备 RPC 能力,仅提供:

    • 参数的序列化
    • 相应的反序列化

  • RPC 框架使用 Protobuf 是为了结构化传输参数和结果
为什么要用 ProtoBuf



  • ProtoBuf 用于结构化数据的序列化与反序列化,提拔网络传输效率;
  • 分布式通讯(RPC)框架 中,ProtoBuf 可以用于描述调用的参数和返回值
  • 本质上: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;
复制代码


  • 默认不会生成 service 相干的类(包括 stub 类);
  • 必须加上该 option 才会生成带有 rpc 方法声明的类。
开启并编译后, 将会生成 以下
  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. };
复制代码


  • Stub 就是客户端代理类,封装了底层网络通讯细节;
  • 实现原理类似于我们 RPC 框架图中讲的 stub -> channel -> codec -> network。
理解service类


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



  • 返回值:一个指向 ServiceDescriptor 的指针。
  • 含义:ServiceDescriptor 是对一个服务的描述对象。
  • 描述内容

    • 服务名(如 UserService)
    • 服务中包罗的 RPC 方法名(如 Login, GetFriendList)

2. 为什么必要 ServiceDescriptor



  • 在举行 RPC 调用 时,必要知道:

    • 调用的是哪个服务(哪个类的对象)
    • 调用的是哪个方法(方法名)

  • ServiceDescriptor 就提供了这种“元信息”,可以让框架动态识别服务与方法,举行 RPC 调度。
3. service 类是如何生成的



  • 由 protoc 工具根据 .proto 文件生成。
  • 默认生成的类不是 UserServiceRpc,而是 UserService。
  • 这个生成类用于服务端(provider / callee)(配合理论图)实现接口,是 RPC 服务的提供者端
4. protobuf 中的两个核心抽象



  • message:表示消息结构(数据)
  • service:表示服务接口(方法)
5. RPC 方法结构统一



  • 每个 RPC 方法最终都以统一情势出现(一般4个参数)
  • 如:请求参数、相应参数都封装在对应的 xxxRequest 和 xxxResponse 中。
protobuf使用(四)


Protobuf 生成的两个核心类


  • UserServiceRpc 类

    • RPC 服务提供者 端使用的类。
    • 继承自 ::google::protobuf::Service。
    • 包罗 RPC 方法(如 Login、GetFriendList)的 虚函数声明,供服务端举行 具体业务实现
    • 构造函数无参,是服务端自动注册服务对象时使用的。

  • UserServiceRpc_Stub 类(桩类)

    • RPC 服务消费者(调用者) 使用的代理类。
    • 继承自 UserServiceRpc。
    • 实现了基类中的虚函数(好比 Login),但实现方式不是执行业务,而是调用底层 RpcChannel::CallMethod。
    • 构造函数必须接收一个 RpcChannel\* 指针,这是关键点。
    1. // .h
    2. class UserServiceRpc_Stub : public UserServiceRpc
    3.   {
    4.   public:
    5.     UserServiceRpc_Stub(::PROTOBUF_NAMESPACE_ID::RpcChannel *channel);
    6.     UserServiceRpc_Stub(::PROTOBUF_NAMESPACE_ID::RpcChannel *channel,
    7.                         ::PROTOBUF_NAMESPACE_ID::Service::ChannelOwnership ownership);
    8.     void Login(::PROTOBUF_NAMESPACE_ID::RpcController *controller,
    9.                const ::hzhpro::LoginRequest *request,
    10.                ::hzhpro::LoginResponse *response,
    11.                ::google::protobuf::Closure *done);
    12.     void GetFriendList(::PROTOBUF_NAMESPACE_ID::RpcController *controller,
    13.                        const ::hzhpro::GetFriendListsRequest *request,
    14.                        ::hzhpro::GetFriendListsResponse *response,
    15.                        ::google::protobuf::Closure *done);
    16.         ....
    17.    
    18.   private:
    19.     ::PROTOBUF_NAMESPACE_ID::RpcChannel *channel_;
    20.     ....
    21. }
    复制代码
    1. // .cc  具体实现
    2. void UserServiceRpc_Stub::Login(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
    3.                               const ::hzhpro::LoginRequest* request,
    4.                               ::hzhpro::LoginResponse* response,
    5.                               ::google::protobuf::Closure* done) {
    6.   channel_->CallMethod(descriptor()->method(0),
    7.                        controller, request, response, done);
    8. }
    9. void UserServiceRpc_Stub::GetFriendList(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
    10.                               const ::hzhpro::GetFriendListsRequest* request,
    11.                               ::hzhpro::GetFriendListsResponse* response,
    12.                               ::google::protobuf::Closure* done) {
    13.   channel_->CallMethod(descriptor()->method(1),
    14.                        controller, request, response, done);
    15. }
    复制代码
          [!IMPORTANT]
        具体实现 的 函数------> 没有实现业务, 而是调用 channel
        channel 又是 :ROTOBUF_NAMESPACE_ID::RpcChannel * 类型
       
  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 是一个 抽象类,界说了纯虚函数 CallMethod。
  • 所有 RPC 方法最终都通过 CallMethod 转发出去,实现 序列化、网络发送、相应接收与反序列化
  • 桩类方法中调用的是:
    1. _channel->CallMethod(method, controller, request, response, done);
    复制代码
自界说 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 通讯。
调用流程总结


  • 客户端构造一个桩类对象:
    1. UserServiceRpc_Stub stub(new MyRpcChannel());
    复制代码
  • 调用 RPC 方法:
    1. stub.Login(controller, &request, &response, nullptr);
    复制代码
  • 现实执行:

    • 并不会执行业务逻辑。
    • 而是调用 MyRpcChannel::CallMethod,由你自界说实现网络通讯和序列化逻辑。

理解的重点发起



  • 理解 Stub 类 ≠ 业务逻辑,它是一个 代理类,所有调用都重定向到 RpcChannel。
  • 理解 Protobuf 提供的 类结构层次:Service → Stub → Channel
  • 理解 继承 + 多态 + 虚函数重写 在此中的作用机制。
  • 最终所有复杂的长途通讯都被隐藏在了 RpcChannel::CallMethod 背后。
   [!TIP]
  此时 再去 看 rpc那个 理论图, 会很清晰, 会明白 什么是代理类(stub)
    [!IMPORTANT]
  最好有项目实践, 不然很难理解
  本地服务发布rpc(一)

项目目标



  • 不是做业务体系,而是做一个RPC 框架
  • 框架核心职责:让上层应用的本地方法变成 RPC 长途可调用方法
  • 框架不做具体业务,服务于业务
  • 但是 不能脱离 业务
开发方式



  • 业务驱动框架设计:先引出具体业务需求,再倒推必要框架提供哪些功能
  • 以 UserService(登录 + 获取好友列表)为示例贯穿开发过程
项目结构说明

  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::RpcController* controller


  • 作用:用于控制整个 RPC 调用过程,包括处置惩罚错误、取消、设置/获取错误信息等。
  • 说明:客户端和服务器都可以使用它来反映 RPC 状态(如失败信息)。如果你必要反馈 RPC 错误,好比请求格式不对、认证失败,可以通过它设置错误。


  • const ::hzhrpc:oginRequest* request


  • 作用:客户端发来的请求数据,封装成了 LoginRequest 对象。
  • 说明:这是 protobuf 自动生成的请求结构体,包罗用户登录所需的信息,好比用户名、暗码等。服务器通过读取它来处置惩罚登录逻辑。


  • ::hzhrpc:oginResponse* response


  • 作用:用于将服务端处置惩罚结果返回给客户端。
  • 说明:服务器会填充这个相应结构体,好比设置登录是否乐成、用户ID、错误信息等,然后通过 RPC 框架发回客户端。


  • ::google::protobuf::Closure* done


  • 作用:这是一个回调函数指针,表示 RPC 方法处置惩罚完成时必要执行的操纵。
  • 说明

    • 服务端逻辑处置惩罚完成后,必须调用 done->Run() 来触发后续操纵,如将 response 发回客户端。
    • 如果不调用 done->Run(),客户端将一直等待相应,造成阻塞或超时。


增补说明:

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


  • RpcController 和 Closure 是 Google Protobuf 官方界说的基类接口,属于 google::protobuf 命名空间;
  • LoginRequest 和 LoginResponse 是你自界说的 protobuf 服务结构体,位于 hzhrpc 命名空间。
本地服务发布rpc(二)

配景说明



  • 我们盼望把本地的 login 函数变成长途可以调用的 RPC 服务
  • 整个过程基于 Protobuf 的序列化能力 + 自界说 RPC 框架的流程控制实现。
  • 客户端通过网络发送了 LoginRequest 请求,服务器框架接收到后会自动触发业务方实现的 Login 方法。
两个rpc类->proto后

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

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

   1. 界说 proto 文件

  

  • 写明:

    • rpc方法名(如 Login)
    • 请求参数类型(LoginRequest)
    • 相应参数类型(LoginResponse)

  • 作用:这是客户端与服务端的通讯约定,客户端只需按这个约定调用即可。
  举例:
  1. service UserServiceRpc {
  2.     rpc Login(LoginRequest) returns (LoginResponse);
  3. }
复制代码
2. 从生成的服务类中继承

  

  • 编译 proto 后会生成一个 UserServiceRpc 类(或类似)
  • 在你的业务代码中继承这个类,并重写此中的方法(如 Login 方法)
  • 也可以使用 lambda 表达式 来传递函数对象实现方法逻辑
  3. 业务处置惩罚逻辑

  

  • Login 方法会由框架自动调用

    • 框架接收到客户端请求后,会通过方法名匹配调用相应函数
    • 请求参数会通过函数参数传入
    • 在函数内:

      • 解析请求数据
      • 举行本地业务处置惩罚(好比验证用户名暗码)
      • 构造相应对象 LoginResponse
      • 填写相应内容


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

  

  • 相应对象必须通过 序列化 变成字节流再通过网络发送
  • 序列化方式:SerializeToArray()、SerializeToString()(由 protobuf 提供)
  • 最后一步是调用 done->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 文件

  

  • 界说 RPC 方法签名(例如 rpc Login(...) returns (...))
  • 界说请求消息类型(如 LoginRequest)
  • 界说相应消息类型(如 LoginResponse)
  • 注意:这是与长途调用方的通讯协议,必须明确方法名、入参、出参
  2. 使用 protoc 生成代码

  

  • 使用 protoc 编译 .proto 文件,生成 C++ 代码
  • 生成两个关键类:

    • UserServiceRpc:服务提供方继承并实现的类
    • UserServiceRpc_Stub:服务调用方使用的类(Stub)

  • 当前仅关注服务端:我们要实现 并 发布服务
  3. 从 UserServiceRpc 继承并重写方法

  

  • 在业务代码中继承该类
  • 重写 .proto 中界说的 RPC 方法(如 Login)

    • 参数说明

      • 第一个参数, 不是很重要
      • const LoginRequest* request:请求参数对象
      • LoginResponse* response:用于填写相应内容
      • Closure* done:框架提供的回调,调用后会执行序列化并发送相应(通过 done->Run())


  4. 框架负责调用逻辑

  

  • 框架在收到长途请求后:

    • 根据方法名匹配对应函数
    • 填充参数并调用你实现的 Login 方法
    • 最终执行 done->Run(),序列化相应并通过网络发出

  后续将举行如何发布

mprpc框架基础类设计

目标

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



  • 创建类:MprpcApplication
  • 提供接口: ---- 本身是个服务器, 传参 不写死, ip/port
    1. void Init(int argc, char **argv);
    复制代码
  • 功能:读取配置文件(如ip/port),初始化日志模块等
  • 推荐设计:单例模式,方便在全局共享框架信息
  • 传参 读 配置文件----[框架初始化函数-文件读取](# 框架初始化函数-文件读取)
创建 服务发布 对象



  • 类名:RpcProvider
  • 核心功能:

    • NotifyService(google::protobuf::Service* service)
      注册 RPC 服务对象(不能依靠具体业务类,只接受抽象基类)
    • Run()
      启动 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. }
复制代码
为什么这么设计?



  • 解耦服务:login 是一个独立服务模块,改动后只需重新部署 login,不影响其他功能。
  • 易于扩展:将来可以部署多个 login 节点,支持分布式部署、负载均衡。
  • 易用性强:框架使用简朴,封装复杂逻辑,让用户只需关注服务发布即可。
  • 面向抽象设计:框架接受 protobuf 的基类 Service*,支持任意 rpc 服务,而不是绑定某个业务类。
框架目录结构

  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. 只必要初始化一次

  

  • 配置信息(如ip、端口、日志级别)只在程序启动时读取一次,之后整个程序生命周期内共享使用。
  • 不必要每次调用服务都重复读取配置,节流资源、克制重复初始化。
  2. 全局访问方便

  

  • 一旦初始化完成,后续任意模块都可以通过 MprpcApplication::GetInstance() 获取到配置信息。
  • 克制传递多个参数或对象,进步代码整洁度与可维护性。
  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* 这样的指针,是严重不合理的。
  因为:
  

  • 框架的本质是通用性:它应该支持任意业务服务的注册,而不但仅是 UserService。
  • 如果在框架代码里依靠了业务类,就会导致框架无法复用,一旦业务变了,框架也得改 —— 完全违反“高内聚、低耦合”的设计原则。
    框架应该是通用的服务容器

  

  • 例如 RpcProvider 提供一个接口叫 NotifyService(service)。
  • 传进来的 service,大概是 UserService、FriendService、OrderService 等等。
  • 所以框架必须设计成能够接收任意 service 的注册,而不是依靠于具体类。
    通过 查察 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企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

tsx81428

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表