rpc
远程过程调用
- Remote Procrdure Call ---->底层自动处理网络传输 和数据序列化细节
使用客户端服务器 的模式,将本地数据 传输到服务器 上进行处理并获取结果 ,但整个过程,是封装 起来的,对用户不可见 的,使用过程就类似 于本地的一个函数调用
grpc
安装
sudo apt install libgrpc-dev libgrpc++-dev
//grpc框架中用于对protobuf的特殊生成插件
sudo apt install protobuf-compiler-grpc
RPC 的核心工作流程
-
客户端调用 :客户端代码像调用本地方法一样发起请求(如
userService.getUser(id))。 -
Client Stub(客户端存根/代理):将调用的方法名、参数打包序列化(Marshalling)为二进制或文本字节流。
-
网络传输(Transport):通过 TCP、UDP 或 HTTP/2 将数据发送给服务端。
-
Server Stub(服务端存根/解包):服务端接收数据包,反序列化(Unmarshalling)还原出方法名与参数。
-
服务端执行:根据解析出的方法,调用本地服务逻辑(如查询数据库),获得执行结果。
-
响应返回:结果由 Server Stub 序列化后发回客户端,Client Stub 解包并返回给客户端调用方。
RPC vs RESTful
| 比较维度 | RPC (如 gRPC) | RESTful API |
|---|---|---|
| 设计理念 | 面向动作/过程(getUser(), createOrder()) |
面向资源(GET /users/1, POST /orders) |
| 数据格式 | 主要是二进制(如 Protobuf),也可用 JSON | 主要为 JSON、XML |
| 传输协议 | 通常基于 TCP 或 HTTP/2 | 通常基于 HTTP/1.1 |
| 性能与延迟 | 极高(Payload 极小,序列化与反序列化速度极快) | 中等(HTTP Header 较大,文本解析开销相对高) |
| 适用场景 | 微服务内部 高吞吐、低延迟的 Server-to-Server 通信 | 对外开放接口、Web 前端/移动端与后端通信 |
定义与使用
1**.请求与响应** 的数据结构定义
2.定义rpc相关的调用接口格式
rpc.proto
cpp
syntax="proto3";
package calculate;
//开启rpc接口生成 选项
//option cc_generic_services=true;
message AddRequest
{
int32 num1=1;
int32 num2=2;
}
message AddResponse
{
int32 result=1;
}
service Calculate
{
rpc Add(AddRequest) returns (AddResponse);
}
//protoc -I. --cpp_out=. --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` rpc.proto

