Thrift之Protocol原始碼分析

maojunxu發表於2016-04-23


之前寫過兩篇關於 Thrift 的相關文章。

也算是對Thrift比較熟悉,不過對 Thrift 裡面的 Protocol 部分還是黑盒使用。 雖然大概能猜到具體實現方式,但是還是忍不住花了一點點時間把具體程式碼實現翻出來看看。 主要是為了滿足一下好奇心。

簡單搞了一個Thrift的描述檔案Insight.thrift作為例子。

struct Person {
    1: string name,
    2: i32 age,
    3: optional string address,
}

service Insight {
    Person Hello(1: Person person),
    Person Hi(1: Person p1, 2: Person p2),
}

然後通過 畢竟Thrift其實就是幹RPC的活,所以看原始碼就按著RPC遠端呼叫的順序來看就行。

從Hello函式呼叫開始,InsightClient::Hello 可以看出, 在每次RPC呼叫的時候,會先將函式名通過writeMessageBegin(“Hello”,
::apache::thrift::protocol::T_CALL, cseqid)
先傳送過去。 這個過程的序列化協議很簡單,直接就是傳輸的函式名字串。 然後再傳送引數。 傳送引數的時候,會將所有引數作為一個 struct 傳送

Insight_Hello_pargs

所以協議的序列化過程主要都是體現在 struct 的序列化上面。 比如像Hi函式的引數序列化過程:

uint32_t Insight_Hi_pargs::write(::apache::thrift::protocol::TProtocol* oprot) const {
  uint32_t xfer = 0;
  xfer += oprot->writeStructBegin("Insight_Hi_pargs");

  xfer += oprot->writeFieldBegin("p1", ::apache::thrift::protocol::T_STRUCT, 1);
  xfer += (*(this->p1)).write(oprot);
  xfer += oprot->writeFieldEnd();

  xfer += oprot->writeFieldBegin("p2", ::apache::thrift::protocol::T_STRUCT, 2);
  xfer += (*(this->p2)).write(oprot);
  xfer += oprot->writeFieldEnd();

  xfer += oprot->writeFieldStop();
  xfer += oprot->writeStructEnd();
  return xfer;
}

整個物件的序列化過程主要是依賴了介面 TProtocol 的函式。

對於實現 TProtocol 介面的序列化實現主要是以下三種(在thrift-0.9.0/lib/cpp/src/thrift/protocol裡):

  • TBinaryProtocol
  • TCompactProtocol
  • TJSONProtocol

要了解協議序列化過程主要看一下 TBinaryProtocol 和 TCompactProtocol 就夠了。

主要是如下幾個關鍵點:

  • 其實 writeStructStruct 和 writeStructEnd 啥屁事也不用做。
  • 其實 writeFieldBegin 只有後兩個引數有用,第二個引數是型別,第三個引數是ID, 因為光靠這兩者就可以在反序列化(讀取解析)的時候知道是哪個成員了。
  • struct write 的過程其實是個遞迴的過程,也就是在write函式中, 會遞迴的呼叫結構體本身每個成員的write函式。
  • TCompactProtocol 和 TBinaryProtocol 的區別主要是, TCompactProtocol 對整數型別使用了 ZigZag 壓縮演算法,比如 i32 型別的整數本來是4個位元組, 可以壓縮成 1~5 位元組不等。而 i64型別的整數本來是8個位元組。可以壓縮成 1~10 位元組不等。

http://yanyiwu.com/work/2015/04/05/thrift-protocol-insight.html


相關文章