调用服务方法
在本教程中,我们调用各方法的阻塞/同步版本。这意味着 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 对象交由 server 来填充(此处是 Feature
)。我们还传递了一个 ClientContext
对象,以便设置 RPC 配置值,例如截止时间。此处我们暂使用默认设置。
注意这个对象不可以在不同的调用间复用。最后,我们调用 stub 上的方法,传递 context,请求对象以及待填充响应对象给它。
如果方法返回 OK
,那么我们可以从响应对象中读取来自服务器的响应信息。
std::cout << "Found feature called " << feature->name() << " at "
<< feature->location().latitude()/kCoordFactor_ << ", "
<< feature->location().longitude()/kCoordFactor_ << std::endl;
服务端流式 RPC
现在让我们来看看流式方法。如果你已经阅读了创建 server 部分,其中一些可能看起来非常相似。流式 RPC 在两端都以类似的方式实现。
以下是调用服务端流式方法 ListFeatures
的代码,它返回地理信息 Feature 的流:
std::unique_ptr<ClientReader<Feature> > reader(
stub_->ListFeatures(&context, rect));
while (reader->Read(&feature)) {
std::cout << "Found feature called "
<< feature.name() << " at "
<< feature.location().latitude()/kCoordFactor_ << ", "
<< feature.location().longitude()/kCoordFactor_ << std::endl;
}
Status status = reader->Finish();
我们将 context 和请求对象传递给方法,而不是之前的 context、请求对象和待填充响应对象。我们得到一个 ClientReader
对象。客户端可以使用 ClientReader
来读取服务器的响应。我们使用 ClientReader
的 Read()
方法来重复读取服务端响应到一个 protocol buffer 对象中(此处是一个 Feature
),直到没有更多的消息为止。客户端需要在每次调用后检查 Read()
的返回值。如果返回 true
,则流仍然正常,可以继续读取;如果返回 false
,则消息流已经结束。最后,我们调用流的 Finish()
方法来完成调用并获取 RPC 状态。
客户端流式 RPC
客户端流式方法 RecordRoute
与服务器端方法类似,不同之处在于我们只传递了 context 和响应对象给方法,并获得了一个 ClientWriter
。
std::unique_ptr<ClientWriter<Point> > writer(
stub_->RecordRoute(&context, &stats));
for (int i = 0; i < kPoints; i++) {
const Feature& f = feature_list_[feature_distribution(generator)];
std::cout << "Visiting point "
<< f.location().latitude()/kCoordFactor_ << ", "
<< f.location().longitude()/kCoordFactor_ << std::endl;
if (!writer->Write(f.location())) {
// Broken stream.
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(
delay_distribution(generator)));
}
writer->WritesDone();
Status status = writer->Finish();
if (status.IsOk()) {
std::cout << "Finished trip with " << stats.point_count() << " points\n"
<< "Passed " << stats.feature_count() << " features\n"
<< "Travelled " << stats.distance() << " meters\n"
<< "It took " << stats.elapsed_time() << " seconds"
<< std::endl;
} else {
std::cout << "RecordRoute rpc failed." << std::endl;
}
当我们使用 Write()
向流写入客户端的请求后,我们需要在流上调用 WritesDone()
,以便让 gRPC 知道我们已经完成写入,然后调用 Finish()
来完成调用并获取 RPC 状态。如果状态是 OK
,我们最初传递给 RecordRoute()
的响应对象将被服务器的响应填充。
双向流式 RPC
最后,让我们来看看双向流式RPC RouteChat()
。在这种情况下,我们只需将一个 context 传递给方法,并返回一个 ClientReaderWriter
。我们可以使用它来写入和读取消息。
void RouteChat() {
ClientContext context;
std::shared_ptr<ClientReaderWriter<RouteNote, RouteNote> > stream(
stub_->RouteChat(&context));
std::thread writer([stream]() {
std::vector<RouteNote> notes{MakeRouteNote("First message", 0, 0),
MakeRouteNote("Second message", 0, 1),
MakeRouteNote("Third message", 1, 0),
MakeRouteNote("Fourth message", 0, 0)};
for (const RouteNote& note : notes) {
std::cout << "Sending message " << note.message() << " at "
<< note.location().latitude() << ", "
<< note.location().longitude() << std::endl;
stream->Write(note);
}
stream->WritesDone();
});
RouteNote server_note;
while (stream->Read(&server_note)) {
std::cout << "Got message " << server_note.message() << " at "
<< server_note.location().latitude() << ", "
<< server_note.location().longitude() << std::endl;
}
writer.join();
Status status = stream->Finish();
if (!status.ok()) {
std::cout << "RouteChat rpc failed." << std::endl;
}
}
这里的读取和写入语法与客户端、服务端流式方法非常相似,尽管两端始终按照其写入顺序收到另一方的消息,但是客户端和服务端都可以按任意顺序读取和写入 - 各自的流完全独立运行。