基础教程

gRPC Objective-C 基础教程简介。

基础教程

gRPC Objective-C 基础教程简介。

本教程为 Objective-C 程序员提供了 gRPC 工作的基础介绍。

通过阅读本示例,您将了解如何

  • 在 .proto 文件中定义服务。
  • 使用 protocol buffer 编译器生成客户端代码。
  • 使用 Objective-C gRPC API 编写服务的简单客户端。

本教程假设您对 protocol buffers 有一定了解。请注意,本教程中的示例使用 proto3 版本的 protocol buffers 语言:您可以在 proto3 语言指南Objective-C 生成代码指南 中找到更多信息。

为什么要使用 gRPC?

我们的示例是一个简单的路线映射应用程序,它允许客户端获取路线上有关特征的信息、创建路线摘要,并与服务器和其他客户端交换路线信息(例如交通更新)。

使用 gRPC,我们可以在 .proto 文件中定义服务,然后生成 gRPC 支持的任何语言的客户端和服务器,这些客户端和服务器可以在从大型数据中心内的服务器到您自己的平板电脑等各种环境中运行 — gRPC 会为您处理不同语言和环境之间通信的所有复杂性。我们还可以获得使用 protocol buffers 的所有优势,包括高效序列化、简单的 IDL 和便捷的接口更新。

示例代码和设置

本教程的示例代码位于 grpc/grpc/examples/objective-c/route_guide 中。要下载示例,请通过运行以下命令克隆 grpc 仓库

git clone -b v1.71.0 --depth 1 --shallow-submodules https://github.com/grpc/grpc
cd grpc
git submodule update --init

然后将当前目录更改为 examples/objective-c/route_guide

cd examples/objective-c/route_guide

我们的示例是一个简单的路线映射应用程序,它允许客户端获取路线上有关特征的信息、创建路线摘要,并与服务器和其他客户端交换路线信息(例如交通更新)。

您还需要安装 Cocoapods 以及生成客户端库代码(和用于测试的另一种语言的服务器)的相关工具。您可以按照 这些设置说明 来获取后者。

试试看!

要试用示例应用程序,我们需要在本地运行一个 gRPC 服务器。例如,让我们编译并运行此仓库中的 C++ 服务器

pushd ../../cpp/route_guide
make
./route_guide_server &
popd

现在使用 Cocoapods 生成并安装我们的 .proto 文件的客户端库

pod install

(这可能需要编译 OpenSSL,如果 Cocoapods 尚未在您的计算机缓存中,这大约需要 15 分钟)。

最后,打开 Cocoapods 创建的 XCode 工作区,并运行应用程序。您可以在 ViewControllers.m 中检查调用代码,并在 XCode 的日志控制台中查看结果。

接下来的章节将逐步指导您了解如何定义此 proto 服务、如何从它生成客户端库以及如何创建使用该库的应用程序。

定义服务

首先,让我们看看我们正在使用的服务是如何定义的。gRPC service 及其方法的 requestresponse 类型均使用 protocol buffers 定义。您可以在 examples/protos/route_guide.proto 中查看我们示例的完整 .proto 文件。

要定义服务,您可以在 .proto 文件中指定一个命名为 service

service RouteGuide {
   ...
}

然后,在您的服务定义中定义 rpc 方法,并指定其请求和响应类型。Protocol buffers 允许您定义四种类型的服务方法,所有这些方法都用于 RouteGuide 服务中

  • 一种 simple RPC,其中客户端向服务器发送请求并稍后接收响应,就像普通的远程过程调用一样。

    // Obtains the feature at a given position.
    rpc GetFeature(Point) returns (Feature) {}
    
  • 一种 response-streaming RPC,其中客户端向服务器发送请求并接收响应消息流。通过在 stream 关键字放在 response 类型之前,您可以指定一个响应流式方法。

    // Obtains the Features available within the given Rectangle.  Results are
    // streamed rather than returned at once (e.g. in a response message with a
    // repeated field), as the rectangle may cover a large area and contain a
    // huge number of features.
    rpc ListFeatures(Rectangle) returns (stream Feature) {}
    
  • 一种 request-streaming RPC,其中客户端向服务器发送一系列消息。客户端写完消息后,等待服务器读完所有消息并返回响应。通过在 stream 关键字放在 request 类型之前,您可以指定一个请求流式方法。

    // Accepts a stream of Points on a route being traversed, returning a
    // RouteSummary when traversal is completed.
    rpc RecordRoute(stream Point) returns (RouteSummary) {}
    
  • 一种 bidirectional streaming RPC,其中双方都向对方发送一系列消息。这两个流独立运行,因此客户端和服务器可以按任意顺序读写:例如,服务器可以在写入响应之前等待接收所有客户端消息,或者它可以交替读取消息然后写入消息,或者读写以其他方式组合。每个流中消息的顺序都得到保留。通过将 stream 关键字放在请求和响应类型之前,您可以指定此类方法。

    // Accepts a stream of RouteNotes sent while a route is being traversed,
    // while receiving other RouteNotes (e.g. from other users).
    rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
    

