RPC框架gRPC


gRPC

簡介

gRPC是由Google公司開源的高性能RPC框架。

gRPC支持多語言

gRPC原生使用C、Java、Go進行了三種實現,而C語言實現的版本進行封裝后又支持C++、C#、Node、ObjC、 Python、Ruby、PHP等開發語言

gRPC支持多平台

支持的平台包括:Linux、Android、iOS、MacOS、Windows

gRPC的消息協議使用Google自家開源的Protocol Buffers協議機制(proto3)

gRPC的傳輸使用HTTP/2標准,支持雙向流和連接多路復用

grpc

架構

C語言實現的gRPC支持多語言,其架構如下

grpc架構

使用

  1. 使用Protocol Buffers(proto3)的IDL接口定義語言定義接口服務,編寫在文本文件(以.proto為后綴名)中。
  2. 使用protobuf編譯器生成服務器和客戶端使用的stub代碼
  3. 編寫補充服務器和客戶端邏輯代碼

安裝

安裝protobuf編譯器和grpc庫

pip install grpcio-tools

HTTP2

gRPC的傳輸是基於HTTP/2標准協議實現的,我們前面提到gRPC支持雙向流和多路復用,實際就是HTTP/2的特性。而且gRPC有四種接口類型,也是依賴HTTP/2協議建立起來的,所以我們有必要先來了解一下HTTP/2協議。

HTTP/2 是HTTP協議的最新版本,我們通過HTTP/1.x與HTTP/2的對比來認識HTTP/2的特性。

HTTP1

HTTP/1.x 可以說是一個文本協議,可讀性很好,但是效率不高。

HTTP1.1
  • 解析

如果要解析一個完整的 HTTP 請求,首先我們需要能正確的讀出 HTTP header。HTTP header 各個 fields 使用 \r\n 分隔,然后跟 body 之間使用 \r\n\r\n 分隔。解析完 header 之后,我們才能從 header 里面的 content-length 拿到 body 的 size,從而讀取 body。

這套流程其實並不高效,因為我們需要讀取多次,才能將一個完整的 HTTP 請求給解析出來,雖然在代碼實現上面,有很多優化方式,譬如:

一次將一大塊數據讀取到 buffer 里面避免多次 IO read

讀取的時候直接匹配 \r\n 的方式流式解析

但上面的方式對於高性能服務來說,終歸還是會有開銷。其實最主要的問題在於,HTTP/1.x 的協議是 文本協議,是給人看的,對機器不友好,如果要對機器友好,二進制協議才是更好的選擇。

  • 交互模型

HTTP/1.x 另一個問題就在於它的交互模式,一個連接每次只能一問一答,也就是client 發送了 request 之后,必須等到 response,才能繼續發送下一次請求。

這套機制是非常簡單,但會造成網絡連接利用率不高。如果需要同時進行大量的交互,client 需要跟 server 建立多條連接,但連接的建立也是有開銷的,所以為了性能,通常這些連接都是長連接一直保活的,雖然對於 server 來說同時處理百萬連接也沒啥太大的挑戰,但終歸效率不高。

  • 服務器推送

因為 HTTP/1.x 並沒有推送機制。所以通常兩種做法:

  • Long polling 長輪詢方式,也就是直接給 server 掛一個連接,等待一段時間(譬如 1 分鍾),如果 server 有返回或者超時,則再次重新 poll。
  • WebSocket,通過 upgrade 機制顯示的將這條 HTTP 連接變成裸的 TCP,進行雙向交互。

HTTP2

HTTP/2 是一個二進制協議,這也就意味着它的可讀性幾乎為 0,但是效率很高。

  • 二進制分幀層

HTTP/2 所有性能增強的核心在於新的二進制分幀層,它定義了如何封裝 HTTP 消息並在客戶端與服務器之間傳輸。

HTTP2二進制分幀層

這里所謂的“層”,指的是位於套接字接口與應用可見的高級 HTTP API 之間一個經過優化的新編碼機制:HTTP 的語義(包括各種動詞、方法、標頭)都不受影響,不同的是傳輸期間對它們的編碼方式變了。HTTP/1.x 協議以換行符作為純文本的分隔符,而 HTTP/2 將所有傳輸的信息分割為更小的消息和幀,並采用二進制格式對它們編碼。

