转载

gRPC初识——同步单向gRPC

复利效应

gRPC是Google开源的RPC框架,拥有高性能、跨语言等诸多优点。gRPC官方网站为grpc.io。鉴于官网的介绍较为混乱,并且其教程并不完善易懂,故而这里做一个简单的整理,希望一起成长。

本文所采用编程语言为C++,其他语言可以参考,开发平台为Windows平台。

如何使用gRPC

gRPC基于Protocol Buffer,在使用gRPC时,一般都是按照下列步骤:

  • 定义proto3协议
  • 生成RPC代码
  • 实现服务端
  • 实现客户端

gRPC初识——同步单向gRPC

上图是gRPC原理图,gRPC服务端实现具体的RPC服务,客户端通过gRPC Stub来调用这些RPC服务。客户端和服务端是通过信道( Channel )来连接的。

gRPC有四种使用场景:单向RPC(一问一答)、服务端流式RPC(一问多答)、客户端流式RPC(多问一答)、双向流式RPC(多问多答)。gRPC的调用方式又分为同步(阻塞)和异步(非阻塞),所以我们需要根据需求,来选择使用场景和调用方式。四类服务方法如下:

  • 单项RPC,客户端发送请求给服务端,服务端发送一个应答;
rpc sayHello(HelloRequest) returns(HelloResponse) { }
  • 服务端流式RPC,客户端发送请求给服务端,可获取一个数据流,通过该数据流能够读取服务端后续发送的一系列消息;
rpc LotsOfReplies(HelloRequest) returns(stream HelloResponse) { }
  • 客户端流式RPC,客户端用提供的一个数据流写入并发送一系列消息给服务端。
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) { }
  • 双向流式RPC,客户端和服务端都可以分别通过一个读写数据流来发送一系列消息。
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){ }

对于gRPC使用场景,在生成RPC代码时,都会生成同步和异步接口。下文中会给出一个简单的同步单向gRPC示例,再次基础上我们会分析gRPC的详细代码。

有关gRPC的示例代码,都可以从 Github: gRPC-Guide 获取。

同步单向gRPC示例

定义proto3协议

这里需要有Protocol Buffer基础,具体使用可以Google。

# hello.proto

syntax="proto3";

package guide;

message HelloRequest {
string name = 1;
}

message HelloResponse {
string message = 1;
}

service HelloSvc {
rpc sayHello(HelloRequest) returns(HelloResponse);
}

生成RPC代码

为了显示目录结构,下面的RPC代码借助了cmake。 protoc.exe 可以从protobuf网站下载,也可以自己编译。

EXECUTE_PROCESS(COMMAND
${CMAKE_SOURCE_DIR}/thirdparty/gRPC/third_party/protobuf/cmake/win/Debug/protoc.exe
-I ${CMAKE_SOURCE_DIR}/protos/guide
--grpc_out=${CMAKE_SOURCE_DIR}/sync/client/src
--grpc_out=${CMAKE_SOURCE_DIR}/sync/server/src
--cpp_out=${CMAKE_SOURCE_DIR}/sync/client/src
--cpp_out=${CMAKE_SOURCE_DIR}/sync/server/src
--plugin=protoc-gen-grpc=${CMAKE_SOURCE_DIR}/thirdparty/gRPC/vsprojects/x64/Debug/grpc_cpp_plugin.exe
${CMAKE_SOURCE_DIR}/protos/guide/hello.proto)

通过上面的命令,我们会生成四个文件:

  • hello.grpc.pb.h
  • hello.grpc.pb.cc
  • hello.pb.h
  • hello.pb.cc

hello.pb.* 中定义了 HelloReqeustHelloResponse 消息的具体实现,而 hello.grpc.pb.* 中定义同步gRPC服务和异步gRPC服务等。后文的gRPC实现解析中会详细的讲解这块的代码。

实现服务端

class HelloService : public HelloSvc::Service
{
public:
HelloService() = default;
~HelloService() = default;

virtualStatussayHello(ServerContext* context,constHelloRequest* req, HelloResponse* rsp)override;
};

Status HelloService::sayHello(ServerContext* context, const HelloRequest* req, HelloResponse* rsp)
{
std::cout << "Received from client: " << req->name() << std::endl;
std::string response = "hello, ";
rsp->set_message(response + req->name());
return Status::OK;
}

