» gRPC快速入门:C++ » 5. 创建 client » 5.2 调用服务方法

调用服务方法

在本教程中,我们调用各方法的阻塞/同步版本。这意味着 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 来读取服务器的响应。我们使用 ClientReaderRead() 方法来重复读取服务端响应到一个 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;
    }
}

这里的读取和写入语法与客户端、服务端流式方法非常相似,尽管两端始终按照其写入顺序收到另一方的消息,但是客户端和服务端都可以按任意顺序读取和写入 - 各自的流完全独立运行。