這樣一來,客戶端和服務器為了相互理解,都必須使用新的二進制編碼機制:HTTP/1.x 客戶端無法理解只支持 HTTP/2 的服務器,反之亦然。不過不要緊,現有的應用不必擔心這些變化,因為客戶端和服務器會替我們完成必要的分幀工作。

  • 數據流、消息、幀

新的二進制分幀機制改變了客戶端與服務器之間交換數據的方式。 為了說明這個過程,我們需要了解 HTTP/2 的三個概念:

- 數據流 Stream:已建立的連接內的雙向字節流,可以承載一條或多條消息。
- 消息 Message :與邏輯請求或響應消息對應的完整的一系列幀。
- 幀 Frame:HTTP/2 通信的最小單位,每個幀都包含幀頭,至少也會標識出當前幀所屬的數據流。

這些概念的關系總結如下:

- 所有通信都在一個 TCP 連接上完成,此連接可以承載任意數量的雙向數據流。
- 每個數據流都有一個唯一的標識符和可選的優先級信息,用於承載雙向消息。
- 每條消息都是一條邏輯 HTTP 消息(例如請求或響應),包含一個或多個幀。
- 幀是最小的通信單位,承載着特定類型的數據,例如 HTTP 標頭、消息負載,等等。 來自不同數據流的幀可以交錯發送,然后再根據每個幀頭的數據流標識符重新組裝。
HTTP2連接 HTTP_2流
  • 請求與響應復用

在 HTTP/1.x 中,如果客戶端要想發起多個並行請求以提升性能,則必須使用多個 TCP 連接。這是 HTTP/1.x 交付模型的直接結果,該模型可以保證每個連接每次只交付一個響應(響應排隊)。更糟糕的是,這種模型也會導致隊首阻塞,從而造成底層 TCP 連接的效率低下。

HTTP/2 中新的二進制分幀層突破了這些限制,實現了完整的請求和響應復用:客戶端和服務器可以將 HTTP 消息分解為互不依賴的幀,然后交錯發送,最后再在另一端把它們重新組裝起來。

HTTP2連接復用

上圖展示了同一個連接內並行的多個數據流。客戶端正在向服務器傳輸一個 DATA 幀(數據流 5),與此同時,服務器正向客戶端交錯發送數據流 1 和數據流 3 的一系列幀。因此,一個連接上同時有三個並行數據流。

HTTP/2 通過 stream 支持了連接的多路復用,提高了連接的利用率。Stream 有很多重要特性:

- 一條連接可以包含多個 streams,多個 streams 發送的數據互相不影響。
- Stream 可以被 client 和 server 單方面或者共享使用。
- Stream 可以被任意一段關閉。
- Stream 會確定好發送 frame 的順序,另一端會按照接受到的順序來處理。
- Stream 用一個唯一 ID 來標識。

這里在說一下 Stream ID,如果是 client 創建的 stream,ID 就是奇數,如果是 server 創建的,ID 就是偶數。ID 0x00 和 0x01 都有特定的使用場景,不會用到。

Stream ID 不可能被重復使用,如果一條連接上面 ID 分配完了,client 會新建一條連接。而 server 則會給 client 發送一個 GOAWAY frame 強制讓 client 新建一條連接。

將 HTTP 消息分解為獨立的幀,交錯發送,然后在另一端重新組裝是 HTTP 2 最重要的一項增強。事實上,這個機制會在整個網絡技術棧中引發一系列連鎖反應,從而帶來巨大的性能提升,讓我們可以:

- 並行交錯地發送多個請求,請求之間互不影響。
- 並行交錯地發送多個響應,響應之間互不干擾。
- 使用一個連接並行發送多個請求和響應。
- 不必再為繞過 HTTP/1.x 限制而做很多工作,例如級聯文件、image sprites 和域名分片。
- 消除不必要的延遲和提高現有網絡容量的利用率,從而減少頁面加載時間。
- 等等…

HTTP/2 中的新二進制分幀層解決了 HTTP/1.x 中存在的隊首阻塞問題,也消除了並行處理和發送請求及響應時對多個連接的依賴。結果,應用速度更快、開發更簡單、部署成本更低。

  • 服務器推送

HTTP/2 新增的另一個強大的新功能是,服務器可以對一個客戶端請求發送多個響應。 換句話說,除了對最初請求的響應外,服務器還可以向客戶端推送額外資源,而無需客戶端明確地請求。

