grpc-swift入門

先戴個頭盔,以下所有論述不保證正確性,請自行甄別服用。

不想看前面的屁話,要直接上代碼的,請?zhí)健竔OS App端如何實(shí)現(xiàn)和RPC服務(wù)器通信」章節(jié)

什么是RPC、gRPC、grpc-swift

要搞清楚什么是grpc-swift

就要先搞清楚什么是gRPC,

要搞清楚什么是gRPC,

就要先搞清楚什么是RPC。

What is RPC

RPC,是Remote procedure call的簡稱,翻譯過來——「遠(yuǎn)程過程調(diào)用」。

請講人話?OK,舉個??,假如我要轉(zhuǎn)1個比特幣給你(事實(shí)上我并沒有1個比特幣,不嫌棄波卡幣/Polkadot的話,可以轉(zhuǎn)給你——最近跌好慘??),然后我就通過RPC這種「傳輸方式」轉(zhuǎn)給你。

本質(zhì)上這是一個傳輸數(shù)據(jù)的過程。所以RPC,就簡單理解成「一種傳輸數(shù)據(jù)的方式」。

對比地看,我們還有另一種更常用的方式:HTTP+REST。(不知道啥玩意兒?不要緊。就理解成是互聯(lián)網(wǎng)上另一種傳輸數(shù)據(jù)的方式就好了。)

簡單來說,HTTP+REST方式,聚焦在數(shù)據(jù)data上:發(fā)送一個請求request,然后返回數(shù)據(jù)response。

而RPC,聚焦在「方法」上——直接調(diào)用一個「方法/函數(shù)/command」——只是對比于在同一個軟件內(nèi)部調(diào)用方法,RPC中調(diào)用有點(diǎn)不太一樣,它是從電腦A,直接調(diào)用電腦B中的某個「方法」,是一個遠(yuǎn)程調(diào)用(Remote Call)。

然后這個「方法」和我們常見的「方法」一樣,會有參數(shù)、返回值。要傳輸?shù)臄?shù)據(jù),就放在參數(shù)、返回值里面,最終實(shí)現(xiàn)數(shù)據(jù)的傳輸。如下圖:


RPC的數(shù)據(jù)傳輸過程

截圖出處: Comparing web API types: SOAP, REST, GraphQL and RPC

What is gRPC

OK,RPC是一種傳輸數(shù)據(jù)的方式,那gRPC又是什么?

聰明的你意識到了,這里多了一個「g」。不過,大多數(shù)人第一反應(yīng),應(yīng)該不會認(rèn)為「g」表示的是「Google」——畢竟百度是眾多宇宙中第一好用的搜索引擎。

事實(shí)上「g」表示的,正是Google(起碼大多數(shù)人是這樣認(rèn)為的。關(guān)于「g」的其他含義,下面再作補(bǔ)充),gRPC是Google主導(dǎo)的對RPC的具體實(shí)現(xiàn)。賣點(diǎn):高性能、開源、通用(支持很多語言: Supported languages)。

其中,Protocol Buffers,有需要認(rèn)識一下的??梢园阉惐瘸蒟ML、JSON,但是Protocol Buffers的數(shù)據(jù)包更小、速度更快、實(shí)現(xiàn)更簡單。

你可能會猜到,RPC還有XML-RPC,JSON-RPC這些其他的實(shí)現(xiàn)。而gRPC,更準(zhǔn)確的對標(biāo),我覺得應(yīng)該叫「Protocol Buffers-RPC」~

再回到「g」,事實(shí)上,把它理解成「Google」沒有錯,不過,經(jīng)常沒事找抽的工程師,對「g」是有另一番調(diào)侃的,詳情: GRPC Core: g_stands_for

為了讓大家有更直觀的理解,下面把互聯(lián)網(wǎng)數(shù)據(jù)傳輸?shù)囊徊糠职l(fā)展史展現(xiàn)如下:


history

What is grpc-swift

OK,我們有g(shù)RPC了,是不是可以開始寫iOS端的App,從「RPC后臺」拿一些數(shù)據(jù)了?
上面提到,gRPC支持多種語言,其中就有Objective-C(如果暫時不理解「支持」的含義,后面會繼續(xù)解釋)。

但是,現(xiàn)在大家都用Swift開發(fā)iOS App,所以就有了grpc-swift了。

