Python gRPC 入門

gRPC 一開始由 google 開發,是一款語言中立、平臺中立、開源的遠程過程調用(RPC)系統。 本文經過一個簡單的 Hello World 例子來向您介紹 gRPC 。html

gRPC 是什麼?

gRPC 也是基於如下理念:定義一個服務,指定其可以被遠程調用的方法(包含參數和返回類型)。在服務端實現這個接口,並運行一個 gRPC 服務器來處理客戶端調用。在客戶端擁有一個存根可以像服務端同樣的方法。java

在 gRPC 裏客戶端應用能夠像調用本地對象同樣直接調用另外一臺不一樣的機器上服務端應用的方法,使得咱們可以更容易地建立分佈式應用和服務。python

gPRC

gRPC 客戶端和服務端能夠在多種環境中運行和交互,而且能夠用任何 gRPC 支持的語言來編寫。linux

gRPC 支持 C++ Java Python Go Ruby C# Node.js PHP Dart 等語言git

gRPC 默認使用 protocol buffers,這是 Google 開源的一種輕便高效的結構化數據存儲格式,能夠用於結構化數據串行化,或者說序列化。它很適合作數據存儲或 RPC 數據交換格式。github

安裝 Google Protocol Buffer

方法一(建議使用)

參考文檔:gRPC Python Quickstart小程序

1. 安裝 gRPC
python -m pip install grpcio
# 或者
sudo python -m pip install grpcio

# 在 El Capitan OSX 系統下可能會看到如下報錯

$ OSError: [Errno 1] Operation not permitted: '/tmp/pip-qwTLbI-uninstall/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/six-1.4.1-py2.7.egg-info'

# 可使用如下命令
python -m pip install grpcio --ignore-installed
複製代碼
2. 安裝 gRPC tools

Python gPRC tools 包含 protocol buffer 編譯器和用於從 .proto 文件生成服務端和客戶端代碼的插件設計模式

python -m pip install grpcio-tools
複製代碼

方法二:

在 github 頁面protobuf Buffers能夠下載二進制源碼,下載後執行如下命令安裝:api

tar -zxvf protobuf-all-3.5.1.tar
cd protobuf-all-3.5.1
./configure
make
make install

>> protoc --version
libprotoc 3.5.1  # 安裝成功
複製代碼

由於是要使用 Protobuf + Python 測試,因此還要安裝 python運行環境。protobuf Buffers python 文檔bash

# 打開 python 目錄
cd python
python setup.py install  # 安裝 python 運行環境
複製代碼

Protobuf 基本使用

定義一個消息類型

先來看一個很是簡單的例子。假設你想定義一個「搜索請求」的消息格式,每個請求含有一個查詢字符串、你感興趣的查詢結果所在的頁數,以及每一頁多少條查詢結果。能夠採用以下的方式來定義消息類型的.proto文件了:

syntax = "proto3";  // 聲明使用 proto3 語法

