核心工作
- 在一个 .proto 文件内定义服务.
- 用 protocol buffer 编译器生成服务器和客户端代码.
- 使用 gRPC 的 C++ API 为你的服务实现一个简单的客户端和服务器.
服务定义
route_guide.proto 定义服务的proto文件:
service RouteGuide {
rpc GetFeature(Point) returns (Feature) {}
rpc ListFeatures(Rectangle) returns (stream Feature) {}
rpc RecordRoute(stream Point) returns (RouteSummary) {}
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
}
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
生成客户端和服务器端代码
通过protocol buffer的编译器protoc以及一个特殊的gRPC C++插件来完成。运行对应的命令后在当前目录中生成如下文件:
route_guide.pb.h, 声明生成的消息类的头文件
route_guide.pb.cc, 包含消息类的实现
route_guide.grpc.pb.h, 声明你生成的服务类的头文件
route_guide.grpc.pb.cc, 包含服务类的实现
这些包括:
所有的填充,序列化和获取我们请求和响应消息类型的 protocol buffer 代码
名为 RouteGuide 的类,包含
- 为了客户端去调用定义在 RouteGuide 服务的远程接口类型(或者 存根 )
- 让服务器去实现的两个抽象接口,同时包括定义在 RouteGuide 中的方法。
创建服务器
想要让RouteGuide服务工作有两个部分
- 实现我们服务定义的生成的服务接口:做我们的服务的实际工作
- 运行一个gRPC服务器,监听来自客户端的请求并返回服务的响应。
实现RouteGuide
我们可以看出,服务器有一个实现了生成的 RouteGuide::Service 接口的 RouteGuideImpl 类:
class RouteGuideImpl final : public RouteGuide::Service {
Status GetFeature(ServerContext* context, const Point* point,Feature* feature) override {
feature->set_name(GetFeatureName(*point, feature_list_));
feature->mutable_location()——>CopyFrom(*point);
return Status::OK;
}
}
这个方法为 RPC 传递了一个上下文对象,包含了客户端的 Point protocol buffer 请求以及一个填充响应信息的Feature protocol buffer。在这个方法中,我们用适当的信息填充 Feature,然后返回OK的状态,告诉 gRPC 我们已经处理完 RPC,并且 Feature 可以返回给客户端。
启动服务器
一旦我们实现了所有的方法,我们还需要启动一个gRPC服务器,这样客户端才可以使用服务。下面这段代码展示了在我们RouteGuide服务中实现的过程:
void RunServer(const std::string& db_path) {
std::string server_address("0.0.0.0:50051");
RouteGuideImpl service(db_path);
ServerBuilder builder;
builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
builder.RegisterService(&service);
std::unique_ptr<Server> server(builder.BuildAndStart());
std::cout << "Server listening on " << server_address << std::endl;
server->Wait();
}
如你所见,我们通过使用ServerBuilder去构建和启动服务器。为了做到这点,我们需要:
创建我们的服务实现类 RouteGuideImpl 的一个实例。
创建工厂类 ServerBuilder 的一个实例。
在生成器的 AddListeningPort() 方法中指定客户端请求时监听的地址和端口。
用生成器注册我们的服务实现。
调用生成器的 BuildAndStart() 方法为我们的服务创建和启动一个RPC服务器。
调用服务器的 Wait() 方法实现阻塞等待,直到进程被杀死或者 Shutdown() 被调用。
创建客户端
examples/cpp/route_guide/route_guide_client.cc看到我们完整的客户端例子代码.
创建一个存梗
为了能调用服务的方法,我们先创建一个存根。
首先需要为我们的存根创建一个gRPC channel,指定我们想联机的服务器的地址和端口,以及channel相关的参数
grpc::CreateChannel("localhost:50051", grpc::InsecureCredentials(), ChannelArguments());
现在我们可以利用channel,使用从.proto中生成的RouteGuide类提供的NewStub方法去创建存根。
public:
RouteGuideClient(std::shared_ptr<ChannelInterface> channel,
const std::string& db)
: stub_(RouteGuide::NewStub(channel)) {
...
}
调用服务的方法
现在我们来看看如何调用服务的方法。注意,在本教程中调用的方法,都是 阻塞/同步 的版本:这意味着 RPC 调用会等待服务器响应,要么返回响应,要么引起一个异常。
简单RPC
调用简单 RPC GetFeature 几乎是和调用一个本地方法一样直观。
Point point;
Feature feature;
point = MakePoint(409146138, -746188906);
GetOneFeature(point, &feature);
...
bool GetOneFeature(const Point& point, Feature* feature) {
ClientContext context;
Status status = stub_->GetFeature(&context, point, feature);
...
}
如你所见,我们创建并且填充了一个请求的 protocol buffer 对象(例子中为 Point),同时为了服务器填写创建了一个响应 protocol buffer 对象。为了调用我们还创建了一个 ClientContext 对象——你可以随意的设置该对象上的配置的值,比如期限,虽然现在我们会使用缺省的设置。注意,你不能在不同的调用间重复使用这个对象。最后,我们在存根上调用这个方法,将其传给上下文,请求以及响应。如果方法的返回是OK,那么我们就可以从服务器从我们的响应对象中读取响应信息。
std::cout << "Found feature called " << feature->name() << " at "
<< feature->location().latitude()/kCoordFactor_ << ", "
<< feature->location().longitude()/kCoordFactor_ << std::endl;