当前位置: 首页 > news >正文

一文读懂GRPC

1.基础知识简介

1.1本地调用和远程过程调用

本地调用

int add(int a,int b)
{return a+b;
}int c=add(2,4);

参数传递:通过栈帧(Stack Frame)直接传递参数值或引用。
执行控制:CPU 直接跳转到函数地址执行,返回时恢复调用点上下文。
返回值传递:结果通过寄存器或栈返回给调用者。
内存共享:调用者与被调用者共享同一进程内存空间,可直接访问相同变量。

关键特性:
同步阻塞:调用线程暂停直到函数返回。
零网络开销:纯内存操作,速度极快(纳秒级)。
错误传播:通过异常直接抛给调用者(如 ZeroDivisionError)。

远程过程调用

现在不好讲代码,用一个通俗的解释就是你一个客户端调用了服务端的程序。

1.2 http各自版本的区别

1. HTTP/1.1(1997 年发布,目前最广泛使用)
特点:
串行请求:一次只能处理一个请求,下一个请求必须等前一个完成后才能开始(类似单车道公路)。
→ 问题:如果一个请求卡住(如大文件下载),后面的请求都要等待(队头阻塞)。
文本协议:请求和响应都是明文文本,容易阅读但体积较大。
简单缓存:通过 Cache-Control、ETag 等头控制缓存。
基本认证:通过 Authorization 头实现简单身份验证。
举例:
打开一个包含 10 张图片的网页,浏览器需要依次请求每个资源,等待时间较长。

2. HTTP/2(2015 年发布,性能大幅提升)
改进点:
二进制分帧:将请求和响应拆分为二进制帧(Frame),并行处理(类似多车道公路)。
→ 解决队头阻塞:多个请求可以同时在一条连接上交错发送,互不影响。
多路复用:单个 TCP 连接可同时处理多个请求和响应(如同时下载图片和加载脚本)。
头部压缩:使用 HPACK 算法压缩请求头,减少数据传输量。
服务器推送:服务器可主动向客户端发送资源(如网页需要的 CSS、JS),无需客户端请求。

1.3 protobuf简介

一、什么是 Protobuf?
Protobuf 是 Google 开发的二进制序列化协议,用于结构化数据的高效存储和传输。它与 HTTP/2 结合后,能进一步提升网络通信的性能。

核心特点

  1. 强类型定义:通过 .proto 文件定义数据结构,类似接口描述语言(IDL)。
  2. 二进制格式:数据被序列化为二进制,比文本格式(XML/JSON)更小、更快。
  3. 高性能:序列化和反序列化速度极快(比 JSON 快 3-10 倍)。
  4. 向后兼容:支持版本演进,旧代码能解析新数据格式。

二、Protobuf vs XML vs JSON(核心区别)

1. 数据格式

类型格式示例(用户信息)
XML文本,标签嵌套<user><id>1</id><name>Alice</name></user>
JSON文本,键值对{"id": 1, "name": "Alice"}
Protobuf二进制(不可读)需通过 .proto 文件解析

关键差异

  • XML/JSON:人类可读,适合调试和简单场景。
  • Protobuf:机器高效处理,适合高性能系统。

2. 定义方式

  • XML/JSON:无需预定义结构,数据自带 schema(如字段名)。
  • Protobuf:必须先定义 .proto 文件:
    message User {int32 id = 1;     // 字段编号(唯一标识,二进制中使用)string name = 2;  // 字段类型和名称
    }
    

3. 性能对比

指标XMLJSONProtobuf
序列化速度慢(解析标签)中等极快(二进制)
数据大小大(冗余标签)中等(文本)小(二进制)
压缩率~50%~60%~80%(最小)

4. 典型场景

场景XMLJSONProtobuf
配置文件是(如 Spring 配置)是(如 package.json)
API 数据交换部分(如 SOAP)主流(REST API)高性能场景(gRPC)
微服务内部通信中等(如 Kafka 消息)是(如 Google 内部)
实时数据传输是(低延迟需求)

三、为什么 Protobuf 适合 HTTP/2?
HTTP/2 本身已具备二进制分帧多路复用特性,而 Protobuf 进一步优化:

  1. 二进制互补

    • HTTP/2 传输二进制帧,Protobuf 生成二进制数据,两者结合避免文本解析开销。
  2. 头部压缩增强

    • HTTP/2 使用 HPACK 压缩请求头,Protobuf 压缩消息体,整体数据量更小。
  3. 多路复用效率

    • Protobuf 序列化后体积小,多个请求/响应可在同一连接上更高效地交错传输。