我们的 .proto 文件还包含我们服务方法中使用的所有请求和响应类型的 protocol buffer 消息类型定义 - 例如,这是 Point 消息类型

// Points are represented as latitude-longitude pairs in the E7 representation
// (degrees multiplied by 10**7 and rounded to the nearest integer).
// Latitudes should be in the range +/- 90 degrees and longitude should be in
// the range +/- 180 degrees (inclusive).
message Point {
  int32 latitude = 1;
  int32 longitude = 2;
}

您可以通过在文件顶部添加 objc_class_prefix 选项来指定生成的类的前缀。例如

option objc_class_prefix = "RTG";

生成客户端代码

接下来,我们需要从 .proto 服务定义生成 gRPC 客户端接口。我们使用 protocol buffer 编译器(protoc)以及专门的 gRPC Objective-C 插件来完成此操作。

为简单起见,我们提供了一个 Podspec 文件,它会为您运行 protoc,并提供合适的插件、输入和输出,并描述了如何编译生成的文件。您只需在此目录(examples/objective-c/route_guide)中运行

pod install

它在将生成的库安装到此示例的 XCode 项目之前,会运行

protoc -I ../../protos --objc_out=Pods/RouteGuide --objcgrpc_out=Pods/RouteGuide ../../protos/route_guide.proto

运行此命令会在 Pods/RouteGuide/ 下生成以下文件

  • RouteGuide.pbobjc.h,这是一个声明您生成的 message 类的头文件。
  • RouteGuide.pbobjc.m,它包含您的 message 类的实现。
  • RouteGuide.pbrpc.h,这是一个声明您生成的 service 类的头文件。
  • RouteGuide.pbrpc.m,它包含您的 service 类的实现。

这些文件包含

  • 所有用于填充、序列化和检索我们的请求和响应消息类型的 protocol buffer 代码。
  • 一个名为 RTGRouteGuide 的类,它允许客户端调用在 RouteGuide 服务中定义的方法。

您还可以使用提供的 Podspec 文件从任何其他 proto 服务定义生成客户端代码;只需替换名称(与文件名匹配)、版本及其他元数据即可。

创建客户端应用程序

在本节中,我们将介绍如何为我们的 RouteGuide 服务创建 Objective-C 客户端。您可以在 examples/objective-c/route_guide/ViewControllers.m 中查看完整的示例客户端代码。

构建服务对象

要调用服务方法,我们首先需要创建一个服务对象,即生成的 RTGRouteGuide 类的实例。该类的指定初始化方法需要一个包含我们要连接的服务器地址和端口的 NSString *

#import <GRPCClient/GRPCCall+Tests.h>
#import <RouteGuide/RouteGuide.pbrpc.h>
#import <GRPCClient/GRPCTransport.h>

static NSString * const kHostAddress = @"localhost:50051";
...
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
options.transport = GRPCDefaultTransportImplList.core_insecure;

RTGRouteGuide *service = [[RTGRouteGuide alloc] initWithHost:kHostAddress callOptions:options];

请注意,我们的服务是使用不安全传输构建的。这是因为我们将用于测试客户端的服务器不使用 TLS。这没问题,因为它将在我们的开发机器上本地运行。然而,最常见的情况是连接互联网上的 gRPC 服务器,通过 TLS 运行 gRPC。对于这种情况,设置选项 options.transport 是不需要的,因为 gRPC 默认会使用安全的 TLS 传输。

调用服务方法

现在让我们看看如何调用服务方法。您会发现,所有这些方法都是异步的,因此您可以在应用程序的主线程中调用它们,而无需担心冻结 UI 或操作系统终止您的应用程序。

简单 RPC

调用简单 RPC GetFeature 就像调用 Cocoa 上的任何其他异步方法一样简单。


RTGPoint *point = [RTGPoint message];
point.latitude = 40E7;
point.longitude = -74E7;

GRPCUnaryResponseHandler *handler =
    [[GRPCUnaryResponseHandler alloc] initWithResponseHandler:
        ^(RTGFeature *response, NSError *error) {
          if (response) {
            // Successful response received
          } else {
            // RPC error
          }
        }
                                        responseDispatchQueue:nil];

[[service getFeatureWithMessage:point responseHandler:handler callOptions:nil] start];

如您所见,我们创建并填充一个请求 protocol buffer 对象(在本例中是 RTGPoint)。然后,我们在服务对象上调用该方法,并将请求以及用于处理响应(或任何 RPC 错误)的块传递给它。如果 RPC 成功完成,则调用处理程序块,其中 error 参数为 nil,并且我们可以从 response 参数中读取服务器的响应信息。如果发生某些 RPC 错误,则调用处理程序块,其中 response 参数为 nil,并且我们可以从 error 参数中读取问题的详细信息。

流式 RPC

现在让我们看看流式方法。这里我们调用响应流式方法 ListFeatures,这导致我们的客户端应用程序接收地理位置的 RTGFeature


- (void)didReceiveProtoMessage(GPBMessage *)message {
  if (message) {
    NSLog(@"Found feature at %@ called %@.", response.location, response.name);
  }
}

- (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error {
  if (error) {
    NSLog(@"RPC error: %@", error);
  }
}

- (void)execRequest {
  ...
  [[service listFeaturesWithMessage:rectangle responseHandler:self callOptions:nil] start];
}

请注意,此处不是提供响应处理程序对象,而是视图控制器对象本身处理响应。方法 didReceiveProtoMessage: 在收到消息时调用;它可以被调用任意次。方法 didCloseWithTrailingMetadata: 在调用完成并从服务器收到 gRPC 状态时(或在调用期间发生任何错误时)调用。

请求流式方法 RecordRoute 期望从客户端接收 RTGPoint 流。调用开始后,可以将此流写入 gRPC 调用对象。

RTGPoint *point1 = [RTGPoint message];
point.latitude = 40E7;
point.longitude = -74E7;

RTGPoint *point2 = [RTGPoint message];
point.latitude = 40E7;
point.longitude = -74E7;

GRPCUnaryResponseHandler *handler =
    [[GRPCUnaryResponseHandler alloc] initWithResponseHandler:
        ^(RTGRouteSummary *response, NSError *error) {
            if (response) {
              NSLog(@"Finished trip with %i points", response.pointCount);
              NSLog(@"Passed %i features", response.featureCount);
              NSLog(@"Travelled %i meters", response.distance);
              NSLog(@"It took %i seconds", response.elapsedTime);
            } else {
              NSLog(@"RPC error: %@", error);
            }
        }
                                        responseDispatchQueue:nil];
GRPCStreamingProtoCall *call =
    [service recordRouteWithResponseHandler:handler callOptions:nil];
[call start];
[call writeMessage:point1];
[call writeMessage:point2];
[call finish];

请注意,由于 gRPC 调用对象不知道请求流的末尾,因此当请求流完成后,用户必须调用 finish: 方法。

最后,让我们看看我们的双向流式 RPC RouteChat()。调用双向流式 RPC 的方式就是调用请求流式 RPC 和响应流式 RPC 的结合。


- (void)didReceiveProtoMessage(GPBMessage *)message {
  RTGRouteNote *note = (RTGRouteNote *)message;
  if (note) {
    NSLog(@"Got message %@ at %@", note.message, note.location);
  }
}

- (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error {
  if (error) {
    NSLog(@"RPC error: %@", error);
  } else {
    NSLog(@"Chat ended.");
  }
}

- (void)execRequest {
  ...
  GRPCStreamingProtoCall *call =
      [service routeChatWithResponseHandler:self callOptions:nil];
  [call start];
  [call writeMessage:note1];
  ...
  [call writeMessage:noteN];
  [call finish];
}
最后修改于 2024 年 11 月 25 日: feat: 将 $ shell 行指示符移至 scss (#1354) (ab8b3af)