【学习笔记】gRPC 协议详细介绍
1. gRPC 概述
gRPC(Google Remote Procedure Call)是由 Google 开发的一个高性能、开源的通用 RPC 框架。它基于 HTTP/2 协议传输,使用 Protocol Buffers 作为接口描述语言(IDL)和消息序列化格式。
核心特性
- 高性能:基于 HTTP/2,支持多路复用、流控制、头部压缩
- 跨语言:支持多种编程语言(C++, Java, Python, Go, C#, Node.js 等)
- 强类型:使用 Protocol Buffers 定义接口,编译时类型检查
- 流式支持:支持客户端流、服务端流、双向流
- 可扩展:支持拦截器、负载均衡、认证等扩展功能
2. 通信模式
2.1 一元 RPC(Unary RPC)
2.1.1 基本概念
- 定义:客户端发送单个请求,服务端返回单个响应
- 特点:最简单的 RPC 模式,类似传统的函数调用
- 生命周期:请求-处理-响应,一次性完成
2.1.2 工作流程
2.1.3 适用场景
- 查询操作:获取用户信息、查询数据库
- 简单计算:数学运算、数据转换
- 状态获取:系统状态、配置信息
- CRUD 操作:创建、读取、更新、删除
2.1.4 技术特点
- 同步调用:客户端阻塞等待响应
- 简单直接:开发和调试容易
- 资源占用少:单次请求,连接利用率高
- 错误处理简单:状态码明确
2.2 服务端流式 RPC(Server Streaming RPC)
-
2.2.1 基本概念
- 定义:客户端发送单个请求,服务端返回多个响应(数据流)
- 特点:服务端可以持续推送数据给客户端
- 生命周期:请求-流式响应-结束
2.2.2 工作流程
2.2.3 适用场景
- 文件下载:大文件分块传输
- 实时推送:股票价格、新闻推送、系统监控
- 批量数据传输:数据库查询结果、日志数据
- 订阅服务:消息订阅、事件通知
2.2.4 技术特点
- 内存效率:逐块处理,不需要一次性加载所有数据
- 实时性:可以即时推送数据
- 可取消:客户端可以随时取消接收
- 背压控制:流控制机制防止数据堆积
2.2.5 代码举例
// 服务端实现 Status GetServerNotifications(ServerContext* context, const Empty* request, ServerWriter<Notification>* writer) override { for (int i = 1; i <= 100; i++) { // 检查客户端是否取消 if (context->IsCancelled()) { return Status::CANCELLED; } Notification notification; notification.set_content("消息 " + std::to_string(i)); // 写入流 if (!writer->Write(notification)) { break; // 客户端断开连接 } std::this_thread::sleep_for(std::chrono::seconds(1)); } return Status::OK; } // 客户端接收 std::unique_ptr<ClientReader<Notification>> reader = stub_->GetServerNotifications(&context, request); Notification notification; while (reader->Read(¬ification)) { // 处理每个通知 ProcessNotification(notification); }
2.3 客户端流式 RPC(Client Streaming RPC)
2.3.1 基本概念
- 定义:客户端发送多个请求(数据流),服务端返回单个响应
- 特点:客户端可以持续发送数据给服务端
- 生命周期:流式请求-处理-单个响应
2.3.2 工作流程
2.3.3 适用场景
- 文件上传:大文件分块上传
- 批量操作:批量插入数据库、批量处理
- 实时数据收集:传感器数据、日志收集
- 流式计算:实时统计、聚合计算
2.3.4 技术特点
- 内存友好:客户端不需要一次性准备所有数据
- 网络效率:复用连接,减少握手开销
- 容错性:可以处理部分失败的情况
- 流控制:防止客户端发送过快
2.3.5 代码举例
// 服务端实现
Status UploadFile(ServerContext* context,
ServerReader<FileChunk>* reader,
UploadResult* result) override {
FileChunk chunk;
std::string file_content;
int chunk_count = 0;
// 读取所有数据块
while (reader->Read(&chunk)) {
file_content += chunk.data();
chunk_count++;
}
// 处理完整文件
bool success = SaveFile(file_content);
result->set_success(success);
result->set_total_chunks(chunk_count);
result->set_file_size(file_content.size());
return Status::OK;
}
// 客户端发送
std::unique_ptr<ClientWriter<FileChunk>> writer =
stub_->UploadFile(&context, &result);
// 分块发送文件
for (const auto& chunk : file_chunks) {
FileChunk file_chunk;
file_chunk.set_data(chunk);
if (!writer->Write(file_chunk)) {
break; // 发送失败
}
}
writer->WritesDone(); // 标记发送完成
Status status = writer->Finish(); // 获取最终结果
2.4 双向流式 RPC(Bidirectional Streaming RPC)
2.4.1 基本概念
- 定义:客户端和服务端都可以发送多个请求/响应(双向数据流)
- 特点:全双工通信,最灵活的通信模式
- 生命周期:建立连接-双向流式通信-关闭连接
2.4.2 工作流程
2.4.3 适用场景
- 聊天应用:即时消息、群聊
- 实时协作:在线文档编辑、代码协作
- 在线游戏:实时对战、状态同步
- 实时监控:双向监控和控制
- 交易系统:实时报价和下单
2.4.4 技术特点
- 全双工:收发完全独立,真正的双向通信
- 实时性:最低延迟的通信方式
- 复杂性:需要处理并发的读写操作
- 状态管理:需要维护连接状态
2.4.5 代码举例
// 服务端实现
Status Chat(ServerContext* context,
ServerReaderWriter<ChatMessage, ChatMessage>* stream) override {
std::thread writer_thread([stream]() {
// 服务端主动推送线程
while (true) {
ChatMessage system_msg;
system_msg.set_content("系统消息");
if (!stream->Write(system_msg)) {
break;
}
std::this_thread::sleep_for(std::chrono::seconds(10));
}
});
// 主线程处理客户端消息
ChatMessage message;
while (stream->Read(&message)) {
// 处理客户端消息
ProcessMessage(message);
// 立即回复
ChatMessage reply;
reply.set_content("收到: " + message.content());
if (!stream->Write(reply)) {
break;
}
}
writer_thread.join();
return Status::OK;
}
// 客户端实现
std::unique_ptr<ClientReaderWriter<ChatMessage, ChatMessage>> stream =
stub_->Chat(&context);
// 接收线程
std::thread reader_thread([&stream]() {
ChatMessage message;
while (stream->Read(&message)) {
std::cout << "收到: " << message.content() << std::endl;
}
});
// 发送线程
std::string input;
while (std::getline(std::cin, input)) {
ChatMessage message;
message.set_content(input);
if (!stream->Write(message)) {
break;
}
}
stream->WritesDone();
reader_thread.join();
特性 | 一元RPC | 服务端流式 | 客户端流式 | 双向流式 |
---|---|---|---|---|
请求数量 | 1 | 1 | 多个 | 多个 |
响应数量 | 1 | 多个 | 1 | 多个 |
复杂度 | 低 | 中 | 中 | 高 |
内存使用 | 少 | 中 | 中 | 多 |
实时性 | 低 | 高 | 中 | 最高 |
连接时间 | 短 | 中 | 中 | 长 |
适用场景 | 查询操作 | 数据推送 | 数据上传 | 实时交互 |
3. 实践
3.1 grpc/calculator.proto
// 声明使用 Protocol Buffers 版本 3 语法
// proto3 是最新版本,语法更简洁
syntax = "proto3";
// 定义包名为 calculator
// 避免不同项目间的命名冲突
// 生成的 C++ 代码会在 calculator 命名空间中
package calculator;
// 定义名为 Calculator 的服务
// Add:计算两个数的和 输入:AddRequest 消息 输出:AddReply 消息
// GetSystemInfo:获取系统信息 输入:Empty 消息(无参数) 输出:SystemInfo 消息
service Calculator
{
rpc Add (AddRequest) returns (AddReply) {}
rpc GetSystemInfo (Empty) returns (SystemInfo) {}
}
// 定义名为 AddRequest 的消息
message AddRequest
{
int32 a = 1;
int32 b = 2;
}
// 定义名为 AddReply 的消息
message AddReply
{
int32 result = 1;
string message = 2;
}
message Empty {}
// SystemInfo:系统信息消息
message SystemInfo
{
string hostname = 1; // 主机名
string ip_address = 2; // IP地址
int64 timestamp = 3; // 时间戳(64位整数)
}
3.2 grpc/server.cpp
#include <iostream>
#include <memory>
#include <string>
#include <grpcpp/grpcpp.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include "calculator.grpc.pb.h"
using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;
using calculator::Calculator;
using calculator::AddRequest;
using calculator::AddReply;
using calculator::Empty;
using calculator::SystemInfo;
// Calculator::Service 是 protoc 生成的抽象基类
// final 表示此类不能被继承
// 必须重写所有在 .proto 中定义的 RPC 方法
class CalculatorServiceImpl final : public Calculator::Service
{
public:
// ServerContext*:gRPC 上下文,包含请求元数据
// const AddRequest*:客户端发送的请求数据(只读)
// AddReply*:服务端要返回的响应数据(可写)
// override:明确表示重写父类虚函数
Status Add(ServerContext* context, const AddRequest* request, AddReply* reply) override
{
int result = request->a() + request->b();
reply->set_result(result);
reply->set_message("计算成功!");
std::cout << "收到计算请求: " << request->a() << " + " << request->b()
<< " = " << result << std::endl;
return Status::OK;
}
Status GetSystemInfo(ServerContext* context, const Empty* request, SystemInfo* reply) override
{
char hostname[256];
gethostname(hostname, sizeof(hostname));
reply->set_hostname(hostname);
reply->set_ip_address("10.241.10.118");
reply->set_timestamp(time(nullptr));
std::cout << "收到系统信息查询请求" << std::endl;
return Status::OK;
}
};
// ServerBuilder:用于构建和配置 gRPC 服务器
// AddListeningPort():设置监听地址和端口
// InsecureServerCredentials():不使用 TLS 加密(仅测试用)
// RegisterService():注册服务实现
// BuildAndStart():构建并启动服务器
// server->Wait():阻塞等待,直到服务器关闭
void RunServer()
{
std::string server_address("0.0.0.0:50051");
CalculatorServiceImpl service;
ServerBuilder builder;
builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
builder.RegisterService(&service);
std::unique_ptr<Server> server(builder.BuildAndStart());
std::cout << "gRPC 服务器启动,监听地址: " << server_address << std::endl;
std::cout << "虚拟机IP: 10.241.10.118" << std::endl;
std::cout << "等待客户端连接..." << std::endl;
server->Wait()
}
int main(int argc, char** argv)
{
RunServer();
return 0;
}
// class MasterServiceImpl final : public MasterService::Service {
// public:
// Status ReportStatus(ServerContext* context, const StatusRequest* request, StatusReply* reply) override {
// // 处理子节点上报的信息
// return Status::OK;
// }
// Status SubscribeNotification(ServerContext* context, const SubscribeRequest* request, ServerWriter<Notification>* writer) override {
// // 持续推送通知给子节点
// while (true) {
// Notification notify;
// // 设置通知内容
// writer->Write(notify);
// sleep(1);
// }
// return Status::OK;
// }
// };
3.3 grpc/client.cpp
#include <iostream>
#include <memory>
#include <string>
#include <grpcpp/grpcpp.h>
#include "calculator.grpc.pb.h"
using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
using calculator::Calculator;
using calculator::AddRequest;
using calculator::AddReply;
using calculator::Empty;
using calculator::SystemInfo;
class CalculatorClient
{
public:
// Channel:gRPC 通道,代表与服务器的连接
// Calculator::NewStub():创建服务存根(Stub)
// 存根提供了调用远程方法的接口
CalculatorClient(std::shared_ptr<Channel> channel)
: stub_(Calculator::NewStub(channel)) {}
bool Add(int a, int b)
{
AddRequest request;
request.set_a(a);
request.set_b(b);
// reply:用于接收服务器响应
AddReply reply;
// context:客户端上下文,可设置超时、元数据等
ClientContext context;
Status status = stub_->Add(&context, request, &reply);
if (status.ok())
{
std::cout << "计算结果: " << a << " + " << b << " = " << reply.result() << std::endl;
std::cout << "服务器消息: " << reply.message() << std::endl;
return true;
}
else
{
std::cout << "RPC 调用失败: " << status.error_code() << ": " << status.error_message() << std::endl;
return false;
}
}
bool GetSystemInfo()
{
Empty request;
SystemInfo reply;
ClientContext context;
Status status = stub_->GetSystemInfo(&context, request, &reply);
if (status.ok())
{
std::cout << "=== 系统信息 ===" << std::endl;
std::cout << "主机名: " << reply.hostname() << std::endl;
std::cout << "IP地址: " << reply.ip_address() << std::endl;
std::cout << "时间戳: " << reply.timestamp() << std::endl;
return true;
}
else
{
std::cout << "获取系统信息失败: " << status.error_code() << ": " << status.error_message() << std::endl;
return false;
}
}
private:
std::unique_ptr<Calculator::Stub> stub_;
};
int main(int argc, char** argv)
{
std::string server_address("192.168.0.10:50051");
// 创建与服务器的连接
CalculatorClient client(grpc::CreateChannel(server_address, grpc::InsecureChannelCredentials()));
std::cout << "连接到 gRPC 服务器: " << server_address << std::endl;
// 测试计算服务
std::cout << "\n=== 测试计算服务 ===" << std::endl;
client.Add(10, 20);
client.Add(100, 200);
client.Add(-5, 15);
// 测试系统信息服务
std::cout << "\n=== 测试系统信息服务 ===" << std::endl;
client.GetSystemInfo();
return 0;
}
// void ReportStatusToMaster() {
// StatusRequest req;
// StatusReply reply;
// ClientContext ctx;
// master_stub->ReportStatus(&ctx, req, &reply);
// }
// void SubscribeNotificationFromMaster() {
// SubscribeRequest req;
// ClientContext ctx;
// std::unique_ptr<ClientReader<Notification>> reader = master_stub->SubscribeNotification(&ctx, req);
// Notification notify;
// while (reader->Read(¬ify)) {
// // 处理主节点下发的通知
// }
// }
没有相关工具安装一下就可以了。
# 手动生成文件
protoc --cpp_out=. calculator.proto
protoc --grpc_out=. --plugin=protoc-gen-grpc=/usr/bin/grpc_cpp_plugin calculator.proto
# 手动编译
g++ -std=c++14 -O2 -Wall -o server server.cpp calculator.pb.cc calculator.grpc.pb.cc -lgrpc++ -lgrpc -lprotobuf -lpthread
g++ -std=c++14 -O2 -Wall -o client client.cpp calculator.pb.cc calculator.grpc.pb.cc -lgrpc++ -lgrpc -lprotobuf -lpthread
./server
gRPC 服务器启动,监听地址: 0.0.0.0:50051
虚拟机IP: 192.168.0.10
等待客户端连接...
收到计算请求: 10 + 20 = 30
收到计算请求: 100 + 200 = 300
收到计算请求: -5 + 15 = 10
收到系统信息查询请求
./client
连接到 gRPC 服务器: 192.168.0.10:50051
=== 测试计算服务 ===
计算结果: 10 + 20 = 30
服务器消息: 计算成功!
计算结果: 100 + 200 = 300
服务器消息: 计算成功!
计算结果: -5 + 15 = 10
服务器消息: 计算成功!
=== 测试系统信息服务 ===
=== 系统信息 ===
主机名: virtual-machine
IP地址: 192.168.0.10
时间戳: 1756976787
4. 总结
1、服务端实现服务接口,启动监听
2、客户端创建存根,发起 RPC 调用
3、gRPC 负责网络通信、序列化/反序列化
一般都是简单的通信,一元RPC用的比较多吧。分布式系统中进程间通信也会用到grpc,结合protobuf确实比较方便快捷。
优点总结:
1、数据传输效率:protobuf 比 JSON 小 3-10 倍,解析速度快 5-20 倍,压缩效果更好。
2、跨网络通信:不限于本机进程,支持分布式部署。
3、跨语言支持:不同语言的进程可以无缝通信。
4、类型安全:编译时类型检查,避免内存错误。
5、安全性:支持 TLS 加密和认证。
6、自动序列化:Protocol Buffers 自动处理数据编解码
7、实时应用:包含四种通信方式,通信方式样式多样,适合多种场景。