施磊老师rpc(二)

文章目录

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

ProtobufmapC++ STLmap 的区别:

相同点:

  • 都是键值对的数据结构。
  • 都能用来表示:key -> value 的映射关系。
  • 都可以通过 key 查找对应的 value

不同点(关键):

项目 Protobuf 中的 map C++ STL 中的 map
类型 一种消息字段的语法糖 一个真正的容器类
底层实现 被编译成一个隐藏的 repeated message 基于平衡二叉树或哈希表
功能限制 不能做复杂操作(如排序、自定义比较函数) 支持全功能,如插入、排序、自定义比较等
使用目的 用来在消息中表达键值对结构 用来在内存中管理数据关系
支持语言广度 统一跨语言序列化格式 仅限于 C++ 标准库

Protobuf 与 RPC 的关系

  • Protobuf 本身不具备 RPC 能力 ,仅提供:
    • 参数的序列化
    • 响应的反序列化
  • RPC 框架使用 Protobuf 是为了结构化传输参数和结果

为什么要用 ProtoBuf

  • ProtoBuf 用于结构化数据的序列化与反序列化,提升网络传输效率;
  • 分布式通信(RPC)框架 中,ProtoBuf 可以用于描述调用的参数和返回值
  • 本质上:ProtoBuf 不负责通信,只负责消息格式的定义

service定义rpc

service 表示定义一个服务类;

rpc 方法名 (参数类型) returns (返回类型)

对应业务逻辑中的远程函数。

protobuf 复制代码
// 在protobuf 定义描述 rpc类型
service UserServiceRpc {
    rpc Login(LoginRequest) returns (LoginResponse);
    rpc GetFriendList(GetFriendListsRequest) returns (GetFriendListsResponse);
  }

必须启用 service 的生成:

只写 service 类, 进行protoc 编译, 是不会生成 service类的

c++ 复制代码
option cc_generic_services = true;
  • 默认不会生成 service 相关的类(包括 stub 类);
  • 必须加上该 option 才会生成带有 rpc 方法声明的类。

开启并编译后, 将会生成 以下

c++ 复制代码
class UserServiceRpc_Stub;

class UserServiceRpc 

protobuf代码结构--重点

Login 为例,会生成如下内容:

!TIP

新版messgae结构加了一个小东西---final

c++ 复制代码
class LoginRequest final :

  public ::PROTOBUF_NAMESPACE_ID::Message{}

在 C++ 中,final 是一个限定符(specifier),它的作用是:

禁止类被继承,或禁止虚函数被重写(override)

1. Message 类型:
c++ 复制代码
class LoginRequest : public google::protobuf::Message {
  // 提供 GetName(), SetName(), GetPwd(), SetPwd()
};


class LoginResponse : public google::protobuf::Message {
  // 同样提供成员变量的读写方法
};
2. Service 类型(抽象类):
c++ 复制代码
class UserServiceRpc_Stub;

class UserServiceRpc : 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 类型(代理类):
c++ 复制代码
class UserServiceRpc_Stub : public UserServiceRpc {
  // 提供 Login 方法的远程代理实现
};
  • 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个参数)
  • 如:请求参数、响应参数都封装在对应的 xxxRequestxxxResponse 中。

protobuf使用(四)