HTTP_2流-1
  • 數據流優先級

因為一條連接允許多個 streams 在上面發送 frame,那么在一些場景下面,我們還是希望 stream 有優先級,方便對端為不同的請求分配不同的資源。譬如對於一個 Web 站點來說,優先加載重要的資源,而對於一些不那么重要的圖片啥的,則使用低的優先級。

我們還可以設置 Stream Dependencies,形成一棵 streams priority tree。假設 Stream A 是 parent,Stream B 和 C 都是它的孩子,B 的 weight 是 4,C 的 weight 是 12,假設現在 A 能分配到所有的資源,那么后面 B 能分配到的資源只有 C 的 1/3。

  • 數據流控制

HTTP/2 也支持流控,如果 sender 端發送數據太快,receiver 端可能因為太忙,或者壓力太大,或者只想給特定的 stream 分配資源,receiver 端就可能不想處理這些數據。譬如,如果 client 給 server 請求了一個視屏,但這時候用戶暫停觀看了,client 就可能告訴 server 別在發送數據了。

雖然 TCP 也有 flow control,但它僅僅只對一個連接有效果。HTTP/2 在一條連接上面會有多個 streams,有時候,我們僅僅只想對一些 stream 進行控制,所以 HTTP/2 單獨提供了流控機制。

接口類型

gRPC有4種接口類型:

- Unary RPC (一元RPC)
- Server Streaming RPC ( 服務器流式RPC)
- Client Streaming RPC ( 客戶端流式RPC)
- Bidirectional Streaming RPC (雙向流式RPC)

對於底層的HTTP/2而言,這些都是數據流Steam,我們所說的接口類型是指進行一次gRPC調用的數據通訊流程(或數據流Stream的生命周期)。

  • Unary RPC

最簡單的RPC類型,客戶端發送單個請求並返回單個響應。

gRPC一元模型
  • Server Streaming RPC

服務器流式RPC類似於我們的簡單示例,只是服務器可以返回多個響應信息。一旦客戶端擁有所有服務器的響應,客戶端就會完成。

grpc服務器流
  • Client Streaming RPC

客戶端流式RPC也類似於我們的簡單示例,只是客戶端向服務器發送請求流而不是單個請求。服務器發送回單個響應。

grpc客戶端流
  • Bidirectional Streaming RPC

在雙向流式RPC中,客戶端和服務器可以按任何順序獨立的讀寫數據流。服務器可以在收到所有的請求信息后再返回響應信息,或者收到一個請求信息返回一個響應信息,或者收到一些請求信息再返回一些請求信息,等等都是可以的。

grpc雙向流

ProtocolBuffers

Protocol Buffers 是一種與語言無關,平台無關的可擴展機制,用於序列化結構化數據。使用Protocol Buffers 可以一次定義結構化的數據,然后可以使用特殊生成的源代碼輕松地在各種數據流中使用各種語言編寫和讀取結構化數據。

現在有許多框架等在使用Protocol Buffers。gRPC也是基於Protocol Buffers。 Protocol Buffers 目前有2和3兩個版本號。

在gRPC中推薦使用proto3版本。

文檔結構

  • 版本

Protocol Buffers文檔的第一行非注釋行,為版本申明,不填寫的話默認為版本2。

syntax = "proto3";
或者
syntax = "proto2";
  • Package包

Protocol Buffers 可以聲明package,來防止命名沖突。 Packages是可選的。

package foo.bar;
message Open { ... }

使用的時候,也要加上命名空間,

message Foo {
  ...
  foo.bar.Open open = 1;
  ...
}

注意:對於Python而言,package會被忽略處理,因為Python中的包是以文件目錄來定義的。

  • 導入

Protocol Buffers 中可以導入其它文件消息等,與Python的import類似。

import “myproject/other_protos.proto”;
  • 注釋
// 單行注釋
/* 多行注釋 */

數據類型

  • 基本數據類型
.proto 說明 Python
double float
float float
int32 使用變長編碼,對負數編碼效率低, 如果你的變量可能是負數,可以使用sint32 int
int64 使用變長編碼,對負數編碼效率低,如果你的變量可能是負數,可以使用sint64 int/long
uint32 使用變長編碼 int/long
uint64 使用變長編碼 int/long
sint32 使用變長編碼,帶符號的int類型,對負數編碼比int32高效 int
sint64 使用變長編碼,帶符號的int類型,對負數編碼比int64高效 int/long
fixed32 4字節編碼, 如果變量經常大於2^{28} 的話,會比uint32高效 int
fixed64 8字節編碼, 如果變量經常大於2^{56} 的話,會比uint64高效 int/long
sfixed32 4字節編碼 int
sfixed64 8字節編碼 int/long
bool bool
string 必須包含utf-8編碼或者7-bit ASCII text str
bytes 任意的字節序列 str
  • 枚舉

