gRPC学习

一、什么是gRPC?

1.1 gRPC简介

gRPCprotocol buffers

gRPC可以使用 protocol buffers作为其接口定义语言(IDL)和基础消息交换格式

1.1.1 概述

在gRPC中,客户端应用程序可以不同的机器上像调用本地方法一样,直接调用服务器应用程序上的方法,使得更容易创建分布式应用和服务。

gRPC是基于定义服务的思想,指定可以远程调用的方法及其参数和返回类型。在服务端,服务端实现此接口并运行gRPC服务器来处理客户端调用。在客户端,客户端有一个stub,提供与服务器相同的方法。

1)使用protocol buffers

默认情况下,gRPC使用protocol buffers,其是谷歌用于序列化结构化数据的开源框架

第一步:在一个proto 文件中定义你想要序列化的数据的结构

message Person {
    string name = 1;
    int32 id = 2;
    bool has_ponycopter = 3;
}

第二步:使用protocol buffers编译器从proto文件中以首选语言生成数据访问类,这为每个字段提供了简单的访问器,以及将整个结构序列化/解析为原始字节的方法。

例如,如果选择的语言是C++,那么在上面的示例中运行编译器后,将生成一个可被调用的类。可以在应用程序中使用这个类来填充、序列化和检索protocol buffer message。

在普通的proto文件中定义gRPC服务,使用RPC方法参数和返回类型来作为protocol buffers的message:

#The greater service definition
service Greater {
    //sends a greeting
    rpc SayHello (HelloRequest) returns (HelloReply) {}
}

# The request message containing the user's name
message HelloRequest {
    string name = 1;
}

# The response message containing the greetings
message HelloReply {
    string message = 1;
}

1.2 核心概念

介绍gRPC的核心概念、架构和生命周期

1.2.1 概述

1)服务定义

与许多RPC系统一样,gRPC基于定义服务的思想,指定可以远程调用的方法及其参数和返回类型。缺省情况下,gRPC使用protocol buffers作为接口定义语言(IDL)来描述服务接口和负载消息的结构。如果需要,可以使用其他替代方案。

service HelloService {
  rpc SayHello (HelloRequest) returns (HelloResponse);
}

message HelloRequest {
  string greeting = 1;
}

message HelloResponse {
  string reply = 1;
}

 gRPC允许定义四种服务方法:

  • Unary RPCs:客户端向服务端发送单个请求并获得单个响应,就像普通的函数调用
rpc SayHello(HelloRequest) returns (HelloResponse);
  • Server Streaming RPCs:客户端向服务端发送请求并获得读取消息序列的流。客户端从返回的流中读取消息,直到没有更多的消息。gRPC保证了单个RPC调用中消息顺序。
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
  •  Client Streming RPCs:客户端写入消息序列并将它们发送到服务器,同样使用提供的流。一旦客户端完成了消息的写入,它将等待服务器读取消息并返回其响应。gRPC再次保证了单个RPC调用中的消息顺序。
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
  • Bidirectional Streaming RPCs:双方使用读写流发送消息序列。这两个流独立运行,因此客户端和服务器可以按照自己喜欢的顺序进行读写:例如,服务器可以等待接收到所有客户端消息后再写入响应,或者它可以交替地读取消息然后写入消息,或者其他一些读写组合。保留了每个流中的消息顺序。
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);

2)使用API

从文件中的服务定义开始,gRPC提供协议缓冲区编译器插件来生成客户端和服务器端代码。gRPC用户通常在客户端调用这些API,并在服务器端实现相应的API

  • 在服务器端,服务器实现服务声明的方法,并运行gRPC服务器来处理客户端调用。gRPC基础结构解码传入请求,执行服务方法,并编码服务响应。
  • 在客户端,客户端有一个本地对象,称为Stub(对于某些语言,首选术语是客户端),它实现与服务相同的方法。然后,客户端可以在本地对象上调用这些方法,这些方法将调用的参数封装在适当的协议缓冲区消息类型中,将请求发送到服务器,并返回服务器的协议缓冲区响应。

3)RPC生命周期

深入了解当gRPC客户机调用gRPC服务器方法时会发生什么

1. Unary RPCs

a)一旦客户端调用了Stub方法,服务端就会收到RPC调用的通知,其中包括该调用的客户端元数据、方法名称和指定的截止日期

b)服务端可以直接发送回自己的初始元数据(在任何响应之前发送),或者等待客户端的请求消息。

c)一旦服务端获得了客户端的请求消息,他就会执行创建和填充响应所需的任何工作。然后将响应连同状态详细信息以及可选的尾部元数据一起返回给客户端

d)如果响应状态为OK,则客户端获得响应,完成客户端的调用

2. Server streaming RPCs

Server streaming RPC类似于Unary RPC,不同之处在于服务器在响应客户端的请求时返回消息流。发送完所有消息后,服务器的状态详细信息(状态码和可选状态消息)和可选的尾随元数据被发送到客户端。这就完成了服务器端的处理。客户端在拥有所有服务器消息后完成调用

3. Client streaming RPCs

