Thrift之TProtocol系列TJSONProtocol解析

      在瞭解JSON協議以前,朋友們能夠先去了解一下JSON的基礎知識,和ASCII基本分佈,關於JSON一些常識請見這裏;html

      JSON (JavaScript Object Notation)是一種數據交換格式,是以JavaScript爲基礎的數據表示語言,是在如下兩種數據結構的基礎上來定義基本的數據描述格式的:1) 含有名稱/值對的Object;2) 以」[「,",","]"組成的數組。對於 JSON,下例:形如{「name」:」tom」,」age」:23}就表示一個JSON 對象,其有兩個屬性,值分別爲tom和23。key必須爲string。JSON支持的基本數據類型,包括Number,Boolean,  null, String;  boolean, null類型值不能加」 「,否則會當作string來出來,再經過object, array來組合成豐富的類型結構。json

    來看看Thrift中JSON經常使用標籤的定義:數組

  private static final byte[] COMMA = new byte[] {','};      //json object中鍵值對之間, json array中元素之間的
  private static final byte[] COLON = new byte[] {':'};       //json object中key : value
  private static final byte[] LBRACE = new byte[] {'{'};      //json object開始標籤
  private static final byte[] RBRACE = new byte[] {'}'};      //json object結束標籤
  private static final byte[] LBRACKET = new byte[] {'['};    //json array開始標籤
  private static final byte[] RBRACKET = new byte[] {']'};    //json array結束標籤
  private static final byte[] QUOTE = new byte[] {'"'};       //json 字符串標籤
  private static final byte[] BACKSLASH = new byte[] {'\\'};   //json中轉義
  private static final byte[] ZERO = new byte[] {'0'};        // 0字符

      Unicode編碼形式:數據結構

private static final byte[] ESCSEQ = new byte[] {'\\','u','0','0'};

      JSON中字符串是由雙引號包圍的任意數量的unicode字符結合,固然還要包含經過'\'轉義字符(很少),以下;ide

  private static final String ESCAPE_CHARS = "\"\\/bfnrt";    //須要轉義的字符 '"' , '\' , ' /' ,'b' , 'f' , 'n' ,'r' ,'t'
  

  private static final byte[] ESCAPE_CHAR_VALS = {
    '"', '\\', '/', '\b', '\f', '\n', '\r', '\t', // 轉義字符對應的字節數組
  };

    ASCII表前48字符表:編碼

1   private static final byte[] JSON_CHAR_TABLE = {
2     /*  0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F */
3         0,  0,  0,  0,  0,  0,  0,  0,'b','t','n',  0,'f','r',  0,  0, // 0
4         0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // 1
5         1,  1,'"',  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1, // 2
6   };

     前48個字符中,值爲0的是一些不經常使用的控制字符,因此這裏標爲0,unicode輸出;值爲1的爲那些可顯示非轉義字符,輸出時直接按其ASCII編碼值輸出便可;其餘的是須要轉義的字符,按照上面的轉義字符的字節數組輸出,spa

下面是ASCII編碼圖,朋友們能夠參照着看一下:設計

 

     下面是Thrift內部數據類型對應的JSON輸出字節數組標誌,在write和read時,會按照其進行相應的轉換,源碼時,具體分析:code

  private static final byte[] NAME_BOOL = new byte[] {'t', 'f'};
  private static final byte[] NAME_BYTE = new byte[] {'i','8'};
  private static final byte[] NAME_I16 = new byte[] {'i','1','6'};
  private static final byte[] NAME_I32 = new byte[] {'i','3','2'};
  private static final byte[] NAME_I64 = new byte[] {'i','6','4'};
  private static final byte[] NAME_DOUBLE = new byte[] {'d','b','l'};
  private static final byte[] NAME_STRUCT = new byte[] {'r','e','c'};
  private static final byte[] NAME_STRING = new byte[] {'s','t','r'};
  private static final byte[] NAME_MAP = new byte[] {'m','a','p'};
  private static final byte[] NAME_LIST = new byte[] {'l','s','t'};
  private static final byte[] NAME_SET = new byte[] {'s','e','t'};

      基本重要數據成員介紹完畢,在具體分析讀寫前,再來介紹下寫輔助方法和類,有助於後面讀寫的具體理解:htm

// 把char('0 - 9' & ' a - f')字符轉變爲相應的hex十六進制值。
  private static final byte hexVal(byte ch) throws TException {
    if ((ch >= '0') && (ch <= '9')) {
      return (byte)((char)ch - '0');
    }
    else if ((ch >= 'a') && (ch <= 'f')) {
      return (byte)((char)ch - 'a' + 10);
    }
    else {
      throw new TProtocolException(TProtocolException.INVALID_DATA,
                                   "Expected hex character");
    }
  }

  // 上面的反過程,把hex十六進制值轉變爲相應的字符。
  private static final byte hexChar(byte val) {
    val &= 0x0F;
    if (val < 10) {
      return (byte)((char)val + '0');
    }
    else {
      return (byte)((char)(val - 10) + 'a');
    }
  }

     輔助類,用於具體讀寫過程當中寫入,或讀取JSON語法字符,該類爲基類,no-op:

 protected class JSONBaseContext {
    protected void write() throws TException {}

    protected void read() throws TException {}

    protected boolean escapeNum() { return false; }
  }

     用於讀取JSON數組的JSONBASEContext子類, 用於寫入,讀取元素item之間的JSON語法字符 ' ,':

 protected class JSONListContext extends JSONBaseContext {
    private boolean first_ = true;  //第一個元素以前不要添加,讀取 ',';

    @Override
    protected void write() throws TException {
      if (first_) {
        first_ = false;
      } else {
        trans_.write(COMMA);
      }
    }

    @Override
    protected void read() throws TException {
      if (first_) {
        first_ = false;
      } else {
        readJSONSyntaxChar(COMMA);
      }
    }
  }

     用於讀取JSON 對象(Object)的JSONBASEContext子類,  用於寫入,讀取元素item之間的 ' ,' , 和key, value對之間的 ':' JSON語法字符:

  protected class JSONPairContext extends JSONBaseContext {
    private boolean first_ = true;   //添加,讀取 ','字符的標誌,第一個不須要加入和讀取;
    private boolean colon_ = true;   //添加,讀取 ':'字符的標誌,

    @Override
    protected void write() throws TException {
      if (first_) {
        first_ = false;
        colon_ = true;
      } else {
        trans_.write(colon_ ? COLON : COMMA);
        colon_ = !colon_;
      }
    }

    @Override
    protected void read() throws TException {
      if (first_) {
        first_ = false;
        colon_ = true;
      } else {
        readJSONSyntaxChar(colon_ ? COLON : COMMA);
        colon_ = !colon_;
      }
    }

    @Override
    protected boolean escapeNum() { //因爲JSON Object的key必須爲string類型,因此這裏用colon標誌,同時標註是否須要寫入,讀取'"'字符;
      return colon_;
    }
  }

   

private Stack<JSONBaseContext> contextStack_ = new Stack<JSONBaseContext>(); //用於讀取不一樣類型時,上下文上下切換,上面說起的兩種上下文,讀取和寫入JSON Object,和JSON array,當遞歸解析和寫入時,得隨時切換
private JSONBaseContext context_ = new JSONBaseContext(); //當前讀取,寫入上下文。
// private void pushContext(JSONBaseContext c) { //吧方法參數的上下文設爲當前上下文(好比當前真在解析JSON array,而array中出現一個JOSN object(map) item時,得切換到JSONPairContext來解析) contextStack_.push(context_); //當前上下文壓入棧, context_ = c; } private void popContext() { context_ = contextStack_.pop(); //吧以前壓入棧的上下文設爲當前上下文(仍是上面的例子,當JSON object解析完後,接着解析array別的item,因此還得以前壓入棧的上下文) }

 

       上面兩個類都用到readJSONSyntaxChar(xxx)方法,咱們再來看看:

 protected void readJSONSyntaxChar(byte[] b) throws TException {
    byte ch = reader_.read();     //方法參數設定一個字節數組,當讀取的字節不等於b[0]拋異常,要來驗證讀取的字節是否爲指定的JSON語句字符的。
    if (ch != b[0]) {
      throw new TProtocolException(TProtocolException.INVALID_DATA,
                                   "Unexpected character:" + (char)ch);
    }
  }

       JOSN協議從傳輸層讀取字節時都是一字節一字節的讀取:

 protected class LookaheadReader {

    private boolean hasData_;
    private byte[] data_ = new byte[1];

   //檢測當前容量爲1的字節數組中是否有數據,沒有從底層傳輸層讀取一字節,該方法是消耗型的
    protected byte read() throws TException {
      if (hasData_) {
        hasData_ = false;
      }
      else {
        trans_.readAll(data_, 0, 1);
      }
      return data_[0];
    }

    // 同上,不一樣的是該方法不發生消耗
    protected byte peek() throws TException {
      if (!hasData_) {
        trans_.readAll(data_, 0, 1);
      }
      hasData_ = true;
      return data_[0];
    }
  }

     別的參數成員:

 // 上面已經介紹,當前解析的底層讀取器,一個字節一個字節的讀取
  private LookaheadReader reader_ = new LookaheadReader();

  // 是否寫入TField的(即方法參數的參數名)的標誌,不寫入參數名,就要參數標號來代替
  private boolean fieldNamesAsString_ = false;

    OK!進入正文,首先看看JSON協議怎麼寫數據的,從string開始,JSON String分Unicode和轉義字符兩種:

 private void writeJSONString(byte[] b) throws TException {
    context_.write(); // JSONBaseContext 空操做
    trans_.write(QUOTE); //寫入'"'字符
    int len = b.length;  
    for (int i = 0; i < len; i++) {
      if ((b[i] & 0x00FF) >= 0x30) { //對照上面的ASCII表, >= 0x30即 >= 48(上面JSON_CHAR_TABLE表述的爲0 - 47,48以上的用的着的轉義字符就'\')
        if (b[i] == BACKSLASH[0]) { //若是寫入的字符爲 '\',須要轉義。
          trans_.write(BACKSLASH);//
          trans_.write(BACKSLASH); // 寫兩次'\'
        }
        else {
          trans_.write(b, i, 1);// 0x30上除'\',以外的字符直接寫入
        }
      }
      else { // b[i] < 48的狀況,即上面設計的那張JSON_CHAR_TABLE倍
        tmpbuf_[0] = JSON_CHAR_TABLE[b[i]];  
        if (tmpbuf_[0] == 1) {       //查表後,表對應的值爲1的狀況:非轉義,便可直接寫入。
          trans_.write(b, i, 1);
        }
        else if (tmpbuf_[0] > 1) {     //即爲那些轉義字符 '"' , '/' , 'b' , 't' , 'r' , 'n';
          trans_.write(BACKSLASH); // 寫以前先追加'\';
          trans_.write(tmpbuf_, 0, 1);
        }
        else { // 0
          trans_.write(ESCSEQ);//unicode   \u00xx形式寫入。
          tmpbuf_[0] = hexChar((byte)(b[i] >> 4)); 
          tmpbuf_[1] = hexChar(b[i]);
          trans_.write(tmpbuf_, 0, 2);
        }
      }
    }
    trans_.write(QUOTE);  // 最後再加上'"';
  }

      JSON 整形輸出:

private void writeJSONInteger(long num) throws TException { //雖然這裏取long的字符串表現形式,可是輸出是按JSON 整數表示輸出(沒加 '"');
    context_.write();  //no -op
    String str = Long.toString(num); //string表示形式
    boolean escapeNum = context_.escapeNum();  //false
    if (escapeNum) {
      trans_.write(QUOTE);  
    }
    try {  
      byte[] buf = str.getBytes("UTF-8");  //UTF-8字符編碼獲得字節數組
      trans_.write(buf);
    } catch (UnsupportedEncodingException uex) {
      throw new TException("JVM DOES NOT SUPPORT UTF-8");
    }
    if (escapeNum) {
      trans_.write(QUOTE);
    }
  }

     JSON 浮點數寫入,JSON浮點數有幾點特殊,JSON幾種特殊的浮點數用字符串表示和傳輸:

     a. NaN表示不是數字值;

     b. Infinity表示正無窮大;

     c. –Infinity表示負無窮大。

private void writeJSONDouble(double num) throws TException {
    context_.write();
    String str = Double.toString(num);
    boolean special = false;
    switch (str.charAt(0)) {
    case 'N': // NaN
    case 'I': // Infinity
      special = true;
      break;
    case '-':
      if (str.charAt(1) == 'I') { // -Infinity
        special = true;
      }
      break;
    default:
      break;
  }

    boolean escapeNum = special || context_.escapeNum();  //三種特殊狀況,special標誌位true,此時escapeNum爲true, 即用JSON字符串形式表示和輸出,不然仍是以JSON Number類型輸出
    if (escapeNum) {
      trans_.write(QUOTE); // '"'
    }
    try {
      byte[] b = str.getBytes("UTF-8");
      trans_.write(b, 0, b.length);
    } catch (UnsupportedEncodingException uex) {
      throw new TException("JVM DOES NOT SUPPORT UTF-8");
    }
    if (escapeNum) {
      trans_.write(QUOTE); // '"'
    }
  }

       Thrift的二進制值並編碼爲base64編碼而後做爲JSON的字符串:

   public void writeBinary(ByteBuffer bin) throws TException {
      writeJSONBase64(bin.array(), bin.position() + bin.arrayOffset(), bin.limit() - bin.position() - bin.arrayOffset());
   }


private void writeJSONBase64(byte[] b, int offset, int length) throws TException { context_.write(); trans_.write(QUOTE); int len = length; int off = offset; while (len >= 3) { // Encode 3 bytes at a time TBase64Utils.encode(b, off, 3, tmpbuf_, 0); trans_.write(tmpbuf_, 0, 4); off += 3; len -= 3; } if (len > 0) { // Encode remainder TBase64Utils.encode(b, off, len, tmpbuf_, 0); trans_.write(tmpbuf_, 0, len + 1); } trans_.write(QUOTE); }

      Thrift的bool類型寫入方式是按JSON 整形寫入,false = 0 , ture = 1:

public void writeBool(boolean b) throws TException {
    writeJSONInteger(b ? (long)1 : (long)0);
  }

    Thrift其餘整型和字節寫入方式一併貼出,轉換爲long類型,都是按JSON整形寫入:

 @Override
  public void writeByte(byte b) throws TException {
    writeJSONInteger((long)b);
  }

  @Override
  public void writeI16(short i16) throws TException {
    writeJSONInteger((long)i16);
  }

  @Override
  public void writeI32(int i32) throws TException {
    writeJSONInteger((long)i32);
  }

  @Override
  public void writeI64(long i64) throws TException {
    writeJSONInteger(i64);
  }

   Thrift的double類型,按照JSON浮點數寫入:

 public void writeDouble(double dub) throws TException {
    writeJSONDouble(dub);
  }

   Thrift的string類型按照JSON字符串寫入,JSON在處理時,會對一些字符進行轉義:

 public void writeString(String str) throws TException {
    try {
      byte[] b = str.getBytes("UTF-8"); //string utf-8編碼以後獲得字節數組
      writeJSONString(b);
    } catch (UnsupportedEncodingException uex) {
      throw new TException("JVM DOES NOT SUPPORT UTF-8");
    }
  }

    再來看看Thrift 的set , list寫入方法:

  @Override
  public void writeListBegin(TList list) throws TException {
    writeJSONArrayStart();
    writeJSONString(getTypeNameForTypeID(list.elemType));
    writeJSONInteger(list.size);
  }

  @Override
  public void writeListEnd() throws TException {
    writeJSONArrayEnd();
  }

  @Override
  public void writeSetBegin(TSet set) throws TException {
    writeJSONArrayStart();
    writeJSONString(getTypeNameForTypeID(set.elemType));
    writeJSONInteger(set.size);
  }

  @Override
  public void writeSetEnd() throws TException {
    writeJSONArrayEnd();
  }

     Thrift的lists和sets被表示爲JSON的array(數組),其中數組的第一個元素表示Thrift元素的數據類型,數組第二值表示後面Thrift元素的個數。接着後面就是全部的元素:

  private void writeJSONArrayStart() throws TException {
    context_.write();
    trans_.write(LBRACKET);  //寫入JSON array語法字符 ‘[’
    pushContext(new JSONListContext()); //設置當前處理解析上下文,
  }

  private void writeJSONArrayEnd() throws TException {
    popContext();   //回覆以前的處理解析上下文
    trans_.write(RBRACKET);  //補上JSON array語句結束字符']'
  }

      數組的第一個元素爲數據類型,按JSON字符串寫入,即本篇開頭貼出的代碼,getTypeNameForTypeID(set.elemType) 用於Thrift數據類型和JSON爲該類型表示的字符串的字節數組之間的轉換:

 private static final byte[] getTypeNameForTypeID(byte typeID)
    throws TException {
    switch (typeID) {
    case TType.BOOL:
      return NAME_BOOL;
    case TType.BYTE:
      return NAME_BYTE;
    case TType.I16:
      return NAME_I16;
    case TType.I32:
      return NAME_I32;
    case TType.I64:
      return NAME_I64;
    case TType.DOUBLE:
      return NAME_DOUBLE;
    case TType.STRING:
      return NAME_STRING;
    case TType.STRUCT:
      return NAME_STRUCT;
    case TType.MAP:
      return NAME_MAP;
    case TType.SET:
      return NAME_SET;
    case TType.LIST:
      return NAME_LIST;
    default:
      throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED,
                                   "Unrecognized type");
    }
  }

     數組的第二個元素爲整個list , set的長度,按JSON整形寫入,而後寫入各個容器成員。

     Thrift map數據類型寫入方式:

  public void writeMapBegin(TMap map) throws TException {
    writeJSONArrayStart(); // '[' ,設置當前處理解析上下文
    writeJSONString(getTypeNameForTypeID(map.keyType)); //數組的第一個元素爲key類型 =>JSON的類型字符串表示字節數組
    writeJSONString(getTypeNameForTypeID(map.valueType)); // 數組的第二個元素  爲 value類型
    writeJSONInteger(map.size); //數組的第三個元素,map的長度
    writeJSONObjectStart(); //map的key,value對按 Json object輸出。
  }

  @Override
  public void writeMapEnd() throws TException {
    writeJSONObjectEnd(); //回覆當前上下文 ‘}’
    writeJSONArrayEnd(); //回覆當前上下文 ']'
  }
  private void writeJSONObjectStart() throws TException {
    context_.write();
    trans_.write(LBRACE);
    pushContext(new JSONPairContext());
  }

  private void writeJSONObjectEnd() throws TException {
    popContext();
    trans_.write(RBRACE);
  }

       Thrift的maps被表示爲JSON的array,其中前兩個值分別表示鍵和值的類型,跟着就是鍵值對的個數,接着就是包含具體鍵值對的JSON對象了。注意了JSON的鍵只能是字符串,這就是要求Thrift的maps類型的鍵必須是數字和字符串的,而且數字和字符串作轉換,就是下面說起的字符串。

       有效的類型標識是:"tf"表明 bool,"i8" 表示 byte,"i16"表示16位整數,"i32"表示32位整數,"i64"表示64位整數,"dbl"表示雙精度浮點型,"str" 表示字符串(包括二進制),"rec"表示結構體 ("records"),"map"表示 map,"lst" 表示 list, "set" 表示set。

 

     Thrift的messages(消息)被表示爲JSON的array,前四個元素分別表明協議版本、消息名稱、消息類型和序列ID:

public void writeMessageBegin(TMessage message) throws TException {
    writeJSONArrayStart();
    writeJSONInteger(VERSION);
    try {
      byte[] b = message.name.getBytes("UTF-8");
      writeJSONString(b);
    } catch (UnsupportedEncodingException uex) {
      throw new TException("JVM DOES NOT SUPPORT UTF-8");
    }
    writeJSONInteger(message.type);
    writeJSONInteger(message.seqid);
  }

    而後呢?該寫Thrift的tstructs了(具體的RPC方法參數封裝),struct是按照JSON object寫入:

public void writeStructBegin(TStruct struct) throws TException {
    writeJSONObjectStart();
  }

   Thrift的TField(具體的每一個參數)的寫入:

 public void writeFieldBegin(TField field) throws TException {
    if (fieldNamesAsString_) {
      writeString(field.name); //參數名
    } else {
      writeJSONInteger(field.id); //或者參數id
    }
    writeJSONObjectStart();  // '{'
    writeJSONString(getTypeNameForTypeID(field.type)); Thrift數據類型的字符串表示
  }

   即Thrift的消息類型形如:

[ 1 (version) ,   "messageName" ,  type (call, oneway, reply, exception (byte)) ,  seq id , 
   {  "argument name" : {  " argument  type"  :  value  },
       "argument name" : {  " argument  type" :   value},
     ....................................................................
   }
]
相關文章
相關標籤/搜索