程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

在Python中使用Protobuf

編輯:Python

前言

之前做游戲開發時,游戲服務端與前端采用Protobuf來進行數據傳輸,為了避免被人惡意破解,還對Protobuf產生的數據做了簡單的偏移處理。最近又要用到Protobuf了,所以簡單記錄一下相關內容。

背景知識

我們日常使用最多的數據通信格式應該是JSON,但在一些請求很大的應用上,JSON有2個問題:

  • 1.JSON中有很多業務無關數據,如大括號、中括號等,傳輸時,這些數據浪費帶寬。

  • 2.JSON解碼速度慢,量大時,解碼對服務器資源占用大了一些。

Google遇到了這些問題,然後提出了Protobuf,其核心目的就是解決上述2個問題。

Protobuf選擇二進制編碼的形式,將業務數據編碼到其中,不會有無關數據,甚至連字段名都不會編碼進去,這讓帶寬壓力減小,此外Google自己設計了編碼與解碼算法,以保證資源占用合理且盡量快速的特點。

此外,Protobuf還有一個福作用:人類對編碼後的數據比較難讀。這個副作用從一定程度實現數據保護的效果,一些網站利用這個特性實現了反爬。

因為發展原因,Protobuf分為proto2和proto3兩個版本,兩者是不相兼容的,即使用proto2應用無法與使用proto3的應用通信,本文主要討論proto3,且不會涉及proto2與proto3的比較。

proto文件語法

使用Protobuf通信的第一步,便是定義出proto文件,我們先展示一個簡單的proto文件,如下:

message Person{
    required string name = 1;
    message Info{
        required int32 id = 1;
        repeated string phonenumber = 2; 
    }
    repeated Info info = 2;
}

先思考一下,為啥需要proto文件?為何不能像JSON那樣直接使用呢?

回顧一下Protobuf其中一個優點:只編碼業務相關的數據到待傳輸的二進制流中,以節省帶寬。

Protobuf實現這個特點的原理是:通信雙方手裡都拿著定義好的proto文件。

發送方通過這個proto文件定義發送的內容,這些內容不會需要像JSON那樣,有字段名、有括號這些,接收方收到數據後,再利用手裡的proto文件,按算法直接解析裡面的數據。

我一直強調,JSON傳輸時,會有字段名、有括號,可能有人會比較懵逼,還是舉個例子,如下JSON:

{"name": "ayuliao", "age": 30}

這段JSON在傳輸時,name和age這兩個字段名也會被傳輸,但這兩個字段名沒啥業務意義,主要就是用來獲取數據的,假設這段數據使用Protobuf傳輸,Protobuf就不會將name和age編碼到數據流裡,只會將ayuliao與30編碼進去,接收方獲得數據後,手裡有一份與發送方一樣的proto文件,然後按proto文件中的格式進行解碼。

思索一下,要在沒有字段名的情況下,合理的解碼Protobuf數據,就必須要求傳輸數據是按一定規則組織的,比如ayuliao必須在30前,這樣我會先解碼ayuliao,再解碼30,至此,我們引入proto文件的一個語法要求,message結構裡的分配標識號不可重復。

看到上面的proto文件,有兩個messge結構,外部的message結構是Person,Person中有有name與info兩個屬性,其中name的分配標識號為1,info的分配標識號為2,在同一個message中,分配標識號不可重復,否則protobuf無法正常使用。

此外,從上面proto文件中也可以發現,message中不同屬性可以有不同的限定修飾符,有3種:

  • required:發送方發送的數據中必須包含這個字段的值,接收方接收的數據也必須要能識別該字段,大白話,加上required修飾符,這個字段雙方必須使用,否則報錯。

  • optional:可選字段,發送方可選擇性地發送該字段,接收方如果能夠識別該字段就進行相應解碼處理,如果不能識別,則直接忽略。

  • repeated:可重復字段,發送方每次發送都可以包含多個值,類似於傳遞一個數組。

在日常使用protobuf時,有兩個常見的tips:

1.分配標識號一般會按業務劃分,不同業務間字段不按大小順序緊密排序,如:

message data {
  optional string name = 10001;
  optional int32 age = 10002;
  
  optional string job = 20001;
  optional string hobby = 20002;
}

上述proto,基礎信息(name、age)以1000開頭,其他信息(job、hobby)以2000開頭,這樣後續要添加時,更加清晰,比如要添加性別這個基本信息 :optional string sex = 10003;。

2.很多使用protobuf通信的系統會將字段的限定修飾符設置為optional,這樣系統在升級時,舊版程序無需升級也可以與新程序進行通信,只是對於新字段無法識別而已,這樣可以做到平滑升級。

這裡從網上摘抄了proto文件可以使用的數據類型以及生成到不同編程語言時,在該編程語言中映射的類型,不需記憶,需要時,到此翻一下就好了。