2 GRPC

2.1 GRPC 基本简介

gRPC 是一个高性能、开源的远程过程调用(RPC,Remote Procedure Call)框架,由 Google 开发并于 2015 年开源,旨在解决分布式系统中服务间通信的效率、可靠性和可扩展性问题。它基于 HTTP/2 协议设计,支持多种编程语言,适用于微服务架构、云原生应用等场景.
请添加图片描述
通信过程

  • 客户端发起请求:两个客户端(Ruby Client、Android - Java Client)都通过各自的 gRPC Stub 向 gRPC Server 发送 Proto Request(基于 Protobuf 格式的请求 ) 。gRPC Stub 是客户端的代理对象,它负责将客户端的调用转换为网络请求发送给服务端。
  • 服务端处理并响应:gRPC Server 接收到请求后进行处理,然后向客户端返回 Proto Response(基于 Protobuf 格式的响应 ) 。例如 Android - Java Client 可能收到多个响应(Proto Response (s) ) ,这对应了 gRPC 支持的不同通信模式(如服务端流式调用 ) 。

数据处理细节

  • 客户端:
    序列化:动态代理将请求数据进行序列化操作,把数据转换为适合传输的格式。
    编码:序列化后的数据再经过编码处理,进一步准备好要在网络上传输的数据。
    传输:编码后的数据通过网络发送给服务端。
  • 服务端:
    解码:接收到网络传输过来的数据后,先进行解码操作。
    反序列化:解码后的数据进行反序列化,还原成服务端可处理的数据结构。
    反射执行:最后通过反射执行相关服务方法来处理请求,并生成响应数据,响应数据再沿着类似的反向流程返回给客户端。

2.2 GRPC四种模式

1. 一元 RPC 模式
就像你去商店买东西,跟店员说 “我要这个东西”(发送请求) ,然后店员拿给你商品(返回响应) ,整个过程就一次请求、一次响应。比如你用手机 app 查余额,app 向服务器问 “我的余额是多少呀”,服务器回个数字给你,这就是一元 RPC 模式,是最常见、简单的模式,就跟平时函数调用差不多。
在这里插入图片描述
2. 服务器端流 RPC 模式
想象你在看在线小说,你跟服务器说 “我要看这本小说”(发送一个请求) ,服务器不是一下子把整本小说给你,而是一章一章慢慢发给你(返回多个响应,形成一个响应流) ,直到发完。像股票行情软件,你打开软件请求看股票信息,服务器就会不断给你推送实时股价变动等消息,这就是服务器端流 RPC 模式,适用于服务器要给你发好多数据的情况。
在这里插入图片描述
3. 客户端流 RPC 模式
好比你给朋友传文件,你不是一下子把文件全丢过去,而是一点一点分段传(客户端发送多个请求 ) ,朋友收到一部分或者全部接收完后,给你回个消息说 “收到啦”(服务器返回一个响应 ) 。比如一些传感器设备,会不断把测量的数据一点一点发给服务器,服务器处理完后给个反馈,这就是客户端流 RPC 模式,适合客户端要连续给服务器发数据的场景。
在这里插入图片描述

4. 双向流 RPC 模式
这就像你和朋友用微信语音通话,你说话的时候朋友能听到(客户端向服务器发消息流 ) ,朋友说话你也能听到(服务器向客户端发消息流 ) ,两边的消息发送和接收都能同时进行,不用等对方说完。像在线多人游戏里玩家之间实时交流、视频会议等场景,就是用的双向流 RPC 模式,两边能随时交流互动。
在这里插入图片描述

3 GRPC实战

关于proto文件编写

// 使用protobuf3
syntax = "proto3";//这段配置表明该 .proto 文件可以生成 多种语言 的代码,包括 Java、Objective-C 和 C++
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";//命名空间
package helloworld;/*service:定义一个 gRPC 服务,类似于接口。
rpc SayHello:定义服务中的一个方法,名为 SayHello。
参数与返回值:使用自定义的 message 类型(HelloRequest 和 HelloReply)
*/
service Greeter {// Sends a greeting rpc是关键字rpc SayHello (HelloRequest) returns (HelloReply) {}
}message HelloRequest {string name = 1;
}// The response message containing the greetings
message HelloReply {string message = 1;
}

message:定义数据结构,类似于类。
字段格式:类型 字段名 = 唯一编号。
string:字段类型(Protobuf 基础类型)。
name/message:字段名。
= 1:字段的唯一编号(1-15 占用 1 字节,16+ 占用 2 字节,建议频繁使用的字段用 1-15)

