上善若水
          In general the OO style is to use a lot of little objects with a lot of little methods that give us a lot of plug points for overriding and variation. To do is to be -Nietzsche, To bei is to do -Kant, Do be do be do -Sinatra
          posts - 146,comments - 147,trackbacks - 0

          Descriptor框架

          對非optimize_forLITE_RUNTIMEproto文件,protobuf編譯器會在編譯出的Java代碼文件末尾添加一個FileDescriptor靜態字段以描述該proto文件定義時的所有元數據信息、為每個message對象定義一個Descriptor靜態字段以描述該message定義時的元數據信息、為每個message對象定義一個FieldAccessorTable靜態字段用于使用反射讀取/設置某個字段的值等(以提供GeneratedMessage中方法的反射實現):    
          private static Descriptor inter-nal_static_levin_protobuf_Result_descriptor;
          private static FieldAccessorTable inter-nal_static_levin_protobuf_Result_fieldAccessorTable;
          private static Descriptor inter-nal_static_levin_protobuf_SearchResponse_descriptor;
          private static FieldAccessorTable inter-nal_static_levin_protobuf_SearchResponse_fieldAccessorTable;
          private static FileDescriptor descriptor;

          protobuf中存在多種類型的元數據描述類:

          1.     FileDescriptor:對一個proto文件的描述,它包含文件名、包名、選項(如java_packagejava_outer_classname等)、文件中定義的所有message、文件中定義的所有enum、文件中定義的所有service、文件中所有定義的extension、文件中定義的所有依賴文件(import)等。在FileDescriptor中還存在一個DescriptorPool實例,它保存了所有的dependencies(依賴文件的FileDescriptor)nameGenericDescriptor的映射、字段到FieldDescriptor的映射、枚舉項到EnumValueDescriptor的映射,從而可以從該DescriptorPool中查找相關的信息,因而可以通過名字從FileDescriptor中查找MessageEnumServiceExtensions等。

          2.   Descriptor:對一個message定義的描述,它包含該message定義的名字、所有字段、內嵌message、內嵌enum、關聯的FileDescriptor等。可以使用字段名或字段號查找FieldDescriptor

          3.   FieldDescriptor:對一個字段或擴展字段定義的描述,它包含字段名、字段號、字段類型、字段定義(required/optional/repeated/packed)、默認值、是否是擴展字段以及和它關聯的Descriptor/FileDescriptor等。

          4.   EnumDescriptor:對一個enum定義的描述,它包含enum名、全名、和它關聯的FileDescriptor。可以使用枚舉項或枚舉值查找EnumValueDescriptor

          5.   EnumValueDescriptor:對一個枚舉項定義的描述,它包含枚舉名、枚舉值、關聯的EnumDescriptor/FileDescriptor等。

          6.   ServiceDescriptor:對一個service定義的描述,它包含service名、全名、關聯的FileDescriptor等。

          7.   MethodDescriptor:對一個在service中的method的描述,它包含method名、全名、參數類型、返回類型、關聯的FileDescriptor/ServiceDescriptor等。

          最后,protobuf編譯生成的代碼末尾還有一個descriptorData字符串數組,它是序列化后的FileDescriptorProto數據,在靜態初始化塊中可以調用FileDescriptor.internalBuildGeneratedFileFrom()方法構造整個FileDescriptor實例,在完成FileDescriptor的構造后,還會回調傳入的InternalDescriptorAssigner實例以初始化其他的靜態字段,如以上提到的所有的靜態字段。

          protobufDescriptor的類圖:


          Message、MessageLite框架

          序列化和反序列化是protobuf最基礎的框架,它使用MessageLite/Message接口來抽象一個可序列化的實例,并且使用Builder從字節數組或輸入字節流中構建MessageLite/Message實例,MessageLiteMessage內部都定義了自己的Builder類,他們個字繼承自MessageLiteOrBuilder以及MessageOrBuiler,它們定義了MessageLite/Message和它們各自Builder類的共同接口。

          MessageLiteOrBuilder接口只定義了MessageLiteMessageLite.Builder兩個接口共有的兩個方法:getDefaultInstanceForType()方法獲取一個當前還未初始化的當前Message實例(沒有字段被賦值,因而所有字段返回默認值,對repeat字段返回空,在當前protobuf 2.5.0的實現中,它返回的是一個單例,和每個生成的靜態方法getDefaultInstance()返回相同的實例)isInitialized()方法用來判斷是否所有required字段已經被賦值。MessageLite接口中定義了兩個writeTo()方法分別將當前實例序列化并寫入輸出字節流中,而另一個writeDelimitedTo()方法則在寫入之前將當前實例的總長度寫入輸出字節流中(以可變長32Int編碼方式),從而可以同時向一個輸出字節流中寫入多個Message實例;MessageLite中還定義了獲取當前MessageLite在序列化成字節流后的總字節數的方法getSerializedSize(),兩個直接返回字節數組的toByteArray()/toByteString()方法,以及獲取它的Parser實例(getParserForType())和返回它的Builder實例(toBuilder()-創建一個新的Builder實例/newBuilderForType()-用當前MessageLite類初始化一個新的Builder實例并返回)方法。其中Builder接口用于從字節流或字節數組中解析并構造MessageLite對象(各種版本的mergeFrom()方法,如果發送端寫入了MessageLite字節長度,則使用mergeDelimitedFrom()方法),最后Builder使用build()方法構造MessageLite對象,此時如果有required字段還未被設置,會拋出UninitializedMessageException,為了避免拋出異常,可以使用buildPartial()方法;另外Builder還定義了clone()clear()方法;在生成的每個Message對象中都定義了一個newBuilder()靜態方法,一般使用該靜態方法初始化一個Builder實例。Parser接口也定義了各個版本的parseFrom()/parsePartialFrom()/parseDelimitedFrom()/parsePartialDelimitedFrom()方法用來從字節數組或字節流中解析出Message實例,在生成的代碼中,Builder的實現直接調用Parser實現類中的方法。

          在大部分情況下,MessageLite已經能完成所有的序列化和反序列化操作了,特別是一些資源有限額手持設備,它如果運行整個protobuf庫會顯得太耗資源;可以在.proto文件中加入一下指令來告訴protobuf編譯器只需要生成實現MessageLite的類:

          option optimize_for = LITE_RUNTIME

          然而對一般的Server程序來說,我們并不在乎這點資源的損耗,因而會選擇實現Message接口,它相比MessageLite,添加了Descriptors相關的支持,即支持使用FieldDescriptor來構建Message.Builder實例并最終構建Message實例。

          MessageOrBuilder接口繼承自MessageLiteOrBuilder接口,它定義了MessageMessage.Builder共有的接口,即添加了DescriptorFieldDescriptor等相關的擴展。由于實現MessageMessage.Builder接口的類保存了所有Message定義時具有的信息(文件名、包名、字段列表等,使用各種Descriptor類來抽象),因而我們可以使用Message/Message.Builder類獲取到更多的信息,如一個Message/Message.Builder沒有賦值所有required的字段,可以使用findInitializationErrors()方法來獲取所有未賦值的字段列表(字段的全路徑名,getInitializationErrorString()是這個列表的字符串形式表達,為了提升性能,建議使用isInitialized()方法先做初步判斷,因為它更快);另外在MessageOrBuilder中還定義了當前Message對應的Descriptor實例:getDescriptorForType()方法,獲取所有已經賦值的FieldDescriptor到其值的一個MapgetAllFields(),通過FieldDescriptor取得其值:getField(),判斷一個字段是否已經被賦值:hasField(),獲取repeated字段的countgetRepeatedFieldCount(),通過FieldDescriptor以及index獲取repeated字段在index處的值:getRepeatedField(),獲取未知的字段:getUnknownFields()Message接口除了繼承自MessageOrBuilder接口的方法,并沒有定義多余的方法,只是添加了equalshashCodetoString方法的定義。而Message.Builder接口除了繼承自MessageOrBuilder接口以外,它還定義了基于FieldDescriptor的方法,如通過FieldDescriptor創建/獲取Builder實例:newBuilderForFileld()/getFieldBuilder(),通過FieldDescriptor設置/清除字段的值:setField()/clearField()/setRepeatedField()/addRepeatedField(),以及設置UnknownFieldssetUnknownFields()/mergeUnknownFields()

           

          MessageLite/Message類圖如下:



          RPC框架

          除了序列化框架,protobuf還定義了一套簡單的RPC框架。之所以說簡單是因為它定義的Service層接口的協議,而沒有具體和傳輸相關的實現,而只是將傳輸相關的邏輯抽象成RpcChannelBlockingRpcChannel分別用于表示同步和一步方式的Service方法調用,而至于底層用什么樣的協議和框架,由用戶自己決定并實現。

          所謂RPC框架,從用戶角度上最基本的就是定義客戶端和服務器端的協議,即服務器端暴露出什么樣的接口供客戶端調用,這個接口定義了服務器在一個Host的某個(些)端口上接收某些請求數據,并期望能返回的響應。其中服務器和端口號屬于傳輸實現的范疇,protobuf只是用RpcChannel/BlockingRpcChannel的概念做了抽象,而沒有給出具體實現;而接收某個請求數據以及期待的響應數據,在protobuf使用Service/BlockingService抽象來定義,并且這也是protobufRPC框架的定義部分,其中ServiceRpcChannel共同構成異步方式的RPC框架,而BlockingServiceBlockingRpcChannel共同構成了同步(阻塞)方式的RPC框架。

          從底層實現的角度,一個RPC調用就是客戶端發送一些請求數據給服務器,服務器解析并處理這些請求數據,然后將響應數據返回給客戶端。為了隱藏內部實現細節,提升寫代碼的效率,RPC將這一過程封裝成方法調用,即不同的請求用不同的方法表達,這就是protobufRPC的定義。在protobuf中,定義一個PRC接口比較簡單:首先開啟RPC功能,然后用service關鍵字定義一個接口,在接口中使用rpc關鍵字定義一個方法,方法包含方法名、方法參數、返回值,其中方法參數和返回值都必須是一個message類型,并且只能有一個:

          option java_generic_services = true;

          service MyService {
              rpc request(SearchRequest) returns(SearchResponse);
          }

          protobuf編譯生成的代碼中,它會生成一個MyService抽象類實現了Service接口,一般它只是作為一個命名空間,它內部定義了兩個接口:InterfaceBlockingInterface本別繼承自Service接口和BlockingService接口,用于抽象異步和同步方式的RPC方法調用;這兩個接口有兩個實現類:StubBlockingStub,他們分別接收RpcChannelBlockingRpcChannel實例作為構造函數參數,可以使用MyService中的靜態方法newStub()newBlockingStub()方法獲取他們各自實例,他們主要用于客戶端的調用。在生成的request方法中,除了request本身的參數,還有一個RpcController參數,它用于處理在RpcChannel/BlockingRpcChannel調用中的狀態處理,如錯誤處理等,使用它可以獲知此次調用是否出錯,錯誤信息是什么等。在MyService中還定義了兩個靜態方法newReflectiveService/newReflectiveBlockingService,他們接收Interface/BlockingInterface實例,并返回Service/BlockingService的實現實例(暫時還沒有想到使用他們的場景)。



          MyServiceRPC框架實現中,在服務器端,實現MyService.Interface/MyService.BlockingInterface接口,然后將它注冊到對RpcChannel/BlockingRpcChannel框架的實現中;在客戶端則創建一個RpcChannel/BlockingRpcChannel實例,傳入MyService.newStub()/MyService.newBlockingStub()方法獲取對應的實例,然后使用這個Stub/BlockingStub實例調用相應的方法即可。

          posted on 2015-04-01 09:31 DLevin 閱讀(23697) 評論(1)  編輯  收藏 所屬分類: Protobuf

          FeedBack:
          # re: 深入Protobuf源碼-Descriptor、Message、RPC框架
          2015-06-12 15:51 | 孫超6106
          你好,請問你這類圖用什么工具生成的? 反向還是手動??謝謝  回復  更多評論
            
          主站蜘蛛池模板: 乐亭县| 威海市| 巴塘县| 寿宁县| 于田县| 晴隆县| 出国| 阳城县| 天等县| 兴化市| 丽水市| 阳新县| 利辛县| 襄城县| 蓬溪县| 南安市| 克山县| 呼图壁县| 沁源县| 和田市| 波密县| 郸城县| 沛县| 昌宁县| 嘉祥县| 韩城市| 潍坊市| 白银市| 康定县| 滨海县| 英山县| 松桃| 田东县| 永善县| 恭城| 铁岭县| 蒙城县| 淮阳县| 襄樊市| 盐亭县| 赫章县|