编译
bash
protoc -I. --cpp_out=. --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` rpc.proto
这条命令通过 Protocol Buffers 编译器(protoc)将 rpc.proto 文件编译生成 C++ 语言的代码,同时包含Protobuf 数据序列化代码 与 gRPC 服务网络接口代码。
各参数的具体含义如下:
-
protoc:Protocol Buffers 编译器主程序,负责解析.proto语法。 -
-I
.(即--proto_path=.):指定查找.proto依赖文件的搜索路径。.表示在当前工作目录下搜索。 -
--cpp_out=.:指示生成标准 Protobuf 数据结构的 C++ 代码,并将文件输出到当前目录(.)。 -
--grpc_out=.:指示生成 gRPC 服务框架相关的 C++ 代码,并将文件输出到当前目录(.)。 -
--plugin=protoc-gen-grpc=\which grpc_cpp_plugin**:为 `protoc` 指定生成 gRPC 代码所需的 C++ 插件路径。which grpc_cpp_plugin```会在系统环境变量中自动获取``grpc_cpp_plugin` 可执行文件的绝对路径。- 不加这个参数,
protoc就只能生成数据结构代码 ,无法生成 gRPC 的客户端与服务端网络调用代码 (.grpc.pb.h/.grpc.pb.cc)
- 不加这个参数,
-
rpc.proto:作为输入的源定义文件。
编译生成的产物说明
| 选项 | 生成的文件 | 主要作用 |
|---|---|---|
--cpp_out=. |
rpc.pb.h rpc.pb.cc |
定义消息体类(如 AddRequest、AddResponse),提供数据设置、获取以及二进制序列化/反序列化方法。 |
--grpc_out=. |
rpc.grpc.pb.h rpc.grpc.pb.cc |
定义 RPC 服务基类(calculate::Calculate::Service)以及客户端调用的存根类(Stub),提供网络传输与方法调度能力。 |
server.cc
cpp
#include <iostream>
#include <memory>
#include <string>
#include <grpcpp/grpcpp.h>
#include "rpc.pb.h"
#include "rpc.grpc.pb.h" // 包含 gRPC 生成的服务头文件
// 继承 gRPC 生成的 Service 基类
class CalculateImpl final : public calculate::Calculate::Service
{
public:
// 使用 gRPC 标准的重写签名
grpc::Status Add(grpc::ServerContext* context,
const ::calculate::AddRequest* request,
::calculate::AddResponse* response) override
{
// 实现真正的数据处理逻辑
int result = request->num1() + request->num2();
response->set_result(result);
// 返回 OK 状态告诉 gRPC 框架处理成功
return grpc::Status::OK;
}
};
int main()
{
std::string server_address("0.0.0.0:9000");
// 实例化计算服务对象(保持生命周期贯穿服务器运行期)
CalculateImpl service;
// 实例化 gRPC 服务器构建器
grpc::ServerBuilder builder;
// 设置监听地址与非加密通信凭据
builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
// 注册计算服务
builder.RegisterService(&service);
// 构建并启动 gRPC 服务器
std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
std::cout << "Server listening on " << server_address << std::endl;
// 阻塞主线程,保持服务器处于监听并响应状态
server->Wait();
return 0;
}
client.cc
cpp
#include <iostream>
#include <memory>
#include <string>
#include <grpcpp/grpcpp.h>
#include "rpc.pb.h"
#include "rpc.grpc.pb.h"
class CalculateClient {
public:
// 1. 构造函数:基于 Channel 创建服务的 Stub 实例
explicit CalculateClient(std::shared_ptr<grpc::Channel> channel)
: stub_(calculate::Calculate::NewStub(channel)) {}
// 2. 业务封装方法
int Add(int num1, int num2) {
// 组装请求消息
calculate::AddRequest request;
request.set_num1(num1);
request.set_num2(num2);
// 准备响应结构体与 Context
calculate::AddResponse response;
grpc::ClientContext context; // 可在此设置超时时间等
// 3. 发起同步 RPC 调用(阻塞等待服务端响应)
grpc::Status status = stub_->Add(&context, request, &response);
// 4. 检查调用结果
if (status.ok()) {
return response.result();
} else {
std::cerr << "RPC 调用失败 (" << status.error_code() << "): "
<< status.error_message() << std::endl;
return -1;
}
}
private:
// 持有 gRPC 生成的 Stub 指针
std::unique_ptr<calculate::Calculate::Stub> stub_;
};
int main() {
// 设置服务端监听地址
std::string target_str("127.0.0.1:9000");
// 创建非加密通信的 Channel 管道
auto channel = grpc::CreateChannel(target_str, grpc::InsecureChannelCredentials());
// 实例化客户端对象
CalculateClient client(channel);
// 发起 RPC 计算请求
int a = 15;
int b = 27;
int result = client.Add(a, b);
std::cout << "客户端调用成功: " << a << " + " << b << " = " << result << std::endl;
return 0;
}
makefile
cpp
.PHONY: all clean proto
# 1. 显式指定生成的 protobuf/grpc 源文件
PB_SRC = rpc.pb.cc rpc.grpc.pb.cc
all: proto client server
# 2. 自动由 rpc.proto 生成 c++ 源文件
proto: rpc.proto
protoc -I. --cpp_out=. --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` rpc.proto
# 3. 编译客户端(注意:使用 -lgrpc++)
client: $(PB_SRC) client.cc
g++ -std=c++17 $^ -o $@ -lgrpc++ -lprotobuf
# 4. 编译服务端
server: $(PB_SRC) server.cc
g++ -std=c++17 $^ -o $@ -lgrpc++ -lprotobuf
clean:
rm -f client server *.pb.cc *.pb.h
lsb_release -a
Linux 命令,查看系统发行版本信息。
拆解
-
lsb_release:工具命令,遵循 LSB (Linux Standard Base) 标准,用来输出系统版本 -
-a:all,输出全部信息
执行示例
bash
lsb_release -a
输出类似:
bash
LSB Version: ::core-4.1-amd64:core-4.1-noarch
Distributor ID: CentOS
Description: CentOS Linux release 7.9.2009 (Core)
Release: 7.9.2009
Codename: Core
字段含义:
-
Distributor ID:发行商(Ubuntu/CentOS/Debian) -
Description:系统完整描述 -
Release:系统版本号 -
Codename:版本代号
⚠️注意
-
最小化安装可能没有这个命令
(提示 command not found)
-
CentOS/RHEL:
yum install redhat-lsb-core -
Ubuntu/Debian:
apt install lsb-release
-
-
替代查看版本命令(不用装包)
cat /etc/os-release
cat /etc/*release
常用简写
-
lsb_release -d只看系统描述 -
lsb_release -r只看版本号
用途:编译、部署软件时确认是什么 Linux 发行版、版本。
自定义RPC框架
RPC请求

bash
rpc_message.proto
syntax="proto3"
package rpc_message;
//枚举消息类型
enum MessageType
{
ERROR=0;
REQUEST=1;
RESPONSE=2;
}
//错误码
enum ErorrCode
{
NO_ERROR=0;
WRONG_PROTO=1;//协议类型错误
NO_SERVICE=2;
NO_METHOD=3;
INVALID_REQUEST=4;//无效请求
INVALID_RESPONSE=5;//无效响应
TIMEOUT=6;
}
//通信消息结构
message RpcMessage
{
fixed32 id=1;
MessageType type=2;//消息类型
string service=3;//服务名称
string method=4;//方法名称
//bytes request=5;
//bytes response=6;
// oneof联合体:request 和 response 互斥,最多只有一个生效
oneof payload {
bytes request =5;
bytes response =6;
}
ErorrCode error=7;
}
RPC服务端

RPC客户端
如果我们要自己实现一个RPC客户端
1.实现一个类,继承于google::protobuf::RpcChannel类
-
重写CallMethod函数:实现通过我们自己的connection发送请求数据****(按照自定制协议(LVC)组装我们的RPC请求工具)
-
请求发送后,服务器处理完毕会进行响应,我们需要能够知道收到的那个响应,对应 的是哪个请求 ,应该是使用哪个回调函数 进行响应处理
2.在客户端的OnMessage 中,接收响应并解析响应 ,并且根据响应找到对应的响应处理回调函数
- 客户端需要建立一个映射关系:请求ID<-->响应处理回调函数
序列化和协议之间有什么关系?
序列化:目的是将多个独立的数据对象按照指定格式组织到一起,用于传输
网络通信协议 :通信的客户端与服务器 之间的数据格式约定
-
数据的组织格式
-
解决通信中可能存在的问题(TCP粘包问题)
-
TCP粘包:TCP传输层不关注数据边界,因此可能将多条数据当作一条处理
-
解决:LV(Length+value)格式------>数据增加边界
-

cpp
// 设置给网络通信链接的消息处理回调函数
void RpcChannel::onMessage(net::TcpConnectionPtr conn,
net::Buffer* buf,
net::Timestamp rtime){
// 使用RpcCodec解析收到的数据
_codec.onMessage(conn, buf, rtime);
}
// 提供给RpcCodec的protobuf消息处理回调函数
void RpcChannel::onRpcMessage(net::TcpConnectionPtr conn,
MessagePtr message,
net::Timestamp rtime){
//根据消息类型,决定处理方式:请求还是响应,处理方式是不一样的
if (message->type() == rpc_message::MessageType::REQUEST) {
assert(message->has_service() && message->has_method() &&
message->has_request());
rpc_message::ErrorCode ecode = rpc_message::ErrorCode::NO_ERROR;
//1. 取出消息中的核心要素:服务名称 + 方法名称
std::string service_name = message->service();
std::string method_name = message->method();
if (_services == NULL) {
ecode = rpc_message::ErrorCode::NO_SERVICE;
}else {
//2. 从路由表中根据服务名称,查找服务对象,进而就能得到方法描述对象
auto it = _services->find(service_name);
if (it == _services->end()) {
ecode = rpc_message::ErrorCode::NO_SERVICE;
}else {
google::protobuf::Service* service = it->second;
auto *method = service->GetDescriptor()->FindMethodByName(method_name);
if (method == NULL) {
ecode = rpc_message::ErrorCode::NO_METHOD;
}else {
//3. 构造请求与响应数据对象, 并使用请求对象对收到的消息中的request进行反序列化
auto *request = service->GetRequestPrototype(method).New();
auto *response = service->GetResponsePrototype(method).New();
bool ret = request->ParseFromString(message->request());
if (ret == false) {
ecode = rpc_message::ErrorCode::INVALID_REQUEST;
}else {
//4. 构造处理回调函数:设置给protobuf框架,业务处理完毕后,调用的函数
int64_t rid = message->id();
gp::Closure *done = gp::NewCallback(this, &RpcChannel::doneCallback, response, rid);
//5. 通过服务对象,进行业务处理
service->CallMethod(method, nullptr, request, response, done);
}
}
}
}
if (ecode != rpc_message::ErrorCode::NO_ERROR) {
//发送错误响应
rpc_message::RpcMessage resp;
resp.set_id(message->id());
resp.set_type(rpc_message::MessageType::RESPONSE);
resp.set_error(ecode);
_codec.send(_connection, resp);
}
}else if (message->type() == rpc_message::MessageType::RESPONSE) {
assert(message->has_response() && message->has_error());
// 1. 取出响应中的核心要素:请求ID
int64_t rid = message->id();
// 2. 从outstandings的map中根据请求ID查找未完成处理对象
OutStanding out = {nullptr, nullptr};
{
std::unique_lock<std::mutex> lock(_mtx);
auto it = _outstandins.find(rid);
if (it != _outstandins.end()) {
out = it->second;
_outstandins.erase(it);
}
}
// 3. 未完成对象中,若存在response,则使用response对响应数据进行反序列化
if (out.response) {
out.response->ParseFromString(message->response());
}
// 4. 未完成对象中,若存在done,则调用done->Run()进行响应处理
if (out.done) {
out.done->Run();
}
}else {
LOG_ERROR("消息类型错误");
}
}
校验和算法
| 算法 | 特点 | 常见场景 |
|---|---|---|
| 简单加法 Checksum | 非常简单 | 简单协议 |
| CRC | 检错能力强 | 网络、存储、文件 |
| Internet Checksum | 基于 16 位反码求和 | IP/TCP/UDP |
| Adler-32 | 速度快 | 一些压缩/数据处理场景 |
| Fletcher Checksum | 比简单加法更强 | 数据完整性检查 |