在 Proto Buffers 中,我們可以定義枚舉和枚舉類型,

enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
}
Corpus corpus = 4;

枚舉定義在一個消息內部或消息外部都是可以的,如果枚舉是 定義在 message 內部,而其他 message 又想使用,那么可以通過 MessageType.EnumType 的方式引用。

定義枚舉的時候,我們要保證第一個枚舉值必須是0,枚舉值不能重復,除非使用 option allow_alias = true 選項來開啟別名。

enum EnumAllowingAlias {
    option allow_alias = true;
    UNKNOWN = 0;
    STARTED = 1;
    RUNNING = 1;
}

枚舉值的范圍是32-bit integer,但因為枚舉值使用變長編碼,所以不推薦使用負數作為枚舉值,因為這會帶來效率問題。

  • map映射

如果要在數據定義中創建關聯映射,Protocol Buffers提供了一種方便的語法:

map< key_type, value_type> map_field = N ;

其中key_type可以是任何整數或字符串類型。請注意,枚舉不是有效的key_type。value_type可以是除map映射類型外的任何類型。

例如,如果要創建項目映射,其中每條Project消息都與字符串鍵相關聯,則可以像下面這樣定義它:

map<string, Project> projects = 3 ;

map的字段可以是repeated。

序列化后的順序和map迭代器的順序是不確定的,所以你不要期望以固定順序處理map

當為.proto文件產生生成文本格式的時候,map會按照key 的順序排序,數值化的key會按照數值排序。

從序列化中解析或者融合時,如果有重復的key則后一個key不會被使用,當從文本格式中解析map時,如果存在重復的key,則解析可能會失敗。

如果為映射字段提供鍵但沒有值,則字段序列化時的行為取決於語言。在Python中,使用類型的默認值。

消息類型

Protocol Buffers使用message定義消息數據。在Protocol Buffers中使用的數據都是通過message消息數據封裝基本類型數據或其他消息數據,對應Python中的類。

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}
  • 字段編號

消息定義中的每個字段都有唯一的編號。這些字段編號用於以消息二進制格式標識字段,並且在使用消息類型后不應更改。 請注意,1到15范圍內的字段編號需要一個字節進行編碼,包括字段編號和字段類型16到2047范圍內的字段編號占用兩個字節。因此,您應該為非常頻繁出現的消息元素保留數字1到15。請記住為將來可能添加的常用元素留出一些空間。

最小的標識號可以從1開始,最大到2^29 - 1,或 536,870,911。不可以使用其中的[19000-19999]的標識號, Protobuf協議實現中對這些進行了預留。如果非要在.proto文件中使用這些預留標識號,編譯時就會報警。同樣你也不能使用早期保留的標識號。

  • 指定字段規則

消息字段可以是以下之一:

singular:格式良好的消息可以包含該字段中的零個或一個(但不超過一個)。

repeated:此字段可以在格式良好的消息中重復任意次數(包括零)。將保留重復值的順序。對應Python的列表。

message Result {
  string url = 1;
  string title = 2;
  repeated string snippets = 3;
}
  • 添加更多消息類型

可以在單個.proto文件中定義多個消息類型。

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}

message SearchResponse {
 ...
}
  • 保留字段

保留變量不被使用

如果通過完全刪除字段或將其注釋來更新消息類型,則未來用戶可以在對類型進行自己的更新時重用字段編號。如果以后加載相同的舊版本,這可能會導致嚴重問題,包括數據損壞,隱私錯誤等。確保不會發生這種情況的一種方法是指定已刪除字段的字段編號(或名稱)reserved。如果將來的任何用戶嘗試使用這些字段標識符,協議緩沖編譯器將會報錯。

message Foo {
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}
  • 默認值

解析消息時,如果編碼消息不包含特定的單數元素,則解析對象中的相應字段將設置為該字段的默認值。這些默認值是特定於類型的:

- 對於字符串,默認值為空字符串。
- 對於字節,默認值為空字節。
- 對於bools,默認值為false。
- 對於數字類型,默認值為零。
- 對於枚舉,默認值是第一個定義的枚舉值,該值必須為0。
- 對於消息字段,未設置該字段。它的確切值取決於語言。
- 重復字段的默認值為空(通常是相應語言的空列表)。
  • 嵌套類型

你可以在其他消息類型中定義、使用消息類型,在下面的例子中,Result消息就定義在SearchResponse消息內,如:

message SearchResponse {
  message Result {
    string url = 1;
    string title = 2;
    repeated string snippets = 3;
  }
  repeated Result results = 1;
}

如果要在其父消息類型之外重用此消息類型,使用

SearchResponse.Result
  • oneof

如果你的消息中有很多可選字段, 並且同時至多一個字段會被設置, 你可以加強這個行為,使用oneof特性節省內存。

為了在.proto定義oneof字段, 你需要在名字前面加上oneof關鍵字, 比如下面例子的test_oneof:

message SampleMessage {
  oneof test_oneof {
    string name = 4;
    SubMessage sub_message = 9;
  }
}

然后你可以增加oneof字段到 oneof 定義中. 你可以增加任意類型的字段, 但是不能使用repeated 關鍵字。

定義服務

一個service中可定義多個方法。

// Unary RPC
rpc SayHello(HelloRequest) returns (HelloResponse){}

// Server Streaming RPC
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){}

// Client Streaming RPC
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) {}

// Bidirectional Streaming RPC
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){}

Protocol Buffers使用service定義RPC服務。

message HelloRequest {
  string greeting = 1;
}

message HelloResponse {
  string reply = 1;
}

service HelloService {
  rpc SayHello (HelloRequest) returns (HelloResponse) {}
  // 參數為消息類型,異常被http2請求的狀態碼所顯示
}

實現案例

接口

  • 定義

protos/test.proto

syntax = 'proto3';

message Work {
    int32 num1 = 1;
    int32 num2 = 2;

    enum Operation {
        ADD = 0;
        SUBTRACT = 1;
        MULTIPLY = 2;
        DIVIDE = 3;
    }
    Operation op = 3;
}

message Result {
    int32 val = 1;
}

message City {
    string name = 1;
}

message Subject {
    string name = 1;
}

message Delta {
    int32 val = 1;
}

message Sum {
    int32 val = 1;
}

message Number {
    int32 val = 1;
}

message Answer {
    int32 val = 1;
    string desc = 2;
}

service Demo {
    // unary rpc
    // 計算處理
    rpc Calculate(Work) returns (Result) {}

    // server streaming rpc
    // 根據城市獲取傳智開設的學科
    rpc GetSubjects(City) returns (stream Subject) {}

    // client streaming rpc
    // 客戶端發送多個請求數據,服務端返回這些數據的累計和
    rpc Accumulate(stream Delta) returns (Sum) {}

    // bidirectional streaming rpc
    // 猜數字,客戶端向服務端發送多個數據,如果是服務端認可的數據,就返回響應,否則忽略
    rpc GuessNumber(stream Number) returns (stream Answer) {}
}
  • 生成

編譯

python -m grpc_tools.protoc -I. --python_out=.. --grpc_python_out=.. itcast.proto

# -I 表示搜索proto文件中被導入文件的目錄
# --python_out 表示保存生成Python文件的目錄,生成的文件中包含接口定義中的數據類型
# --grpc_python_out 表示保存生成Python文件的目錄,生成的文件中包含接口定義中的服務類型

生成目錄

- protos
	- test.proto
- test_pb2.py
- test_pb2_grpc.py
- client.py
- server.py

服務器

# server.py
import test_pb2_grpc
import test_pb2
import grpc
from concurrent import futures
import time