voidrunServer()
{
guide::HelloService service;
ServerBuilder builder;
builder.AddListeningPort("0.0.0.0:50051", grpc::InsecureServerCredentials());
builder.RegisterService(&service);
std::unique_ptr<Server> server(builder.BuildAndStart());
server->Wait();
}

sayHello 接口是在 HelloSvc::Service 类中定义,这个类就是在 hello.grpc.pb.h 中生成的同步服务类。 HelloService 服务实现类派生自该类,并实现 sayHello 接口,我们就可以利用 ServerBuilder 建立服务(绑定端口,)并运行。

实现客户端

客户端通过Stub来调用RPC服务端的代码,Stub必须运行在具体Channel上。我们必须要先建立信道:

grpc::CreateChannel("localhost:50051", grpc::InsecureChannelCredentials())

在建立信道的基础上,新建Stub,通过Stub来调用具体的RPC代码。

class HelloClient
{
public:
HelloClient(std::shared_ptr<Channel> channel)
: _stub(HelloSvc::NewStub(channel)) { }
~HelloClient() = default;

std::stringsayHello(conststd::stringname);

private:
std::unique_ptr<HelloSvc::Stub> _stub;
};

std::string HelloClient::sayHello(std::string user)
{
HelloRequest req;
req.set_name(user);

HelloResponse rsp;
ClientContext ctx;
Status status = _stub->sayHello(&ctx, req, &rsp);
if (status.ok()) {
return rsp.message();
} else {
return "RPC Failed.";
}
}

调用RPC服务:

void runClient()
{
HelloClient client(grpc::CreateChannel(
"localhost:50051", grpc::InsecureChannelCredentials()));
std::string user("John");
std::string rsp = client.sayHello(user);
std::cout << "Hello Client Received: " << rsp << std::endl;
}

gRPC实现解析

上文中我们给出了单向RPC示例,步骤二:生成RPC代码会生成RPC服务和客户端调用代码,这块代码是gRPC实现的核心代码。该段代码涉及到三点:

  • 客户端桩Stub类代码
  • 服务端同步服务接口类代码
  • 服务端异步服务接口类代码

我们依次来看着三段代码:

1、客户端桩Stub类代码

class Stub GRPC_FINAL : public StubInterface {
public:
Stub(const std::shared_ptr< ::grpc::ChannelInterface>& channel);
::grpc::StatussayHello(::grpc::ClientContext* context,const::guide::HelloRequest& request, ::guide::HelloResponse* response)GRPC_OVERRIDE;
std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::guide::HelloResponse>>
AsyncsayHello(::grpc::ClientContext* context, const ::guide::HelloRequest& request, ::grpc::CompletionQueue* cq)
{
return std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::guide::HelloResponse>>(AsyncsayHelloRaw(context, request, cq));
}

客户端桩Stub类中分别定义了同步版本和异步版本的RPC方法,我们可以按照我们的需求来选择。

2、服务端同步服务接口类代码

class Service : public ::grpc::Service {
public:
Service();
virtual ~Service();
virtual ::grpc::StatussayHello(::grpc::ServerContext* context,const::guide::HelloRequest* request, ::guide::HelloResponse* response);
};

同步服务接口是阻塞的,服务端会阻塞在Server.wait()代码这儿,直到出现一次RPC调用。

3、服务端异步服务接口类代码

template <class BaseClass>
class WithAsyncMethod_sayHello : public BaseClass {
private:
voidBaseClassMustBeDerivedFromService(constService *service){}
public:
WithAsyncMethod_sayHello()
{
::grpc::Service::MarkMethodAsync(0);
}
~WithAsyncMethod_sayHello() GRPC_OVERRIDE
{
BaseClassMustBeDerivedFromService(this);
}
voidRequestsayHello(::grpc::ServerContext* context, ::guide::HelloRequest* request, ::grpc::ServerAsyncResponseWriter< ::guide::HelloResponse>* response, ::grpc::CompletionQueue* new_call_cq, ::grpc::ServerCompletionQueue* notification_cq,void*tag)
{
::grpc::Service::RequestAsyncUnary(0, context, request, response, new_call_cq, notification_cq, tag);
}
};
typedef WithAsyncMethod_sayHello<Service > AsyncService;

异步服务接口是既可以是阻塞的也可以是非阻塞的,异步服务通过在 CompletionQueue 上等待完成实践,一旦等到相应的事件 Next 函数返回( AsyncNext 等到一定的时间间隔也会返回),执行相应的RPC服务代码。

原文  http://freehacker.cn/developing/network/grpc-basic/
正文到此结束
Loading...