所以,總括來看,他們的關(guān)系如下圖:
(對了,題外話:Bitcoin用的是JSON-RPC

RPC關(guān)系圖

為什么要用gRPC

OK,上面講了各種概念。那么,為什么要用gRPC呢?
(注意,我這里的問題是「為什么要用gRPC」,而不是「為什么要用RPC」)

天下武功,唯快不破

這是一條受用千年的古訓(xùn)。

gRPC用了上面提到過的Protocol Buffers,在數(shù)據(jù)傳輸過程,數(shù)據(jù)包/payload是基于二進(jìn)制/binary的。
所以,數(shù)據(jù)包的size,比JSON小很多(想象一個例子:一個55bytes,一個20bytes)。
另外,二進(jìn)制形式的數(shù)據(jù)包,CPU可以更高效地進(jìn)行「序列化」和「反序列化」。

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

當(dāng)然,gRPC也不是萬金油,也有自己的劣勢:瀏覽器支持有限、二進(jìn)制格式對人類不友好等等。

更多的優(yōu)劣分析,可以參考:
What is gRPC: Main Concepts, Pros and Cons, Use Cases

至于你要不要用gRPC,請自行斟酌——跟我好像沒有關(guān)系。

iOS App端如何實(shí)現(xiàn)和RPC服務(wù)器通信

好了,上面講了一大堆屁話,終于到正題了。

要寫一個iOS的App,和gRPC后臺通信。首先,我們要有一個gRPC后臺——好一句廢話。

服務(wù)端跑起來

沒有后臺經(jīng)驗(yàn)的小伙伴不需要菊花一緊,你只需要在你的終端敲入swift run HelloWorldServer這行命令,然后再輕輕敲一下回車鍵,官方GitHub的HelloWorld后臺,就會神奇般地跑起來了:

  • grpc-swift項(xiàng)目clon下來
  • cd到項(xiàng)目根目錄
  • 打開終端/Termanil,執(zhí)行swift run HelloWorldServer命令(成功后會看到終端的打印:server started on port 1234

這樣,RPC后臺就跑起來了。從這個后臺能拿到什么數(shù)據(jù)?
首先這個后臺有一個方法sayHello()可供(App)客戶端調(diào)用,然后,假如你調(diào)用這個方法并傳入Antony作為方法的參數(shù)(準(zhǔn)確說應(yīng)該是一個Rquest對象),他會返回字符串Hello Antony!(準(zhǔn)確說應(yīng)該是一個Response對象)。如果不傳參數(shù),默認(rèn)返回Hello stranger!。
有沒有很厲害?!

如果你迫不及待,沒寫好App,就想調(diào)sayHello()方法試試看??梢裕?/p>

  • 再打開一個終端
  • cd到項(xiàng)目根目錄
  • 執(zhí)行swift run HelloWorldClient命令(成功后會看到打?。?code>Greeter received: Hello stranger!)
    表示我們的客戶端(是一個命令行工具)調(diào)用了sayHello()并收到了后臺服務(wù)端的數(shù)據(jù)了!
    RPC后臺跑起來!

.proto文件的撰寫

在寫App之前,還想介紹一下 .proto文件。

上面介紹了,我們客戶端這邊,調(diào)用了sayHello()方法,同樣地,到時候我們的App,也會調(diào)用這個方法,獲取數(shù)據(jù),而這個方法自然是用Swift語言寫的,我們需要自己寫這個方法嗎?答案是不需要。那這個方法從哪里來?

答案就是接下來介紹的 .proto文件。我們利用Protocol Buffers這個接口描述語言,來把我們的數(shù)據(jù)傳輸過程中的「數(shù)據(jù)模型」和「方法」在 .proto文件定義好,然后再通過相關(guān)指令,生成你的客戶端需要的代碼。比如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
// 事實(shí)上你可以在同一個 .proto文件,定義多個serive(按我目前理解,這樣做可以讓不同功能的APIs,組織得更有條理?)
service Greeter {
  // SayHello接口方法的具體定義
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// 參數(shù)HelloRequest的定義
// 注意,這里的1,并不是給name賦值,而是標(biāo)記tag,用于序列化和反序列化時的字段匹配
// 這里的message關(guān)鍵字,可以理解成和class類似
message HelloRequest {
  string name = 1;
}

// 返回值類型HelloReply的定義
message HelloReply {
  string message = 1;
}

// 如果有其他的數(shù)據(jù)模型和方法,繼續(xù)添加就好。是不是挺簡單的?

具體的語法介紹: Language Guide (proto3)

這里需要說明一下, .proto文件,理論上是負(fù)責(zé)后臺的工程師去撰寫的。
可能比較nice一點(diǎn)的同事,會順便生成swift文件給你,你直接用就可以了。沒那么nice的,可能會把 .proto文件丟給你,讓你自己玩。

不過這里的最佳實(shí)踐,我相信是前后端的工程師一起討論 .proto文件中API接口的撰寫,畢竟前后端開發(fā)有差異,很難避免寫出一些不符合對方預(yù)期的API接口。

有興趣的前端小伙伴,也可以試試往helloworld.proto 文件加點(diǎn)方法,改點(diǎn)內(nèi)容,重新生成代碼,更新實(shí)現(xiàn)。感受一下后臺的開發(fā)。

接口代碼的生成

OK,現(xiàn)在我們有 .proto文件了,假如我們碰到一位沒那么nice的后臺同事,把 .proto文件直接丟過來,要怎么生成Swift代碼?

gRPC Swift 提供了一個插件/plugin,叫protoc(名字算是起得夠爛了?讓人很confusing)。詳見: protoc gRPC Swift plugin
(如果沒有安裝這個插件而運(yùn)行生成代碼的指令,報錯command not found: protoc

插件的安裝,如果是macOS(應(yīng)該沒有人用Windows做iOS開發(fā)的?),直接在終端執(zhí)行命令brew install swift-protobuf grpc-swift,用Homebrew來安裝。
更詳細(xì)的安裝說明:Getting the protoc Plugins

(這里有個坑,一開始我搜到的是gRPC官網(wǎng)的安裝教程Protocol Buffer Compiler Installation,這個不是針對Swift的,安裝后生成代碼的時候會提示protoc-gen-grpc-swift: program not found or is not executable

裝好后,就可以用命令來生成Swift代碼了。不過,先看看生成的代碼文件長什么樣:


Generated Swift Files

可以看到,兩個文件(命名還有點(diǎn)奇怪):

  • .grpc.swift文件生成的是:API接口方法(對應(yīng)上面的SayHello方法)、Client(App端用到)、Provider(實(shí)現(xiàn)后臺時用到——后臺工程師用)
  • .pb.swift文件生成的是:模型類(對應(yīng)上面的HelloRequestHelloReply

接著,就可以敲命令行生成代碼了,個人感覺命令行還是有點(diǎn)復(fù)雜,敲敲打打半天,才搞明白,所以畫圖說明一下(以這個目錄下的helloworld.proto文件為例。先cd到倉庫的根目錄grpc-swift):

代碼生成指令說明

執(zhí)行上面命令后,如無意外,就會得到helloworld.grpc.swifthelloworld.pb.swift兩個文件。

可參考: protoc gRPC Swift plugin——不過感覺還沒我講得清楚

App端請求數(shù)據(jù)

終于可以寫App端的代碼了!?。?/p>

新建一個iOS工程,獲取gRPC Swift:可以用Swift Package Manager;可以手動導(dǎo)入;也可以用CocoaPods。詳情可以看Github倉庫的README。

連接服務(wù)器,調(diào)用方法,獲取數(shù)據(jù)

接著可以連接gRPC服務(wù)器了并獲取數(shù)據(jù)了:

let group = PlatformSupport.makeEventLoopGroup(loopCount: 1)

// 創(chuàng)建一個channel
// 通過host和port,就知道連接那個服務(wù)器了
let channel = try? GRPCChannelPool.with(target: .host("localhost", port: 1234),
                                        transportSecurity: .plaintext,
                                        eventLoopGroup: group)

// 創(chuàng)建Client對象
// Helloworld_GreeterClient是根據(jù).proto文件生成的代碼
let greeter = Helloworld_GreeterClient(channel: channel!)

// 創(chuàng)建Request對象,作為方法的參數(shù)傳給服務(wù)器
let request = Helloworld_HelloRequest.with {
    $0.name = "ANTONY"
}

// 傳入?yún)?shù),調(diào)用方法
let sayHello = greeter.sayHello(request)

do {
    // 拿到方法的返回值(后臺返回的數(shù)據(jù))
    let response = try sayHello.response.wait()
    print("Greeter received: \(response.message)")
} catch {
    print("Greeter failed: \(error)")
}

最后會看到Xcode控制臺打?。?code>Greeter received: Hello ANTONY!。這樣就完成gRPC「客戶端」和「服務(wù)器」之間的數(shù)據(jù)傳輸了。

Are you kidding me? 就這幾行代碼?你寫了3000字?

OK,別著急,后面再寫進(jìn)階一點(diǎn)的內(nèi)容。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容