Client streaming RPC类似于Unary RPC,不同之处是客户端向服务器发送消息流而不是单个消息。服务器用一条消息(连同它的状态详细信息和可选的尾随元数据)进行响应,通常是在它接收到所有客户机的消息之后,但不一定是这样。

4. Bidirectional streaming RPCs

在Bidirectional streaming RPC中,调用由调用方法的客户端和接收客户端元数据、方法名称和截止日期的服务器发起。服务器可以选择发回其初始元数据,或者等待客户端开始流式传输消息。

客户端和服务器端流处理是特定于应用程序的。由于这两个流是独立的,客户端和服务器可以以任何顺序读写消息。例如,服务器可以等到接收到客户端的所有消息后再写入消息,或者服务器和客户端可以玩“ping-pong”游戏——服务器收到请求,然后发回响应,然后客户端根据响应发送另一个请求,以此类推。

4)Deadline/Timeouts

gRPC允许客户端指定在RPC以错误终止之前他们愿意等待RPC完成的时间。在服务器端,服务器

### C++ gRPC 学习教程和示例代码 #### 一、环境搭建 为了能够顺利运行C++中的gRPC程序,需要先完成开发环境的配置。这通常涉及到安装必要的依赖库以及设置编译工具链。具体来说,可以按照官方文档指导来准备所需的软件包,比如Protocol Buffers编译器`protoc`及其对应的C++插件,还有gRPC核心库等[^1]。 ```bash sudo apt-get install build-essential autoconf libtool pkg-config git clone https://github.com/protocolbuffers/protobuf.git cd protobuf ./autogen.sh && ./configure && make -j$(nproc) && sudo make install ``` 对于gRPC本身,则可以通过如下命令获取并构建: ```bash git clone --recurse-submodules -b v1.48.x https://github.com/grpc/grpc cd grpc mkdir -p cmake/build && cd cmake/build cmake ../.. make -j$(nproc) sudo make install ``` #### 二、创建Protobuf文件 接下来就是定义服务接口,在`.proto`文件里描述消息结构和服务方法。这里给出一个简单的例子——HelloWorld服务,其中包含了一个名为SayHello的方法用于接收请求并向客户端返回响应信息。 ```protobuf syntax = "proto3"; option cc_enable_arenas = true; package helloworld; // The greeting service definition. service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply) {} } // The request message containing the user's name. message HelloRequest { string name = 1; } // The response message containing the greetings message HelloReply { string message = 1; } ``` 保存上述内容到`helloworld.proto`之后,利用之前提到过的`protoc`命令行工具将其转换成相应的头文件与源码文件以便后续使用。 #### 三、编写服务器端逻辑 基于前面所生成的服务类模板,现在可以在项目中实现具体的业务处理函数了。下面展示的是如何继承自动生成出来的基类,并重写虚函数以提供实际功能的部分代码片段。 ```cpp #include <iostream> #include "helloworld.grpc.pb.h" using namespace std; using grpc::Server; using grpc::ServerBuilder; using grpc::ServerContext; using grpc::Status; using helloworld::Greeter; using helloworld::HelloReply; using helloworld::HelloRequest; class GreeterServiceImpl final : public Greeter::Service { public: Status SayHello(ServerContext* context, const HelloRequest* request, HelloReply* reply) override { string prefix("Hello "); reply->set_message(prefix + request->name()); return Status::OK; } }; ``` 这段代码实现了当接收到客户端发起的调用时会执行的操作:拼接字符串形成回复文本并通过参数传递给对方。 #### 四、启动监听进程 有了完整的协议声明加上对应的功能模块后就可以着手建立网络连接等待远端访问啦! ```cpp void RunServer() { string server_address("0.0.0.0:50051"); GreeterServiceImpl service; 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. unique_ptr<Server> server(builder.BuildAndStart()); cout << "Server listening on " << server_address << 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(); } ``` 此部分负责初始化HTTP/2传输层设施并将指定地址开放出去供外界联系;同时注册好先前定义好的处理器对象使得每次有新链接进来都能找到合适的地方去解析数据流进而触发相应动作。 #### 五、客户端编程指南 最后一步自然是要让应用程序具备主动出击的能力咯~即构造出能向远程主机发出请求的消息体格式化为wire format再经由socket发送过去得到回应为止的过程。 ```cpp void RunClient() { string target_str("localhost:50051"); // 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; you may provide extra arguments to indicate credentials, // compression Level etc. shared_ptr<Channel> channel = CreateChannel(target_str, InsecureChannelCredentials()); // Stub acts like a proxy object representing remote side entity. unique_ptr<Greeter::Stub> stub(Greeter::NewStub(channel)); // Data we are sending to the server. HelloRequest request; request.set_name("you"); // 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()) { cout << "Greeter received: " << reply.message() << endl; } else { cerr << status.error_code() << ": " << status.error_message() << endl; } } ``` 以上便是整个流程的大致介绍,当然这只是冰山一角而已,更多高级特性和最佳实践还需要读者朋友们自行探索学习哦~
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

罗啰萝在努力

如果这篇文章对你有帮助,望支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值