这个 .proto 文件定义了一个最简单的 gRPC 服务:

服务名:Greeter
方法:SayHello(接收名字,返回问候语)
数据结构:HelloRequest(包含 name 字段)和 HelloReply(包含 message 字段)

如何运行 cmakelist 重要部分

#将 .proto 文件的相对路径转换为绝对路径,确保跨环境兼容性。
#提取 .proto 文件所在的目录,用于 Protobuf 编译器的 import 路径设置。
get_filename_component(hw_proto "../../protos/helloworld.proto" ABSOLUTE)
get_filename_component(hw_proto_path "${hw_proto}" PATH)# 设置生成的源文件和头文件变量
set(hw_proto_srcs "${CMAKE_CURRENT_BINARY_DIR}/helloworld.pb.cc")
set(hw_proto_hdrs "${CMAKE_CURRENT_BINARY_DIR}/helloworld.pb.h")
set(hw_grpc_srcs "${CMAKE_CURRENT_BINARY_DIR}/helloworld.grpc.pb.cc")
set(hw_grpc_hdrs "${CMAKE_CURRENT_BINARY_DIR}/helloworld.grpc.pb.h")# 添加自定义命令,使用 protoc 生成代码
add_custom_command(OUTPUT "${hw_proto_srcs}" "${hw_proto_hdrs}" "${hw_grpc_srcs}" "${hw_grpc_hdrs}"COMMAND ${_PROTOBUF_PROTOC}  # Protobuf 编译器ARGS --grpc_out "${CMAKE_CURRENT_BINARY_DIR}"  # 生成 gRPC C++ 代码--cpp_out "${CMAKE_CURRENT_BINARY_DIR}"   # 生成 Protobuf C++ 代码-I "${hw_proto_path}"                      # .proto 文件路径--plugin=protoc-gen-grpc="${_GRPC_CPP_PLUGIN_EXECUTABLE}"  # gRPC 插件"${hw_proto}"                              # 输入的 .proto 文件DEPENDS "${hw_proto}"                           # 依赖的 .proto 文件
)# 将生成的头文件目录添加到包含路径
include_directories("${CMAKE_CURRENT_BINARY_DIR}")

客户端

#include <iostream>
#include <memory>
#include <string>#include <grpcpp/grpcpp.h>#ifdef BAZEL_BUILD
#include "examples/protos/helloworld.grpc.pb.h"
#else
#include "helloworld.grpc.pb.h"
#endif//初始化阶段
using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
//
using helloworld::Greeter;
using helloworld::HelloReply;
using helloworld::HelloRequest;

grpc::Channel

作用:客户端与服务端的 “连接管道”。
关键:
只需创建一次,可被多个客户端复用(类似数据库连接池)。
负责底层网络管理(如 TCP 连接、负载均衡)。

grpc::ClientContext

  • 作用:单次 RPC 调用的 “配置和环境”。

  • 常用功能:

    • 设置超时(如 context.set_deadline(5秒))。
    • 添加请求头(如认证 token)。
    • 取消请求(如 context.TryCancel())。
    • 注意:每个请求必须创建新的 ClientContext。

grpc::Status

  • 作用:RPC 调用的 “结果报告”。
  • 核心字段:
    • status.ok():调用是否成功。
    • status.error_code():错误码(如 NOT_FOUND、INTERNAL)。
    • status.error_message():错误详情。
lass GreeterClient {public:GreeterClient(std::shared_ptr<Channel> channel): stub_(Greeter::NewStub(channel)) {}// Assembles the client's payload, sends it and presents the response back// from the server.std::string SayHello(const std::string& user) {// Data we are sending to the server.HelloRequest request;request.set_name(user);// Container for the data we expect from the server.HelloReply reply;// Context for the client. It could be used to convey extra information to// the server and/or tweak certain RPC behaviors.ClientContext context;// The actual RPC.Status status = stub_->SayHello(&context, request, &reply);// Act upon its status.if (status.ok()) {return reply.message();} else {std::cout << status.error_code() << ": " << status.error_message()<< std::endl;return "RPC failed";}}private:std::unique_ptr<Greeter::Stub> stub_;
};

存根(Stub):
是 gRPC 生成的代理对象,封装了所有 RPC 方法(如 SayHello),负责将本地调用转换为网络请求

