文章目录
- protobuf使用(三)
-
- 复习
- [Protobuf 与 RPC 的关系](#Protobuf 与 RPC 的关系)
- [为什么要用 ProtoBuf](#为什么要用 ProtoBuf)
- service定义rpc
- [**必须启用** service 的生成:](#必须启用 service 的生成:)
- protobuf代码结构--**重点**
-
-
- [1. Message 类型:](#1. Message 类型:)
- [2. Service 类型(抽象类):](#2. Service 类型(抽象类):)
- [3. Stub 类型(代理类):](#3. Stub 类型(代理类):)
-
- 理解service类
-
-
- [1. `getDescriptor()` 方法的作用](#1.
getDescriptor()
方法的作用) - [2. 为什么需要 `ServiceDescriptor`](#2. 为什么需要
ServiceDescriptor
) - [3. service 类是如何生成的](#3. service 类是如何生成的)
- [4. protobuf 中的两个核心抽象](#4. protobuf 中的两个核心抽象)
- [5. RPC 方法结构统一](#5. RPC 方法结构统一)
- [1. `getDescriptor()` 方法的作用](#1.
-
- protobuf使用(四)
- 本地服务发布rpc(一)
-
- 项目目标
- 开发方式
- 项目结构说明
- **CMakeLists.txt**
- [业务示例 - UserService](#业务示例 - UserService)
- [RPC 方法定义(Protobuf)](#RPC 方法定义(Protobuf))
- 修改业务代码-加rpc
- 错误-1:
- 错误-2:
- 重写基类的虚函数(部分)
- 各参数理解
- 补充说明:
- 本地服务发布rpc(二)
-
- 背景说明
- 两个rpc类->proto后
- 再次回顾四个参数
- 把本地服务变成远程服务流程-总结
-
-
- [1. **定义 proto 文件**](#1. 定义 proto 文件)
- [2. **从生成的服务类中继承**](#2. 从生成的服务类中继承)
- [3. **业务处理逻辑**](#3. 业务处理逻辑)
- [4. **发送响应数据-run实现**](#4. 发送响应数据-run实现)
-
- callee代码整体
- 阶段大总结-实现流程
- 后续将进行如何发布
- mprpc框架基础类设计
-
- 目标
- 初始化框架
- [创建 服务发布 对象](#创建 服务发布 对象)
- 业务代码中使用框架发布服务
- 为什么这么设计?
- 框架目录结构
- 框架类为什么设计成单例?
-
-
- [1. **只需要初始化一次**](#1. 只需要初始化一次)
- [2. **全局访问方便**](#2. 全局访问方便)
- [3. **确保状态一致**](#3. 确保状态一致)
-
- 框架类.h-code
- [`RpcProvider`:RPC 服务发布者](#
RpcProvider
:RPC 服务发布者) - 设计理念总结
- 服务发布者类.h-code
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 中的 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个参数)
- 如:请求参数、响应参数都封装在对应的
xxxRequest
和xxxResponse
中。
protobuf使用(四)

Protobuf 生成的两个核心类
-
UserServiceRpc 类
- 是 RPC 服务提供者 端使用的类。
- 继承自
::google::protobuf::Service
。 - 包含 RPC 方法(如
Login
、GetFriendList
)的 虚函数声明 ,供服务端进行 具体业务实现。 - 构造函数无参,是服务端主动注册服务对象时使用的。
-
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 通信。
调用流程总结
-
客户端构造一个桩类对象:
UserServiceRpc_Stub stub(new MyRpcChannel());
-
调用 RPC 方法:
stub.Login(controller, &request, &response, nullptr);
-
实际执行:
- 并不会执行业务逻辑。
- 而是调用
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);
各参数理解
::google::protobuf::RpcController* controller
- 作用:用于控制整个 RPC 调用过程,包括处理错误、取消、设置/获取错误信息等。
- 说明:客户端和服务器都可以使用它来反映 RPC 状态(如失败信息)。如果你需要反馈 RPC 错误,比如请求格式不对、认证失败,可以通过它设置错误。
const ::hzhrpc::LoginRequest* request
- 作用 :客户端发来的请求数据,封装成了
LoginRequest
对象。 - 说明:这是 protobuf 自动生成的请求结构体,包含用户登录所需的信息,比如用户名、密码等。服务器通过读取它来处理登录逻辑。
::hzhrpc::LoginResponse* 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
)- 作用:这是客户端与服务端的通信约定,客户端只需按这个约定调用即可。
举例:
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
,可能是UserService
、FriendService
、OrderService
等等。- 所以框架必须设计成能够接收任意 service 的注册,而不是依赖于具体类。
通过 查看 pb.hUserSeviceRpc 源于
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();
};