gRPC通信流程学习
Welcome to 9ilk's Code World

(๑•́ ₃ •̀๑) 个人主页: 9ilk
本篇博客主要是对grpc简单的学习总结,了解客户端和服务端使用grpc通信的简单流程。
什么是gRPC
RPC(Remote Procedure Call):远程过程调用。是一种像调用本地函数一样调用远程服务器上的函数的通信机制,屏蔽了网络通信的复杂性,让开发者专注于业务逻辑。
gRPC(gRPC Remote Procedure Calls)是 Google 开源的高性能、跨语言的 RPC 框架,基于 HTTP/2 协议和 Protocol Buffers 序列化格式,支持双向流、多路复用、头部压缩等特性,适用于微服务、移动应用、后端服务等场景。
核心特性:
- 协议缓冲区 (Protocol Buffers): 使用 protobuf 作为接口定义语言和消息格式
- HTTP/2 传输: 基于 HTTP/2,支持多路复用、头部压缩和双向流
- 多语言支持: 支持 C++, Java, Python, Go, Node.js, C# 等多种语言
服务类型
1. 简单 RPC: 一问一答模式
2. 服务器流 RPC: 客户端发送请求,服务器返回流
3. 客户端流 RPC: 客户端发送流,服务器返回响应
4. 双向流 RPC: 客户端和服务器可以双向发送流
gRPC广泛应用于微服务架构,特别适合高并发、低延迟的服务间通信场景。
优点
- 高性能: 二进制传输,比 JSON 更高效
- 代码生成: 根据 .proto 文件自动生成客户端和服务端代码
- 强类型: 编译时类型检查
- 跨语言: 不同语言服务间无缝通信
典型架构:
环境准备
环境:Ubuntu22.04
使用包管理器直接安装预编译的 gRPC、Protobuf 与 protoc/grpc_cpp_plugin(插件):
sudo apt update
sudo apt install -y protobuf-compiler protobuf-compiler-grpc libprotobuf-dev \libgrpc++-dev grpc-proto
项目目录结构:
├── client
│ └── client.cc
├── generated
│ ├── hello.grpc.pb.cc
│ ├── hello.grpc.pb.h
│ ├── hello.pb.cc
│ └── hello.pb.h
├── Makefile
├── proto
│ └── hello.proto
└── server└── server.cc
编写协议(.proto)
gRPC 以 IDL(接口描述) 为中心。先定义消息与服务接口,再为 C++ 生成强类型代码,确保跨语言一致与类型安全。.proto文件好比双方通信协议,定义好服务,编译器会生成对应的类。
需要注意的是,和普通的.proto文件不同的是,普通Proto只关注数据序列化,只描述数据结构,生成而gRPC的.proto还要额外加service块声明服务,里面用rpc关键字声明远程方法,让框架知道哪些RPC方法可用。下面我们展示service块普通的语法格式(一元调用):
syntax = "proto3";
package hello;// 请求/响应消息
message HelloRequest {string name = 1;
}message HelloReply {string message = 1;
}// 服务与 RPC
service Greeter {rpc SayHello (HelloRequest) returns (HelloReply);
}
对于这段service块,被 protoc 解析后生成:
- Greeter::Service(服务端要继承的抽象基类,里面有 virtual Status SayHello(...))
- Greeter::Stub(客户端持有的“远端代理”,有 Status SayHello(...) 可直接调用)
然后使用protoc生成对应的C++消息类和服务桩:
protoc --cpp_out=generated --grpc_out=generated --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` helloworld.proto
-
--cpp_out=.:生成helloworld.pb.h和helloworld.pb.cc(消息类,如HelloRequest)。 -
--grpc_out=.:生成helloworld.grpc.pb.h/cc(服务端基类、客户端 Stub ,如Greeter::Service)。 -
--plugin=...:指定gRPC C++插件 -
--grpc_out+--grpc_cpp_plugin生成 服务桩(服务端基类、客户端 Stub)。
服务端代码编写
在服务端我们主要做这几件事:
1. 继承由protoc生成的服务基类,重写对应的rpc方法。
2. 配置服务器端口ip信息等。
3. 注册rpc服务。
4. 启动服务器进行监听
#include<grpcpp/grpcpp.h>
#include "../generated/hello.pb.h"
#include "../generated/hello.grpc.pb.h"#include <grpcpp/impl/codegen/status.h>
#include <grpcpp/security/server_credentials.h>
#include <grpcpp/server_builder.h>
#include <iostream>
#include <memory>
#include <string>
//声明命名空间和作用域,方便简写
using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;
using hello::Greeter;
using hello::HelloReply;
using hello::HelloRequest;//继承由protoc生成的服务基类,覆盖rpc方法
class GreeterServiceImpl final : public Greeter::Service
{public:Status SayHello(ServerContext* context, const HelloRequest* request,HelloReply* response){std::string msg = "Hello " + request->name() + "! [from gRPC Server]";response->set_message(msg);return Status::OK; }
};//启动服务器注册rpc服务
void RunServer(const std::string& address)
{GreeterServiceImpl service; //服务对象ServerBuilder builder;//绑定监听地址,使用明文通道(实际生产建议用TLS)//InsecureServerCredentials()告诉grpc服务器使用明文传输协议,不对数据进行加密,可以快速搭建服务,不需要配置SSL、TLS证书builder.AddListeningPort(address,grpc::InsecureServerCredentials());//注册服务实现builder.RegisterService(&service);std::unique_ptr<Server> server(builder.BuildAndStart());//该函数:1.收集buidler设置的配置项目//2.创建Server实例//3.为每个监听端口建立监听器//4.注册用户定义的服务//5.启动服务器线程池与监听器std::cout << "RPC Server Listening on " << address << std::endl;server->Wait();
}int main() {RunServer("0.0.0.0:50051");return 0;
}
- RegisterService():把你实现的 GreeterServiceImpl 挂到服务器路由表,这样当请求打到 /hello.Greeter/SayHello(路径由包名+服务名+方法名决定)时,就会调用对应的函数。
- BuildAndStart():启动服务器。ServerBuilder 负责配置,BuildAndStart() 负责构建和启动。
- Wait():阻塞主线程以长期服务(直到被终止)
客户端编写
客户端主要做的是这几件事:
1. 创建到服务端的channel:它是到服务端的“连接抽象”,封装了 HTTP/2 连接池、重试、负载均衡(按配置)等。
2. 生成客户端的Stub:Stub类似信使帮我们进行代理,它封装了序列化、网络传输与反序列化,调用像本地函数一样。
3. 创建对应的请求填充信息。
4. 调用rpc方法,获取响应。
#include <cstddef>
#include <grpcpp/create_channel.h>
#include <grpcpp/impl/codegen/client_context.h>
#include <grpcpp/impl/codegen/status.h>
#include <grpcpp/security/credentials.h>
#include <iostream>
#include <memory>
#include <string>
#include <grpcpp/grpcpp.h>
#include "../generated/hello.pb.h"
#include "../generated/hello.grpc.pb.h"using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
using hello::Greeter;
using hello::HelloReply;
using hello::HelloRequest;int main1() {// 创建到服务端的 channel(演示用明文) 信道//// 它是到服务端的“连接抽象”,封装了 HTTP/2 连接池、重试、负载均衡(按配置)等。auto channel = grpc::CreateChannel("127.0.0.1:50051",grpc::InsecureChannelCredentials());// 生成的客户端 Stub,像“远程接口代理” 类似信使/// 这是 protoc 生成的“远程代理”,里面的每个方法都对应 .proto 里的每个 rpc。std::unique_ptr<Greeter::Stub> stub = Greeter::NewStub(channel);HelloRequest req;req.set_name("zqh");HelloReply resp;ClientContext ctx; // 可设置 metadata/超时等Status status = stub->SayHello(&ctx, req, &resp);// 1) 将 req 序列化为 protobuf 二进制// 2) 通过 gRPC/HTTP2 发到路径 /hello.Greeter/SayHello// 3) 收到响应体后反序列化到 resp// 4) 返回传输层状态给你检查if (!status.ok()) {std::cerr << "RPC failed: " << status.error_message() << std::endl;return 1;}std::cout << "Reply: " << resp.message() << std::endl;return 0;
}
使用Stub对象调用rpc方法时,他帮我们做了这几件事:
- 将req序列化为protobuf二进制
- 通过gRPC/HTTP2发到路径/hello.Greeter/SayHello
- 收到响应体后反序列化到 resp
- 返回传输层状态给你检查
注意:Status 是 gRPC 的返回类型,用来表示一个 RPC 调用的状态,表示这次RPC调用是否成功,要与上层设置的错误码进行区分。
编译生成可执行文件
注意要链接上对应的protobuf grpc++库等:
CXX := g++
PKG := pkg-config# 生成代码在 generated/ 下,源码里直接 #include "hello.pb.h"CXXFLAGS := -std=c++17 -Wall -pthread \$(shell $(PKG) --cflags protobuf grpc++)
# LDLIBS := $(shell pkg-config --libs protobuf grpc++),这样如果系统里的 libprotobuf/libgrpc++
#还依赖其他库(比如某些发行版会额外带上 -lz、-labsl_* 等),会自动带上,不会漏
LDLIBS := $(shell $(PKG) --libs protobuf grpc++)GEN_SRCS := generated/hello.pb.cc generated/hello.grpc.pb.cc
SERVER_SRCS := server/server.cc $(GEN_SRCS)
CLIENT_SRCS := client/client.cc $(GEN_SRCS)SERVER_BIN := RPCserver
CLIENT_BIN := RPCclientall: $(SERVER_BIN) $(CLIENT_BIN)$(SERVER_BIN): $(SERVER_SRCS)$(CXX) $(CXXFLAGS) $^ -o $@ $(LDLIBS)$(CLIENT_BIN): $(CLIENT_SRCS)$(CXX) $(CXXFLAGS) $^ -o $@ $(LDLIBS)clean:rm -f $(SERVER_BIN) $(CLIENT_BIN)