int main(int argc, char** argv) {// Instantiate the client. It requires a channel, out of which the actual RPCs// are created. This channel models a connection to an endpoint specified by// the argument "--target=" which is the only expected argument.// We indicate that the channel isn't authenticated (use of// InsecureChannelCredentials()).std::string target_str;std::string arg_str("--target");if (argc > 1) {std::string arg_val = argv[1];size_t start_pos = arg_val.find(arg_str);if (start_pos != std::string::npos) {start_pos += arg_str.size();if (arg_val[start_pos] == '=') {target_str = arg_val.substr(start_pos + 1);} else {std::cout << "The only correct argument syntax is --target="<< std::endl;return 0;}} else {std::cout << "The only acceptable argument is --target=" << std::endl;return 0;}} else {target_str = "localhost:50051";}GreeterClient greeter(grpc::CreateChannel(target_str, grpc::InsecureChannelCredentials()));std::string user("world");std::string reply = greeter.SayHello(user);std::cout << "Greeter received: " << reply << std::endl;return 0;
}

分为两部分
第一部分:
解析命令行参数,提取服务端地址(如 --target=192.168.1.1:50051)。
关键点:

若未提供参数,默认连接 localhost:50051。
严格校验参数格式,必须为 --target=地址。

第二部分

GreeterClient greeter(grpc::CreateChannel(target_str, grpc::InsecureChannelCredentials()));/*grpc::CreateChannel:创建与服务端的连接通道。
InsecureChannelCredentials:使用非加密连接(开发环境常用)。
GreeterClient:实例化客户端,传入通道。*/

服务端

// Logic and data behind the server's behavior.
class GreeterServiceImpl final : public Greeter::Service {Status SayHello(ServerContext* context, const HelloRequest* request,HelloReply* reply) override {std::string prefix("Hello ");reply->set_message(prefix + request->name());return Status::OK;}
};//处理响应 返回 hellovoid RunServer() {std::string server_address("0.0.0.0:50051");GreeterServiceImpl service;grpc::EnableDefaultHealthCheckService(true);grpc::reflection::InitProtoReflectionServerBuilderPlugin();ServerBuilder builder;// Listen on the given address without any authentication mechanism.builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());// Register "service" as the instance through which we'll communicate with// clients. In this case it corresponds to an *synchronous* service.builder.RegisterService(&service);// Finally assemble the server.std::unique_ptr<Server> server(builder.BuildAndStart());std::cout << "Server listening on " << server_address << std::endl;// Wait for the server to shutdown. Note that some other thread must be// responsible for shutting down the server for this call to ever return.server->Wait();
}int main(int argc, char** argv) {RunServer();return 0;
}
std::string server_address("0.0.0.0:50051");
ServerBuilder builder;
builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());

相当于建立socket

// 对应注册请求处理函数
GreeterServiceImpl service;
builder.RegisterService(&service);

注册回调函数

// 对应 accept() 循环
std::unique_ptr<Server> server(builder.BuildAndStart());
server->Wait(); // 阻塞等待,直到服务器关闭

启动epoll-wait循环

相关文章:

  • STM32之温湿度传感器(DHT11)
  • python 实现 web 请求与相应
  • NIFI的处理器:RouteOnContent 1.28.1
  • 车载软件架构 --- FLASH bootloader 设计要点
  • Accelerate 2025北亚巡展正式启航!AI智御全球·引领安全新时代
  • Nginx核心功能
  • 【朝花夕拾】S32K144 backdoor key解锁后劳德巴赫或者JLINK更新app
  • GraphRAG使用
  • Java集合再探
  • 3452. 好数字之和
  • Java 模块化系统(JPMS)
  • 4.2.4 Thymeleaf内置对象
  • 无人机避障——深蓝学院浙大栅格地图以及ESDF地图内容
  • Mybatis的基本结构和说明
  • 支持PAM特权账号管理和人脸识别,JumpServer开源堡垒机v4.10 LTS版本发布
  • 选择排序 Python实现
  • 深度学习之-目标检测算法汇总(超全面)
  • Java 系统属性深度解析:从 API 校验到实战应用
  • 右键长按超过 200ms, 高亮选中的typora内容, win+a换颜色
  • 文件IO操作、目录操作
  • 网站数据库清空/网站推广怎么做有效果
  • 石家庄做外贸的网站推广/北京网站优化方式
  • 六安网站定制/郑州网站制作公司
  • 云南城乡建设厅网站/免费发布信息的网站平台
  • 免费空间建网站/站长统计推荐
  • 企业网站源代码下载/搜索引擎网站排名