message SearchRequest {
  string query = 1;  // 每一個字段都要指定數據類型
  int32 page_number = 2; // 這裏的數字2 是標識符,最小的標識號能夠從1開始,最大到2^29 - 1, or 536,870,911。不可使用其中的[19000-19999]
  int32 result_per_page = 3; // 這裏是註釋,使用 //
}
複製代碼
  • 文章的第一行指定了你正在使用 proto3 語法:若是不指定,編譯器會使用 proto2。這個指定語法必須是文件的非空非註釋的第一行
  • SearchRequest消息格式有三個字段,在消息中承載的數據分別對應於每個字段。其中每一個字段都有一個名字和一種類型。
  • 向.proto文件添加註釋,可使用C/C++/java風格的雙斜槓(//) 語法格式。
  • 在消息體中,每一個字段都有惟一的一個數字標識符。這些標識符用來在消息的二進制格式中識別各個字段,一旦開始使用就不能再改變。

[1,15]以內的標識號在編碼的時候會佔用一個字節。[16,2047]以內的標識號則佔用2個字節。因此應該爲那些頻繁出現的消息元素保留 [1,15]以內的標識號。切記:要爲未來有可能添加的、頻繁出現的標識號預留一些標識號。

指定字段規則

所指定的消息字段修飾符必須是以下之一:

  • singular:一個格式良好的消息應該有0個或者1個這種字段(可是不能超過1個)。

  • repeated:在一個格式良好的消息中,這種字段能夠重複任意屢次(包括0次)。重複的值的順序會被保留。

    在proto3中,repeated的標量域默認狀況蝦使用packed。

    message Test4 {
      repeated int32 d = 4 [packed=true];
    }
    複製代碼

數值類型

一個標量消息字段能夠含有一個以下的類型——該表格展現了定義於.proto文件中的類型,以及與之對應的、在自動生成的訪問類中定義的類型:

.proto Type Notes C++ Type Java Type Python Type[2] Go Type Ruby Type
double double double float float64 Float
float float float float float32 Float
int32 使用變長編碼,對於負值的效率很低,若是你的域有可能有負值,請使用sint64替代 int32 int int int32 Fixnum 或者 Bignum(根據須要)
uint32 使用變長編碼 uint32 int int/long uint32 Fixnum 或者 Bignum(根據須要)
uint64 使用變長編碼 uint64 long int/long uint64 Bignum
sint32 使用變長編碼,這些編碼在負值時比int32高效的多 int32 int int int32 Fixnum 或者 Bignum(根據須要)
sint64 使用變長編碼,有符號的整型值。編碼時比一般的int64高效。 int64 long int/long int64 Bignum
fixed32 老是4個字節,若是數值老是比老是比228大的話,這個類型會比uint32高效。 uint32 int int uint32 Fixnum 或者 Bignum(根據須要)
fixed64 老是8個字節,若是數值老是比老是比256大的話,這個類型會比uint64高效。 uint64 long int/long uint64 Bignum
sfixed32 老是4個字節 int32 int int int32 Fixnum 或者 Bignum(根據須要)
sfixed64 老是8個字節 int64 long int/long int64 Bignum
bool bool boolean bool bool TrueClass/FalseClass
string 一個字符串必須是UTF-8編碼或者7-bit ASCII編碼的文本。 string String str/unicode string String (UTF-8)
bytes 可能包含任意順序的字節數據。 string ByteString str []byte String (ASCII-8BIT)

默認值

當一個消息被解析的時候,若是被編碼的信息不包含一個特定的singular元素,被解析的對象鎖對應的域被設置位一個默認值,對於不一樣類型指定以下:

  • 對於strings,默認是一個空string

  • 對於bytes,默認是一個空的bytes

  • 對於bools,默認是false

  • 對於數值類型,默認是0

  • 對於枚舉,默認是第一個定義的枚舉值,必須爲0;

  • 對於消息類型(message),域沒有被設置,確切的消息是根據語言肯定的,詳見generated code guide

    對於可重複域的默認值是空(一般狀況下是對應語言中空列表)。

嵌套類型

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

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

在 message SearchResponse 中,定義了嵌套消息 Result,並用來定義SearchResponse消息中的results域。

Protobuf 文件編譯

從.proto文件生成了什麼?

當用protocol buffer編譯器來運行.proto文件時,編譯器將生成所選擇語言的代碼,這些代碼能夠操做在.proto文件中定義的消息類型,包括獲取、設置字段值,將消息序列化到一個輸出流中,以及從一個輸入流中解析消息。

  • 對C++來講,編譯器會爲每一個.proto文件生成一個.h文件和一個.cc文件,.proto文件中的每個消息有一個對應的類。
  • 對Java來講,編譯器爲每個消息類型生成了一個.java文件,以及一個特殊的Builder類(該類是用來建立消息類接口的)。
  • 對Python來講,有點不太同樣——Python編譯器爲.proto文件中的每一個消息類型生成一個含有靜態描述符的模塊,,該模塊與一個元類(metaclass)在運行時(runtime)被用來建立所需的Python數據訪問類。
  • 對go來講,編譯器會位每一個消息類型生成了一個.pd.go文件。
  • 對於Ruby來講,編譯器會爲每一個消息類型生成了一個.rb文件。
  • javaNano來講,編譯器輸出相似域java可是沒有Builder類
  • 對於Objective-C來講,編譯器會爲每一個消息類型生成了一個pbobjc.h文件和pbobjcm文件,.proto文件中的每個消息有一個對應的類。
  • 對於C#來講,編譯器會爲每一個消息類型生成了一個.cs文件,.proto文件中的每個消息有一個對應的類。

Python gRPC 示例

編譯

這裏咱們用Python 編譯一下,看獲得什麼:

// 文件名 hello.proto
syntax = "proto3";

package hello;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}
複製代碼

使用如下命令編譯:

python -m grpc_tools.protoc -I./ --python_out=. --grpc_python_out=. ./hello.proto
複製代碼

生成了兩個文件:

  • hello_pb2.py 此文件包含生成的 request(HelloRequest) 和 response(HelloReply) 類。
  • hello_pb2_grpc.py 此文件包含生成的 客戶端(GreeterStub)和服務端(GreeterServicer)的類。

源碼地址爲github.com/grpc/grpc/b…

雖然如今已經生成了服務端和客戶端代碼,可是咱們還須要手動實現以及調用的方法。

建立服務端代碼

建立和運行 Greeter 服務能夠分爲兩個部分:

  • 實現咱們服務定義的生成的服務接口:作咱們的服務的實際的「工做」的函數。

  • 運行一個 gRPC 服務器,監聽來自客戶端的請求並傳輸服務的響應。

在當前目錄,打開文件 greeter_server.py,實現一個新的函數:

from concurrent import futures
import time

import grpc

import hello_pb2
import hello_pb2_grpc

_ONE_DAY_IN_SECONDS = 60 * 60 * 24


class Greeter(hello_pb2_grpc.GreeterServicer):
	# 工做函數
    def SayHello(self, request, context):
        return hello_pb2.HelloReply(message='Hello, %s!' % request.name)


def serve():
    # gRPC 服務器
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    hello_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
    server.add_insecure_port('[::]:50051')
    server.start()  # start() 不會阻塞,若是運行時你的代碼沒有其它的事情可作,你可能須要循環等待。
    try:
        while True:
            time.sleep(_ONE_DAY_IN_SECONDS)
    except KeyboardInterrupt:
        server.stop(0)

if __name__ == '__main__':
    serve()
複製代碼

更新客戶端代碼

在當前目錄,打開文件 greeter_client.py,實現一個新的函數:

from __future__ import print_function

import grpc

import hello_pb2
import hello_pb2_grpc


def run():
    channel = grpc.insecure_channel('localhost:50051')
    stub = hello_pb2_grpc.GreeterStub(channel)
    response = stub.SayHello(hello_pb2.HelloRequest(name='goodspeed'))
    print("Greeter client received: " + response.message)


if __name__ == '__main__':
    run()
複製代碼

對於返回單個應答的 RPC 方法("response-unary" 方法),gRPC Python 同時支持同步(阻塞)和異步(非阻塞)的控制流語義。對於應答流式 RPC 方法,調用會當即返回一個應答值的迭代器。調用迭代器的 next() 方法會阻塞,直到從迭代器產生的應答變得可用。

運行代碼

  1. 首先運行服務端代碼
python greeter_server.py
複製代碼
  1. 而後運行客戶端代碼
python greeter_client.py
# output

Greeter client received: Hello, goodspeed!
複製代碼

源碼地址: https://github.com/grpc/grpc/tree/master/examples/python

參考連接


最後,感謝女友支持和包容,比❤️

也能夠在公號輸入如下關鍵字獲取歷史文章:公號&小程序 | 設計模式 | 併發&協程

相關文章
相關標籤/搜索