# 實現被調用的方法的具體代碼
class DemoServicer(test_pb2_grpc.DemoServicer):
    def __init__(self):
        self.city_subjects_db = {
            'beijing': ['python', 'c++', 'go', '產品經理', '測試', '運維', 'java', 'php'],
            'shanghai': ['python', 'c++', 'go', '測試', '運維', 'java', 'php'],
            'wuhan': ['python', '測試', 'java', 'php']
        }
        self.answers = list(range(10))

    def Calculate(self, request, context):
        if request.op == test_pb2.Work.ADD:
            result = request.num1 + request.num2
            return test_pb2.Result(val=result)
        elif request.op == test_pb2.Work.SUBTRACT:
            result = request.num1 - request.num2
            return test_pb2.Result(val=result)
        elif request.op == test_pb2.Work.MULTIPLY:
            result = request.num1 * request.num2
            return test_pb2.Result(val=result)
        elif request.op == test_pb2.Work.DIVIDE:
            if request.num2 == 0:
                # 通過設置響應狀態碼和描述字符串來達到拋出異常的目的
                context.set_code(grpc.StatusCode.INVALID_ARGUMENT)
                context.set_details('cannot divide by 0')
                return test_pb2.Result()
            result = request.num1 // request.num2
            return test_pb2.Result(val=result)
        else:
            context.set_code(grpc.StatusCode.INVALID_ARGUMENT)
            context.set_details('invalid operation')
            return test_pb2.Result()

    def GetSubjects(self, request, context):
        city = request.name
        subjects = self.city_subjects_db.get(city)
        for subject in subjects:
            yield test_pb2.Subject(name=subject)  # 服務端stream

    def Accumulate(self, request_iterator, context):
        sum = 0
        for request in request_iterator:
            sum += request.val
        return test_pb2.Sum(val=sum)

    def GuessNumber(self, request_iterator, context):
        for request in request_iterator:
            if request.val in self.answers:
                yield test_pb2.Answer(val=request.val, desc='bingo')  # 服務端stream


# 開啟服務器,對外提供rpc調用
def serve():
    # 創建服務器對象, 多線程的服務器
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))

    # 注冊實現的服務方法到服務器對象中
    test_pb2_grpc.add_DemoServicer_to_server(DemoServicer(), server)

    # 為服務器設置地址
    server.add_insecure_port('127.0.0.1:8888')

    # 開啟服務
    print('服務器已開啟')
    server.start()

    # 關閉服務
    # 使用 ctrl+c 可以退出服務
    try:
        time.sleep(1000)
    except KeyboardInterrupt:
        server.stop(0)


if __name__ == '__main__':
    serve()

客戶端

# client.py
import grpc
import test_pb2_grpc
import test_pb2
import random


def invoke_calculate(stub):
    work = test_pb2.Work()
    work.num1 = 100
    work.num2 = 20
    work.op = test_pb2.Work.ADD
    result = stub.Calculate(work)
    print('100 + 20 = {}'.format(result.val))

    work.op = test_pb2.Work.SUBTRACT
    result = stub.Calculate(work)
    print('100 - 20 = {}'.format(result.val))

    work.op = test_pb2.Work.MULTIPLY
    result = stub.Calculate(work)
    print('100 * 20 = {}'.format(result.val))

    work.op = test_pb2.Work.DIVIDE
    result = stub.Calculate(work)
    print('100 // 20 = {}'.format(result.val))

    work.num2 = 0
    try:
        result = stub.Calculate(work)
        print('100 // 20 = {}'.format(result.val))
    except grpc.RpcError as e:
        print('{}: {}'.format(e.code(), e.details()))


def invoke_get_subjects(stub):
    city = test_pb2.City(name='beijing')
    subjects = stub.GetSubjects(city)
    for subject in subjects:
        print(subject.name)


def generate_delta():  # 迭代器
    for _ in range(10):
        delta = random.randint(1, 100)
        print(delta)
        yield test_pb2.Delta(val=delta)


def invoke_accumulate(stub):  # 客戶端stream
    delta_iterator = generate_delta()
    sum = stub.Accumulate(delta_iterator)
    print('sum={}'.format(sum.val))


def generate_number():  # 迭代器
    for _ in range(10):
        number = random.randint(1, 20)
        print(number)
        yield test_pb2.Number(val=number)


def invoke_guess_number(stub):  # 客戶端stream
    number_iterator = generate_number()
    answers = stub.GuessNumber(number_iterator)
    for answer in answers:
        print('{}: {}'.format(answer.desc, answer.val))


def run():
    with grpc.insecure_channel('127.0.0.1:8888') as channel:
        # 創建輔助客戶端調用的stub對象
        stub = test_pb2_grpc.DemoStub(channel)

        # invoke_calculate(stub)
        # invoke_get_subjects(stub)
        # invoke_accumulate(stub)
        invoke_guess_number(stub)


if __name__ == '__main__':
    run()


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM