grpc-swift入门

作者: AntonyWong | 来源:发表于2022-06-05 02:33 被阅读0次

    先戴个头盔,以下所有论述不保证正确性,请自行甄别服用。

    不想看前面的屁话,要直接上代码的,请跳到「iOS App端如何实现和RPC服务器通信」章节

    什么是RPC、gRPC、grpc-swift

    要搞清楚什么是grpc-swift

    就要先搞清楚什么是gRPC

    要搞清楚什么是gRPC,

    就要先搞清楚什么是RPC

    What is RPC

    RPC,是Remote procedure call的简称,翻译过来——「远程过程调用」。

    请讲人话?OK,举个🌰,假如我要转1个比特币给你(事实上我并没有1个比特币,不嫌弃波卡幣/Polkadot的话,可以转给你——最近跌好惨🫠),然后我就通过RPC这种「传输方式」转给你。

    本质上这是一个传输数据的过程。所以RPC,就简单理解成「一种传输数据的方式」。

    对比地看,我们还有另一种更常用的方式:HTTP+REST。(不知道啥玩意儿?不要紧。就理解成是互联网上另一种传输数据的方式就好了。)

    简单来说,HTTP+REST方式,聚焦在数据data上:发送一个请求request,然后返回数据response

    而RPC,聚焦在「方法」上——直接调用一个「方法/函数/command」——只是对比于在同一个软件内部调用方法,RPC中调用有点不太一样,它是从电脑A,直接调用电脑B中的某个「方法」,是一个远程调用(Remote Call)。

    然后这个「方法」和我们常见的「方法」一样,会有参数、返回值。要传输的数据,就放在参数、返回值里面,最终实现数据的传输。如下图:


    RPC的数据传输过程

    截图出处: Comparing web API types: SOAP, REST, GraphQL and RPC

    What is gRPC

    OK,RPC是一种传输数据的方式,那gRPC又是什么?

    聪明的你意识到了,这里多了一个「g」。不过,大多数人第一反应,应该不会认为「g」表示的是「Google」——毕竟百度是众多宇宙中第一好用的搜索引擎。

    事实上「g」表示的,正是Google(起码大多数人是这样认为的。关于「g」的其他含义,下面再作补充),gRPC是Google主导的对RPC的具体实现。卖点:高性能、开源、通用(支持很多语言: Supported languages)。

    其中,Protocol Buffers,有需要认识一下的。可以把它类比成XML、JSON,但是Protocol Buffers的数据包更小、速度更快、实现更简单。

    你可能会猜到,RPC还有XML-RPCJSON-RPC这些其他的实现。而gRPC,更准确的对标,我觉得应该叫「Protocol Buffers-RPC」~

    再回到「g」,事实上,把它理解成「Google」没有错,不过,经常没事找抽的工程师,对「g」是有另一番调侃的,详情: GRPC Core: g_stands_for

    为了让大家有更直观的理解,下面把互联网数据传输的一部分发展史展现如下:


    history

    What is grpc-swift

    OK,我们有gRPC了,是不是可以开始写iOS端的App,从「RPC后台」拿一些数据了?
    上面提到,gRPC支持多种语言,其中就有Objective-C(如果暂时不理解「支持」的含义,后面会继续解释)。

    但是,现在大家都用Swift开发iOS App,所以就有了grpc-swift了。

    所以,总括来看,他们的关系如下图:
    (对了,题外话:Bitcoin用的是JSON-RPC

    RPC关系图

    为什么要用gRPC

    OK,上面讲了各种概念。那么,为什么要用gRPC呢?
    (注意,我这里的问题是「为什么要用gRPC」,而不是「为什么要用RPC」)

    天下武功,唯快不破

    这是一条受用千年的古训。

    gRPC用了上面提到过的Protocol Buffers,在数据传输过程,数据包/payload是基于二进制/binary的。
    所以,数据包的size,比JSON小很多(想象一个例子:一个55bytes,一个20bytes)。
    另外,二进制形式的数据包,CPU可以更高效地进行「序列化」和「反序列化」。

    所以,概括来说,用gRPC的小伙伴,是想榨出更多的性能。

    当然,gRPC也不是万金油,也有自己的劣势:浏览器支持有限、二进制格式对人类不友好等等。

    更多的优劣分析,可以参考:
    What is gRPC: Main Concepts, Pros and Cons, Use Cases

    至于你要不要用gRPC,请自行斟酌——跟我好像没有关系。

    iOS App端如何实现和RPC服务器通信

    好了,上面讲了一大堆屁话,终于到正题了。

    要写一个iOS的App,和gRPC后台通信。首先,我们要有一个gRPC后台——好一句废话。

    服务端跑起来

    没有后台经验的小伙伴不需要菊花一紧,你只需要在你的终端敲入swift run HelloWorldServer这行命令,然后再轻轻敲一下回车键,官方GitHub的HelloWorld后台,就会神奇般地跑起来了:

    • grpc-swift项目clon下来
    • cd到项目根目录
    • 打开终端/Termanil,执行swift run HelloWorldServer命令(成功后会看到终端的打印:server started on port 1234

    这样,RPC后台就跑起来了。从这个后台能拿到什么数据?
    首先这个后台有一个方法sayHello()可供(App)客户端调用,然后,假如你调用这个方法并传入Antony作为方法的参数(准确说应该是一个Rquest对象),他会返回字符串Hello Antony!(准确说应该是一个Response对象)。如果不传参数,默认返回Hello stranger!
    有没有很厉害?!

    如果你迫不及待,没写好App,就想调sayHello()方法试试看。可以:

    • 再打开一个终端
    • cd到项目根目录
    • 执行swift run HelloWorldClient命令(成功后会看到打印:Greeter received: Hello stranger!
      表示我们的客户端(是一个命令行工具)调用了sayHello()并收到了后台服务端的数据了!
      RPC后台跑起来!

    .proto文件的撰写

    在写App之前,还想介绍一下 .proto文件。

    上面介绍了,我们客户端这边,调用了sayHello()方法,同样地,到时候我们的App,也会调用这个方法,获取数据,而这个方法自然是用Swift语言写的,我们需要自己写这个方法吗?答案是不需要。那这个方法从哪里来?

    答案就是接下来介绍的 .proto文件。我们利用Protocol Buffers这个接口描述语言,来把我们的数据传输过程中的「数据模型」和「方法」在 .proto文件定义好,然后再通过相关指令,生成你的客户端需要的代码。比如iOS的Swift、Android的Kotlin等等。
    (上面说过的「gRPC支持多种语言」,就是这个意思。)

    下面是仓库中的helloworld.proto 文件

    // Protocol Buffers有proto2版本,这里表明,我们用的是比较新的proto3版本
    syntax = "proto3";
    
    // 下面的option,是生成代码时候的一些配置
    option java_multiple_files = true; // 生成的Java代码,是否分成多个文件
    option java_package = "io.grpc.examples.helloworld";
    option java_outer_classname = "HelloWorldProto";
    option objc_class_prefix = "HLW"; // 生成的Objective-C代码的前缀是什么
    
    // 「包名」。想象一下,你在这里定义、最后生成的「类」和「方法」,有可能会和你原来App的「类」、「方法」重名。
    // 这里加一个package的名称,避免「命名冲突」
    package helloworld;
    
    // 定义一个service
    // 事实上你可以在同一个 .proto文件,定义多个serive(按我目前理解,这样做可以让不同功能的APIs,组织得更有条理?)
    service Greeter {
      // SayHello接口方法的具体定义
      rpc SayHello (HelloRequest) returns (HelloReply) {}
    }
    
    // 参数HelloRequest的定义
    // 注意,这里的1,并不是给name赋值,而是标记tag,用于序列化和反序列化时的字段匹配
    // 这里的message关键字,可以理解成和class类似
    message HelloRequest {
      string name = 1;
    }
    
    // 返回值类型HelloReply的定义
    message HelloReply {
      string message = 1;
    }
    
    // 如果有其他的数据模型和方法,继续添加就好。是不是挺简单的?
    

    具体的语法介绍: Language Guide (proto3)

    这里需要说明一下, .proto文件,理论上是负责后台的工程师去撰写的。
    可能比较nice一点的同事,会顺便生成swift文件给你,你直接用就可以了。没那么nice的,可能会把 .proto文件丢给你,让你自己玩。

    不过这里的最佳实践,我相信是前后端的工程师一起讨论 .proto文件中API接口的撰写,毕竟前后端开发有差异,很难避免写出一些不符合对方预期的API接口。

    有兴趣的前端小伙伴,也可以试试往helloworld.proto 文件加点方法,改点内容,重新生成代码,更新实现。感受一下后台的开发。

    接口代码的生成

    OK,现在我们有 .proto文件了,假如我们碰到一位没那么nice的后台同事,把 .proto文件直接丢过来,要怎么生成Swift代码?

    gRPC Swift 提供了一个插件/plugin,叫protoc(名字算是起得够烂了?让人很confusing)。详见: protoc gRPC Swift plugin
    (如果没有安装这个插件而运行生成代码的指令,报错command not found: protoc

    插件的安装,如果是macOS(应该没有人用Windows做iOS开发的?),直接在终端执行命令brew install swift-protobuf grpc-swift,用Homebrew来安装。
    更详细的安装说明:Getting the protoc Plugins

    (这里有个坑,一开始我搜到的是gRPC官网的安装教程Protocol Buffer Compiler Installation,这个不是针对Swift的,安装后生成代码的时候会提示protoc-gen-grpc-swift: program not found or is not executable

    装好后,就可以用命令来生成Swift代码了。不过,先看看生成的代码文件长什么样:


    Generated Swift Files

    可以看到,两个文件(命名还有点奇怪):

    • .grpc.swift文件生成的是:API接口方法(对应上面的SayHello方法)、Client(App端用到)、Provider(实现后台时用到——后台工程师用)
    • .pb.swift文件生成的是:模型类(对应上面的HelloRequestHelloReply

    接着,就可以敲命令行生成代码了,个人感觉命令行还是有点复杂,敲敲打打半天,才搞明白,所以画图说明一下(以这个目录下的helloworld.proto文件为例。先cd到仓库的根目录grpc-swift):

    代码生成指令说明

    执行上面命令后,如无意外,就会得到helloworld.grpc.swifthelloworld.pb.swift两个文件。

    可参考: protoc gRPC Swift plugin——不过感觉还没我讲得清楚

    App端请求数据

    终于可以写App端的代码了!!!

    新建一个iOS工程,获取gRPC Swift:可以用Swift Package Manager;可以手动导入;也可以用CocoaPods。详情可以看Github仓库的README

    连接服务器,调用方法,获取数据

    接着可以连接gRPC服务器了并获取数据了:

    let group = PlatformSupport.makeEventLoopGroup(loopCount: 1)
    
    // 创建一个channel
    // 通过host和port,就知道连接那个服务器了
    let channel = try? GRPCChannelPool.with(target: .host("localhost", port: 1234),
                                            transportSecurity: .plaintext,
                                            eventLoopGroup: group)
    
    // 创建Client对象
    // Helloworld_GreeterClient是根据.proto文件生成的代码
    let greeter = Helloworld_GreeterClient(channel: channel!)
    
    // 创建Request对象,作为方法的参数传给服务器
    let request = Helloworld_HelloRequest.with {
        $0.name = "ANTONY"
    }
    
    // 传入参数,调用方法
    let sayHello = greeter.sayHello(request)
    
    do {
        // 拿到方法的返回值(后台返回的数据)
        let response = try sayHello.response.wait()
        print("Greeter received: \(response.message)")
    } catch {
        print("Greeter failed: \(error)")
    }
    

    最后会看到Xcode控制台打印:Greeter received: Hello ANTONY!。这样就完成gRPC「客户端」和「服务器」之间的数据传输了。

    Are you kidding me? 就这几行代码?你写了3000字?

    OK,别着急,后面再写进阶一点的内容。

    相关文章

      网友评论

        本文标题:grpc-swift入门

        本文链接:https://www.haomeiwen.com/subject/imtkmrtx.html