Protobuf 生成的两个核心类

  1. UserServiceRpc 类

    • RPC 服务提供者 端使用的类。
    • 继承自 ::google::protobuf::Service
    • 包含 RPC 方法(如 LoginGetFriendList)的 虚函数声明 ,供服务端进行 具体业务实现
    • 构造函数无参,是服务端主动注册服务对象时使用的。
  2. UserServiceRpc_Stub 类(桩类)

    • RPC 服务消费者(调用者) 使用的代理类。
    • 继承自 UserServiceRpc
    • 实现了基类中的虚函数(比如 Login),但实现方式不是执行业务 ,而是调用底层 RpcChannel::CallMethod
    • 构造函数必须接收一个 RpcChannel\* 指针,这是关键点。
    c++ 复制代码
    // .h
    class UserServiceRpc_Stub : public UserServiceRpc
      {
      public:
        UserServiceRpc_Stub(::PROTOBUF_NAMESPACE_ID::RpcChannel *channel);
        UserServiceRpc_Stub(::PROTOBUF_NAMESPACE_ID::RpcChannel *channel,
                            ::PROTOBUF_NAMESPACE_ID::Service::ChannelOwnership ownership);
        void Login(::PROTOBUF_NAMESPACE_ID::RpcController *controller,
                   const ::hzhpro::LoginRequest *request,
                   ::hzhpro::LoginResponse *response,
                   ::google::protobuf::Closure *done);
        void GetFriendList(::PROTOBUF_NAMESPACE_ID::RpcController *controller,
                           const ::hzhpro::GetFriendListsRequest *request,
                           ::hzhpro::GetFriendListsResponse *response,
                           ::google::protobuf::Closure *done);
    	....
        
      private:
        ::PROTOBUF_NAMESPACE_ID::RpcChannel *channel_;
        ....
    }
    c++ 复制代码
    // .cc  具体实现
    
    void UserServiceRpc_Stub::Login(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
                                  const ::hzhpro::LoginRequest* request,
                                  ::hzhpro::LoginResponse* response,
                                  ::google::protobuf::Closure* done) {
      channel_->CallMethod(descriptor()->method(0),
                           controller, request, response, done);
    }
    void UserServiceRpc_Stub::GetFriendList(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
                                  const ::hzhpro::GetFriendListsRequest* request,
                                  ::hzhpro::GetFriendListsResponse* response,
                                  ::google::protobuf::Closure* done) {
      channel_->CallMethod(descriptor()->method(1),
                           controller, request, response, done);
    }

    !IMPORTANT

    具体实现 的 函数------> 没有实现业务, 而是调用 channel

    channel 又是 ::PROTOBUF_NAMESPACE_ID::RpcChannel * 类型

c++ 复制代码
class PROTOBUF_EXPORT RpcChannel {
 public:
 	....
  virtual void CallMethod(const MethodDescriptor* method,
                          RpcController* controller, const Message* request,
                          Message* response, Closure* done) = 0;
	....
 private:
  GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(RpcChannel);
};

RpcChannel 的作用与机制

  • RpcChannel 是一个 抽象类 ,定义了纯虚函数 CallMethod

  • 所有 RPC 方法最终都通过 CallMethod 转发出去,实现 序列化、网络发送、响应接收与反序列化

  • 桩类方法中调用的是:

    复制代码
    _channel->CallMethod(method, controller, request, response, done);

自定义 RpcChannel 实现

为了真正执行 RPC 远程调用,需要开发者自己实现一个类

c++ 复制代码
class MyRpcChannel : public RpcChannel {
public:
    void CallMethod(...) override {
        // 1. 对 request 序列化
        // 2. 构造 RPC 请求包
        // 3. 发送网络请求
        // 4. 接收响应并反序列化到 response
    }
};

该类实现了 CallMethod 的逻辑,真正完成 RPC 通信。

调用流程总结

  1. 客户端构造一个桩类对象:

    复制代码
    UserServiceRpc_Stub stub(new MyRpcChannel());
  2. 调用 RPC 方法:

    复制代码
    stub.Login(controller, &request, &response, nullptr);
  3. 实际执行:

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

理解的重点建议

  • 理解 Stub 类 ≠ 业务逻辑 ,它是一个 代理类 ,所有调用都重定向到 RpcChannel
  • 理解 Protobuf 提供的 类结构层次:Service → Stub → Channel
  • 理解 继承 + 多态 + 虚函数重写 在其中的作用机制。
  • 最终所有复杂的远程通信都被隐藏在了 RpcChannel::CallMethod 背后。

!TIP

此时 再去 看 rpc那个 理论图, 会很清晰, 会明白 什么是代理类(stub)

!IMPORTANT

最好有项目实践, 不然很难理解

本地服务发布rpc(一)

项目目标

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

开发方式

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

项目结构说明

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

CMakeLists.txt

c++ 复制代码
# 设置cmake最低版本 和 项目名称
cmake_minimum_required(VERSION 3.0)
project(mprpc-hzh)

# 设置项目可执行文件输出的路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
# 设置项目库文件输出的路径
set(LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib)
# 设置编译头文件搜索路径 -I
include_directories(${PROJECT_SOURCE_DIR}/src)
# 设置项目库文件搜索路径 -L
link_directories(${PROJECT_SOURCE_DIR}/lib)

add_subdirectory(src) 
# 放 使用 rpc服务的 使用者 caller和 消费者callee
add_subdirectory(example)   

业务示例 - UserService

把一个原本本地类的方法 UserService::Login(name, pwd) 变成一个可以远程调用的 RPC 方法

c++ 复制代码
#include <iostream>
#include <string>


/*
UserService 原来是一个 本地服务, 提供了 两个进程内的本地方法, Login和GetFriendLists
*/
class UserService 
{
    public:
    bool  Login(const std::string username, const std::string password) 
    {
        std::cout << "doing local service : Login" << std::endl;
        std::cout<<"username: " << username << " password: " << password << std::endl;
    }
};

int main(int argc, char* argv[])
{

    
    return 0;
}

RPC 方法定义(Protobuf)

example/user.proto

c++ 复制代码
syntax = "proto3";
package example;

option cc_generic_services = true; // 生成服务类和rpc方法描述, 默认不生成

message ResultCode
{
    int32 errcode = 1;
    bytes errmsg = 2;
}


// 定义登录请求消息类型 name pwd
message LoginRequest
{
    bytes name = 1;
    bytes pwd = 2;
}

// 定义登录响应消息类型
message LoginResponse
{
    ResultCode result = 1;
    bool success = 3;
}

service UserServiceRpc {
    rpc Login(LoginRequest) returns (LoginResponse);
}

编译---->在cmake 加 头文件搜索路径

example/CMakeLists.txt

c++ 复制代码
add_subdirectory(callee)

example/callee/CMakeLists.txt

c++ 复制代码
set(SRC_LIST userservice.cc ../user.pb.cc)

add_executable(provider ${SRC_LIST})

修改业务代码-加rpc

c++ 复制代码
#include <iostream>
#include <string>
#include "user.pb.h"

/*
UserService 原来是一个 本地服务, 提供了 两个进程内的本地方法, Login和GetFriendLists
*/
class UserService : public UserServiceRpc
{
    public:
    bool  Login(const std::string username, const std::string password) 
    {
        std::cout << "doing local service : Login" << std::endl;
        std::cout<<"username: " << username << " password: " << password << std::endl;
    }
};

int main(int argc, char* argv[])
{


    return 0;
}

错误-1:

add_subdirectory(src) , src里面必须有 cmake配置文件, 哪怕是空的

add_subdirectory() 会递归调用指定目录下的 CMakeLists.txt,如果这个文件不存在,CMake 配置过程就会报错,提示找不到构建脚本。

错误-2:

!WARNING

proto 相关的 命名空间!!!

c++ 复制代码
#include <iostream>
#include <string>
#include "user.pb.h"
// using namespace hzhrpc;

/*
UserService 原来是一个 本地服务, 提供了 两个进程内的本地方法, Login和GetFriendLists
*/
class UserService : public hzhrpc::UserServiceRpc
{
    public:
    bool  Login(const std::string username, const std::string password) 
    {
        std::cout << "doing local service : Login" << std::endl;
        std::cout<<"username: " << username << " password: " << password << std::endl;
        return true;
    }
};

int main(int argc, char* argv[])
{


    return 0;
}

重写基类的虚函数(部分)

c++ 复制代码
// 重写  UserServiceRpc 里的 Login方法
    void Login(::google::protobuf::RpcController* controller,   // 这里命名空间不写hzhrpc::, 直接写google::protobuf   特别注意
        const ::hzhrpc::LoginRequest* request,
        ::hzhrpc::LoginResponse* response,
        ::google::protobuf::Closure* done);

各参数理解

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

  1. const ::hzhrpc::LoginRequest* request
  • 作用 :客户端发来的请求数据,封装成了 LoginRequest 对象。
  • 说明:这是 protobuf 自动生成的请求结构体,包含用户登录所需的信息,比如用户名、密码等。服务器通过读取它来处理登录逻辑。

  1. ::hzhrpc::LoginResponse* response
  • 作用:用于将服务端处理结果返回给客户端。
  • 说明:服务器会填充这个响应结构体,比如设置登录是否成功、用户ID、错误信息等,然后通过 RPC 框架发回客户端。

  1. ::google::protobuf::Closure* done
  • 作用:这是一个回调函数指针,表示 RPC 方法处理完成时需要执行的操作。
  • 说明
    • 服务端逻辑处理完成后,必须调用 done->Run() 来触发后续操作,如将 response 发回客户端。
    • 如果不调用 done->Run(),客户端将一直等待响应,造成阻塞或超时。

补充说明:

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

这是因为:

  • RpcControllerClosureGoogle Protobuf 官方定义的基类接口 ,属于 google::protobuf 命名空间;
  • LoginRequestLoginResponse 是你自定义的 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
  • 作用:这是客户端与服务端的通信约定,客户端只需按这个约定调用即可。

举例:

c++ 复制代码
service UserServiceRpc {
    rpc Login(LoginRequest) returns (LoginResponse);
}
2. 从生成的服务类中继承
  • 编译 proto 后会生成一个 UserServiceRpc 类(或类似)
  • 在你的业务代码中继承这个类,并重写其中的方法 (如 Login 方法)
  • 也可以使用 lambda 表达式 来传递函数对象实现方法逻辑
3. 业务处理逻辑
  • Login 方法会由框架自动调用
    • 框架接收到客户端请求后,会通过方法名匹配调用相应函数
    • 请求参数会通过函数参数传入
    • 在函数内:
      • 解析请求数据
      • 进行本地业务处理(比如验证用户名密码)
      • 构造响应对象 LoginResponse
      • 填写响应内容
4. 发送响应数据-run实现
  • 响应对象必须通过 序列化 变成字节流再通过网络发送
  • 序列化方式:SerializeToArray()SerializeToString()(由 protobuf 提供)
  • 最后一步是调用 done->Run(),由框架统一处理序列化与网络发送

callee代码整体

注意一下, Login 函数有两个, 名字没起好, 要明白 各个Login的含义

c++ 复制代码
#include <iostream>
#include <string>
#include "user.pb.h"
// using namespace hzhrpc;

/*
UserService 原来是一个 本地服务, 提供了 两个进程内的本地方法, Login和GetFriendLists
*/
class UserService : public hzhrpc::UserServiceRpc  // 这个rpc类使用在 rpc服务发布端(rpc服务提供者) 
{
    public:
    bool  Login(const std::string username, const std::string password) 
    {
        std::cout << "doing local service : Login" << std::endl;
        std::cout<<"username: " << username << " password: " << password << std::endl;
        return true;
    }


    // 重写  UserServiceRpc 里的 Login方法
    // 1. caller ====> 发起请请求 Login(request)  ==> 通过 muduo  ==> callee
    // 2. callee ====> 收到请求 Login(request)  ==> 交到下面重写的 Login方法
    void Login(::google::protobuf::RpcController* controller,   // 这里命名空间不写hzhrpc::, 直接写google::protobuf   特别注意
        const ::hzhrpc::LoginRequest* request,
        ::hzhrpc::LoginResponse* response,
        ::google::protobuf::Closure* done)
        {

            // 1. 框架给业务上报了请求参数LoginRequest, 应用 获取相应数据做本地业务
            std::string username = request->name();
            std::string password = request->pwd();


            // 2. 做本地业务
            bool login_result = Login(username, password); 

            // 3. 业务处理完毕, 设置响应参数
            // 包括 设置返回码, 提示信息, 以及业务处理结果
            hzhrpc::ResultCode* code = response->mutable_result();  // 可修改的
            code->set_errcode(0);  // 0表示成功
            code->set_errmsg("success");  // 成功的提示信息
            response->set_success(login_result);  // 设置成功与否的标志

            // 4. 执行回调函数
            // 通过查看 Closure 类, run是个 纯虚函数, 即回调函数, 需要重写
            // 这里的done是个回调函数, 由框架提供, 业务上不需要关心
            // 任务: 执行响应对象数据的序列化和网络发送 (都是由 框架来完成的)
           done->Run();  

        }
};

int main(int argc, char* argv[])
{


    return 0;
}

阶段大总结-实现流程

目标

将业务层的本地方法(如用户登录)发布成 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::Login() 方法发布成一个远程可调用的 RPC 方法,形成一个"可独立部署、可多实例扩展"的微服务节点。

初始化框架

  • 创建类:MprpcApplication

  • 提供接口: ---- 本身是个服务器, 传参 不写死, ip/port

复制代码
  void Init(int argc, char **argv);
  • 功能:读取配置文件(如ip/port),初始化日志模块等

  • 推荐设计:单例模式,方便在全局共享框架信息

  • 传参 读 配置文件----[框架初始化函数-文件读取](# 框架初始化函数-文件读取)

创建 服务发布 对象

  • 类名:RpcProvider
  • 核心功能:
    • NotifyService(google::protobuf::Service* service)
      注册 RPC 服务对象(不能依赖具体业务类,只接受抽象基类)
    • Run()
      启动 RPC 网络服务,等待远程调用

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

从业务入手, 看框架

直接看框架 是学不明白的

c++ 复制代码
int main(int argc, char* argv[])
{
    // 1. 初始化框架
    MprpcApplication::Init(argc, argv);

    // 2. 创建 provider 并注册服务
    RpcProvider provider;
    provider.NotifyService(new UserService());  // 注册并发布服务, 这里的UserService是个本地服务, 不是rpc服务

    // 3. 启动rpc服务发布 节点
    provider.Run();  // 启动服务, 这里的Run是阻塞状态, 一直监听rpc调用请求

    return 0;
}

为什么这么设计?

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

框架目录结构

c++ 复制代码
src/
├── include/
│   ├── mprpcapplication.h    // 初始化类头文件
│   └── rpcprovider.h         // 服务发布类头文件
├── mprpcapplication.cc
├── rpcprovider.cc

CMake 配置中记得加上:

复制代码
include_directories(${PROJECT_SOURCE_DIR}/src/include)

框架类为什么设计成单例?

1. 只需要初始化一次
  • 配置信息(如ip、端口、日志级别)只在程序启动时读取一次,之后整个程序生命周期内共享使用。
  • 不需要每次调用服务都重复读取配置,节省资源、避免重复初始化。
2. 全局访问方便
  • 一旦初始化完成,后续任意模块都可以通过 MprpcApplication::GetInstance() 获取到配置信息。
  • 避免传递多个参数或对象,提高代码整洁度与可维护性。
3. 确保状态一致
  • 使用单例可以避免多个实例产生不同的状态副本(如多个日志配置、多个端口号),保证全局行为一致。
    网络服务模块可以直接通过单例获取配置,如绑定地址、端口。

日志系统可以读取框架统一配置,决定是否输出调试信息。

RPC 调用封装中可以依赖框架统一的超时时间、协议设置等。

框架类.h-code

回顾单例 设计模式!!

c++ 复制代码
#pragma once

// mprpc框架的基础类, 负责框架的初始化工作
class MprpcApplication {
    public:
        static void Init(int argc, char **argv);
        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,可能是 UserServiceFriendServiceOrderService 等等。
  • 所以框架必须设计成能够接收任意 service 的注册,而不是依赖于具体类。
    通过 查看 pb.h

UserSeviceRpc 源于

c++ 复制代码
class UserServiceRpc : public ::PROTOBUF_NAMESPACE_ID::Service{}
c++ 复制代码
Service--->google::protobuf::Service

设计理念总结

  • 从"怎么用"出发设计框架类结构
  • 强调解耦易用性扩展性
  • 框架代码避免依赖业务代码,支持任意服务注册

服务发布者类.h-code

c++ 复制代码
#pragma one
#include "google/protobuf/service.h" // 这个头文件里有 RpcController, Closure, Service

// 框架提供的专门服务发布 rpc 服务的类对象
class RpcProvider {
    public:

    // 这里是框架提供给外部使用的, 可以发布rpc方法的函数接口
        void NotifyService(google::protobuf::Service *server); // 注册服务  
        // 传参那边是 new UserService() 这个对象, 这里是个指针, 传递给框架, 不能用引用

        // 启动rpc服务发布 节点, 开始提供rpc远程网络调用服务
        void Run(); 
    };
    
相关推荐
wadesir3 小时前
当前位置:首页 > 服务器技术 > 正文Linux网络HSRP协议(实现路由器热备份与高可用性的实用指南)
linux·服务器·网络
泡沫·3 小时前
4.iSCSI 服务器
运维·服务器·数据库
胡八一3 小时前
解决PHP未检测到您服务器环境的sqlite3数据库扩展报错
服务器·数据库·php
不解不惑3 小时前
OpenAI whisper 语音识别服务器搭建
服务器·whisper·语音识别
gaize12133 小时前
适合业务规模较大的场景的服务器测评
服务器
悠悠121384 小时前
告别Zabbix?我用Netdata只花10分钟就搞定了50台服务器的秒级监控(保姆级实战)
运维·服务器·zabbix
天庭鸡腿哥4 小时前
大小只有4K的软件,可让系统瞬间丝滑!
运维·服务器·windows·microsoft·everything
虚伪的空想家4 小时前
华为昇腾Atlas 800 A2物理服务器开启VT-d模式
运维·服务器·ubuntu·kvm·vt-d·直通
学渣676564 小时前
服务器端口映射
运维·服务器
红袜子i4 小时前
【问题】实验室服务器恢复记录,一个主板挂两张显卡,
运维·服务器