【学习笔记】gRPC 协议详细介绍

【学习笔记】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(&notification)) {
        // 处理每个通知
        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服务端流式客户端流式双向流式
请求数量11多个多个
响应数量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(&notify)) {
//         // 处理主节点下发的通知
//     }
// }

没有相关工具安装一下就可以了。

# 手动生成文件
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、实时应用:包含四种通信方式,通信方式样式多样,适合多种场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值