首先看一下Thrift的整體架構,如下圖:

如圖所示,黃色部分是用戶實現的業務邏輯,褐色部分是根據thrift定義的服務接口描述文件生成的客戶端和服務器端代碼框架(前篇2中已分析了thrift service生成代碼),紅色部分是根據Thrift文件生成代碼實現數據的讀寫操作。紅色部分以下是Thrift的協議,傳輸體系以及底層的IO通信,使用thrift可以很方便的定義一個服務並且選擇不同的傳輸協議和傳輸層而不用重新生成代碼(Thrift提供的是一種大而全的服務,它認為沒有統一的標准,用戶根據自己需要,組合使用之;而avro排斥多方案引起的混亂,提倡建立統一的標准,后篇在來分析avro,其作為hadoop rpc框架,在大數據量傳輸方面有一定的優勢。)。
數據類型:
Thrift可定義的數據類型包括以下幾種(說句后話,為什么這些框架protobuf, thrift, avro都定義自己的數據類型???他們都作為多語言支持的,內部類型的定義,不同語言支持的數據類型統一映射到框架內部支持數據類型,方便處理,在數據讀寫傳輸過程中按統一方式處理。)
- 基本類型
- bool : 布爾,true or false,對應java的boolean
- byte : 8位有符號整數,對應java的byte
- i16 : 16位有符號整數,對應java的short
- i32 : 32位有符號整數,對應java的int
- i64 : 64位有符號整數,對應java的long
- double : 64位浮點數 ,對應java的double
- string : 未知編碼文本或二進制字符串,對應java的string
- 結構體類型
- struct : 定義公共對象,類似於C預壓中的結構體定義,在java中是一個javabean.
- 容器類型
- list : 對應java的arraylist
- set : 對應java的hashset
- map: 對應java的HashMap
- 異常類型 (在java中,TException為基類)
- 服務類型 (在Java中,統一為Iface, AsyncIface接口)
協議:
Thrift可以讓用戶選擇客戶端和服務器端之間的傳輸通信協議的類別(用戶不同的需求,不同應用可以根據自己需求選擇適合自己的傳輸協議),一般情況下使用二進制類型的傳輸協議(提高傳輸效率,多數用於內部系統之間的通信傳輸),還可以使用基於文本類型的協議(json),json,xml作為通用網絡數據傳輸協議,可以實現外部系統調用。
- TBinaryProtocol- 二進制編碼格式進行數據傳輸(默認)
- TCompactProtocol- 高效率,密集的二進制編碼格式進行數據傳輸(了解protocol buffer內部編碼實現的話,就不足為奇了)
- TJSONProtocol - 使用JSON的數據編碼協議進行數據傳輸。
- TSimpleJSONProtocol- 只提供JSON只寫的協議,使用與通過腳本語言解析

其中TProtocolDecorator,裝飾者,抽象類,其中典型實現TMultiplexedProtocol,允許客戶端連接多功能server.
TBinaryProtocol:
該協議作為thrift默認的二進制協議,通過它,所有數據都是以二進制形式讀寫,沒有什么特殊處理,除了tag外,基本都是數據本身的二進制,不過值得了解的是Thrift的讀寫message的過程(tag的運用);

這里列出協議抽象基類TProtocol的一部分方法,可以看出各種tagBeging(),tagEnd()方法,read方法一樣。
還是以上篇Thrift 代碼生成分析篇解析(Hello.thrift)開始,看一下客戶端調用service方法開始引入TBinaryProtocol,沒看過的朋友可以先了解一下。方法如下:
string helloString(1:string para)
進Thrift為我們生成的Hello類里面看看吧。
public String helloString(String para) throws org.apache.thrift.TException { send_helloString(para); return recv_helloString(); } public void send_helloString(String para) throws org.apache.thrift.TException { helloString_args args = new helloString_args(); args.setPara(para); sendBase("helloString", args); }
helloString_args上篇分析過,直接進其父類TServiceClient中看下sendBase():
protected void sendBase(String methodName, TBase args) throws TException { oprot_.writeMessageBegin(new TMessage(methodName, TMessageType.CALL, ++seqid_)); args.write(oprot_); oprot_.writeMessageEnd(); oprot_.getTransport().flush(); }
協議層先寫入messageBeginTag,然后寫message(對應其方法參數的封裝類) ,最后messageEndTag,傳輸層flush。再來看下message的結構吧。
public final class TMessage { public TMessage() { this("", TType.STOP, 0);//占位符,1byte,沒實際內容 } public TMessage(String n, byte t, int s) { name = n; //方法名 type = t; //消息類型 seqid = s; //消息 seq number } public final String name; public final byte type; public final int seqid; @Override public String toString() { return "<TMessage name:'" + name + "' type: " + type + " seqid:" + seqid + ">"; }
Tmessage三個成員,RPC調用方法名,消息類型,消息遞增序列化。接着看下消息類型:
public final class TMessageType { public static final byte CALL = 1; public static final byte REPLY = 2; public static final byte EXCEPTION = 3; public static final byte ONEWAY = 4; }
四種消息類型,RPC request(客戶端請求),RPC正常repsonse(服務器響應),RPC exception(服務器端返回異常),單向RPC(客戶端發出request,但不要求服務器端給出響應).
<****************************************************************************************************************************************>
進入TBinaryProtocol中的writeMessageBegin()瞧瞧:
1 public void writeMessageBegin(TMessage message) throws TException { 2 if (strictWrite_) { //是否嚴格寫 3 int version = VERSION_1 | message.type; //版本號,消息類型。 4 writeI32(version); 5 writeString(message.name); //消息name屬性,即方法名。 6 writeI32(message.seqid);//序列號 7 } else { //非嚴格寫,無版本號和消息類型 8 writeString(message.name); 9 writeByte(message.type); 10 writeI32(message.seqid); 11 } 12 }
版本號如下:
protected static final int VERSION_MASK = 0xffff0000; protected static final int VERSION_1 = 0x80010000;
繼續writeI32():
1 private byte[] i32out = new byte[4]; 2 public void writeI32(int i32) throws TException { 3 i32out[0] = (byte)(0xff & (i32 >> 24)); 4 i32out[1] = (byte)(0xff & (i32 >> 16)); 5 i32out[2] = (byte)(0xff & (i32 >> 8)); 6 i32out[3] = (byte)(0xff & (i32)); 7 trans_.write(i32out, 0, 4); 8 }
大端寫入int的字節數組。writeString():
public void writeString(String str) throws TException { try { byte[] dat = str.getBytes("UTF-8"); writeI32(dat.length); trans_.write(dat, 0, dat.length); } catch (UnsupportedEncodingException uex) { throw new TException("JVM DOES NOT SUPPORT UTF-8"); } }
string進UTF-8后獲得其字節數組,寫入數組長度,在寫string bytes,(string寫,統一utf-8編碼后,先寫其字節數組長度,再寫實際內容)。再來看一下寫實際消息:
args.write(oprot_); // TServiceClient中的write,調用生成hellostring_args的write.下面是其實現。 public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException { schemes.get(oprot.getScheme()).getScheme().write(oprot, this); //調用schema對應的的write,上篇有分析 }
private static final Map<Class<? extends IScheme>, SchemeFactory> schemes = new HashMap<Class<? extends IScheme>, SchemeFactory>(); static { schemes.put(StandardScheme.class, new helloString_argsStandardSchemeFactory()); schemes.put(TupleScheme.class, new helloString_argsTupleSchemeFactory()); }
private static class helloString_argsStandardScheme extends StandardScheme<helloString_args> { public void write(org.apache.thrift.protocol.TProtocol oprot, helloString_args struct) throws org.apache.thrift.TException { struct.validate(); oprot.writeStructBegin(STRUCT_DESC); //先寫structbeginTag. if (struct.para != null) { //struct中參數不為null oprot.writeFieldBegin(PARA_FIELD_DESC); //寫fieldbegingTag oprot.writeString(struct.para); //寫參數 oprot.writeFieldEnd(); //寫fieldEndTag } oprot.writeFieldStop();//寫fieldStopTag(猜測應該是當遠程RPC調用方法中有多個參數時,用於標記所有參數寫完標志Tag,fieldEndtag只代表每個參數寫完,因為本例就一個參數不好驗證,朋友確定的話,不吝賜教) oprot.writeStructEnd(); //寫structEndTag(方法參數在Thrift中被視為struct結構,即java中javabean,其中成員為具體方法參數值。方法返回值也一樣。) } }
上面注解可以了解大概步驟:
private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("helloString_args"); private static final org.apache.thrift.protocol.TField PARA_FIELD_DESC = new org.apache.thrift.protocol.TField("para", org.apache.thrift.protocol.TType.STRING, (short)1);
TStruct結構再來瞧瞧:
public final class TStruct { public TStruct() { this(""); } public TStruct(String n) { name = n; } public final String name; }
簡單的string屬性,struct名,即自動生成的代碼類名。再瞅瞅TField吧:
public class TField { public TField() { this("", TType.STOP, (short)0);//空成員,沒賦值的情況。 } public TField(String n, byte t, short i) { name = n; //RPC方法調用參數名 type = t; //參數類型 id = i;//thrift文件定義的參數順序 } public final String name; public final byte type; public final short id; public String toString() { return "<TField name:'" + name + "' type:" + type + " field-id:" + id + ">"; }
public final class TType { //thrift內部數據類型 public static final byte STOP = 0; public static final byte VOID = 1; public static final byte BOOL = 2; public static final byte BYTE = 3; public static final byte DOUBLE = 4; public static final byte I16 = 6; public static final byte I32 = 8; public static final byte I64 = 10; public static final byte STRING = 11; public static final byte STRUCT = 12; public static final byte MAP = 13; public static final byte SET = 14; public static final byte LIST = 15; public static final byte ENUM = 16; }
OK,繼續跳回TBinaryProtocol中,跳來跳去的,大家有點累了吧,堅持就是勝利^!^
public void writeMessageEnd() {} public void writeStructBegin(TStruct struct) {} public void writeStructEnd() {}
fuck,這三個為空操作,蛋蛋傷。
public void writeFieldBegin(TField field) throws TException { writeByte(field.type); writeI16(field.id); } public void writeFieldEnd() {}
fieldBeginTag中,先寫入參數類型,參數序列號。至此消息寫完畢(讀操作就不講了,反操作,差不多,不過異步操作,准備后面單獨開一篇來講下),我們再來看看TBinaryProtocol中其他方法:
boolean:一個字節1或0.
public void writeBool(boolean b) throws TException { writeByte(b ? (byte)1 : (byte)0); //一個字節 }
i16,i64:依舊大端。
public void writeI16(short i16) throws TException { //2字節 i16out[0] = (byte)(0xff & (i16 >> 8)); i16out[1] = (byte)(0xff & (i16)); trans_.write(i16out, 0, 2); } private byte[] i64out = new byte[8]; public void writeI64(long i64) throws TException {//8字節 i64out[0] = (byte)(0xff & (i64 >> 56)); i64out[1] = (byte)(0xff & (i64 >> 48)); i64out[2] = (byte)(0xff & (i64 >> 40)); i64out[3] = (byte)(0xff & (i64 >> 32)); i64out[4] = (byte)(0xff & (i64 >> 24)); i64out[5] = (byte)(0xff & (i64 >> 16)); i64out[6] = (byte)(0xff & (i64 >> 8)); i64out[7] = (byte)(0xff & (i64)); trans_.write(i64out, 0, 8); }
double:先轉化為long字節分布,然后按I64寫,(沒有float哦!):
public void writeDouble(double dub) throws TException { writeI64(Double.doubleToLongBits(dub)); }
Map tag:先寫map key類型(1字節),然后map value類型(1字節),最后寫鍵值對長度(4字節),扯句后話,不想avro中的map,其key type只能為string.
public void writeMapBegin(TMap map) throws TException { writeByte(map.keyType); writeByte(map.valueType); writeI32(map.size); } public void writeMapEnd() {}
List Tag: 先寫list 值類型(1字節),在寫list長度(4字節)。
public void writeListBegin(TList list) throws TException { writeByte(list.elemType); writeI32(list.size); } public void writeListEnd() {}
set Tag: 同上。
public void writeSetBegin(TSet set) throws TException { writeByte(set.elemType); writeI32(set.size); } public void writeSetEnd() {}
read操作就不細談了,朋友們可以自己去看看。