.proto typenotesC ++ typeJava typePython type [2]TypeRuby typeC# typePHP typedouble
doubledoublefloatfloat64floatdoublefloatfloat
floatfloatfloatFLOAT32floatfloatfloatINT32使用可變長度編碼。編碼負數的效率低 - 如果您的字段可能有負值,請改用sint32。INT32INTINTINT32Fixnum or Bignum (as needed)INTIntegerInt64使用可變長度編碼。編碼負數的效率低 - 如果您的字段可能有負值,請改用sint64。Int64longint / long [3]Int64TWINSlongInteger/string[5]UINT32使用可變長度編碼。UINT32int [1]int / long [3]UINT32Fixnum or Bignum (as needed)UINTIntegerUINT64使用可變長度編碼。UINT64Long [1]int / long [3]UINT64TWINSULONGInteger/string[5]SINT32使用可變長度編碼。簽名的int值。這些比常規int32更有效地編碼負數。INT32INTINTINT32Fixnum or Bignum (as needed)INTIntegersint64使用可變長度編碼。簽名的int值。這些比常規int64更有效地編碼負數。Int64longint / long [3]Int64TWINSlongInteger/string[5]fixed32總是四個字節。如果值通常大於2 28,則比uint32更有效。UINT32int [1]int / long [3]UINT32Fixnum or Bignum (as needed)UINTIntegerfixed64總是八個字節。如果值通常大於2 56,則比uint64更有效。UINT64Long [1]int / long [3]UINT64TWINSULONGInteger/string[5]sfixed32總是四個字節。INT32INTINTINT32Fixnum or Bignum (as needed)INTIntegersfixed64總是八個字節。Int64longint / long [3]Int64TWINSlongInteger/string[5]Boolean
BooleanBooleanBooleanBooleanTrueClass / FalseClassBooleanBooleanstring字符串必須始終包含UTF-8編碼或7位ASCII文本。stringstringstr / unicode[4]stringString (UTF-8)stringstringbyte可以包含任意字節序列。stringByte stringStrait[]byteString (ASCII-8BIT)Byte stringstring

Protobuf在Python中的基本使用

當下,微服務架構比較流行,Python中在這塊常用的通信技術便是Protobuf+gRPC,本文先簡單介紹Protobuf,後面再以本文為基礎,寫一篇Protobuf+gRPC的使用例子。

首先,我們需要下載protoc編譯器,通過protoc,我們可以將定義好的proto文件轉成相應編程語言的形式。

下載地址:https://github.com/protocolbuffers/protobuf/releases,如果你跟我一樣,使用的windows 64位的系統,那麼下載win64版本的protoc則可。

隨後,我們定義一個簡單的proto文件,名為demo.proto,內容如下:

syntax = "proto3";
message Person {
    string name = 1;
    int32 id = 2;
    string email = 3;
    string phone = 4;
}

通過protoc將demo.proto編譯成Python文件,命令如下(protoc路徑替換成自己的路徑則可):

& "C:\Users\admin\Downloads\protoc-21.1-win64\bin\protoc.exe" --python_out=. demo.proto

--python_out用於指定生成Python文件要存放的路徑,隨後緊接demo.proto文件路徑(注意有空格做間隔)。

運行命令後,會生成名為demo_pd2.py的文件。

然後我們導入demo_pd2文件,使用其中的Person類便可以實現Protobuf的編碼與解碼。

通過一段簡單的代碼演示一下:

from asyncore import read
import demo_pb2
R = 'read'
W = 'write'
def fwrb(filepath, opt, data=None):
    if opt == R:
        with open(filepath, 'rb') as f:
            return f.read()
    elif opt == W:
        with open(filepath, 'wb') as f:
            f.write(data)
filepath = './person_protobuf_data'
person = demo_pb2.Person()
person.name = "ayuliao"
person.id = 6
person.email = "[email protected]"
person.phone = "13229483229"
person_protobuf_data = person.SerializeToString()
print(f'person_protobuf_data: {person_protobuf_data}')
fwrb(filepath, W, person_protobuf_data)
data_from_file = fwrb(filepath, R)
parse_person  = demo_pb2.Person()
# 沒有數據
print(f'parse_person1 : {parse_person}')
# ParseFromString函數返回解析數據的長度
parse_data_len = parse_person.ParseFromString(data_from_file)
# 解析後,parse_person對象被填充了數據
print(f'parse_person2 : {parse_person}')
print(f'parse_data_len: {parse_data_len}')
print(f'data_from_file lenght: {len(data_from_file)}')

上述代碼中,需要注意的是,ParseFromString函數不會直接返回解析後的結果,而是將結果直接填充到調用它的parse_person對象中。

結尾

本文討論了protobuf基礎知識,protobuf在通信質量要求比較高的應用中會比較常見,但我個人的觀點依舊是:如無必要勿增實體,如果應用沒有到達需要使用protobuf的地步,還是優先使用HTTP吧。

最後,對於Python入門、Python自動化感興趣的同學,可以入手《Python自動化辦公》這本書籍,目前5折售賣中喲~


  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved