Thrift 객체 시리얼화, 역시리얼화 - 바이트 배열 분석

5747 단어
설명
이 블로그는 Thrift 대상의 서열화, 반서열화 바이트 그룹과 Thrift 대상의 서열화, 반서열화 원리만 분석한다.기타 원본 분석은 따로 개설합니다~
준비 작업
Thrift 파일 정의
 struct Person {
    1: required i32 age;
    2: required string name;
 }

Java 코드 생성
thrift -r --gen java test.thrift

테스트 코드
@Test
public void testPerson() throws TException {

    Person person = new Person().setAge(18).setName("yano");
    System.out.println(person);

    TSerializer serializer = new TSerializer();
    byte[] bytes = serializer.serialize(person);
    System.out.println(Arrays.toString(bytes));

    Person parsePerson = new Person();
    TDeserializer deserializer = new TDeserializer();
    deserializer.deserialize(parsePerson, bytes);
    System.out.println(parsePerson);

}

결과 내보내기
com.yano.nankai.spring.thrift.Person(age:18, name:yano)
[8, 0, 1, 0, 0, 0, 18, 11, 0, 2, 0, 0, 0, 4, 121, 97, 110, 111, 0]
com.yano.nankai.spring.thrift.Person(age:18, name:yano)

서열화 과정
상술한 테스트 용례는 먼저 Person 대상을 새로 만들었는데, 이 대상은 두 개의 필드만 있다.이어서 Thrift의 Tserializer를 호출하여 person 객체를 서열화합니다.
생성된 바이트 배열은 다음과 같습니다.
[8, 0, 1, 0, 0, 0, 18, 11, 0, 2, 0, 0, 0, 4, 121, 97, 110, 111, 0]

Tserializer 클래스의 Serialize 방법은 다음과 같습니다. 최종적으로 개인 대상의 write 방법을 호출했습니다.
public byte[] serialize(TBase base) throws TException {
    this.baos_.reset();
    base.write(this.protocol_);
    return this.baos_.toByteArray();
}

Person 클래스의 write 방법:
  public void write(TProtocol oprot) throws TException {
    validate();

    oprot.writeStructBegin(STRUCT_DESC);
    oprot.writeFieldBegin(AGE_FIELD_DESC);
    oprot.writeI32(this.age);
    oprot.writeFieldEnd();
    if (this.name != null) {
      oprot.writeFieldBegin(NAME_FIELD_DESC);
      oprot.writeString(this.name);
      oprot.writeFieldEnd();
    }
    oprot.writeFieldStop();
    oprot.writeStructEnd();
  }

여기서 Tprotocol은 기본적으로 TBinary Protocol이고 write StructBegin () 및 write StructEnd () 방법은 비어 있습니다.
oprot.writeFieldBegin(AGE_FIELD_DESC);

TBinaryProtocol의 구체적인 구현은 다음과 같습니다.
public void writeFieldBegin(TField field) throws TException {
    this.writeByte(field.type);
    this.writeI16(field.id);
}

보시다시피, 우선 바이트 그룹을 byte에 써서 이 필드의 유형을 표시하고, 여기 있는 TFiled AGEFIELD_DESC:
private static final TField AGE_FIELD_DESC = new TField("age", TType.I32, (short)1);

thrift에서 정의된 첫 번째 필드는 다음과 같습니다.
1: required i32 age;

여기서 TType의 정의는 다음과 같습니다.
public final class TType {
    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;

    public TType() {
    }
}

그러면 바이트 그룹의 첫 번째 요소는 i32라는 유형으로 8이다.
다음에 이 필드에 정의된 id를 쓸 것입니다.age 필드의 id는 1입니다. (여기는 두 바이트를 차지합니다.) 따라서 바이트 그룹의 다음 두 요소는 0, 1입니다.
name 필드에도 동일합니다.
출력된 바이트 배열의 각 값이 나타내는 의미:
8 //      i32
0, 1 //   id 1
0, 0, 0, 18 //   id 1(age)  , 4   
11 //      string
0, 2 //   id 2(name)
0, 0, 0, 4 //    name   , 4   
121, 97, 110, 111 // "yano" 4 ASCII (   UTF-8  )
0 //   

역서열화 과정
역정렬된 문은 다음과 같습니다.
Person parsePerson = new Person();
TDeserializer deserializer = new TDeserializer();
deserializer.deserialize(parsePerson, bytes);

Person 클래스의 read 함수:
  public void read(TProtocol iprot) throws TException {
    TField field;
    iprot.readStructBegin();
    while (true)
    {
      field = iprot.readFieldBegin();
      if (field.type == TType.STOP) { 
        break;
      }
      switch (field.id) {
        case 1: // AGE
          if (field.type == TType.I32) {
            this.age = iprot.readI32();
            setAgeIsSet(true);
          } else { 
            TProtocolUtil.skip(iprot, field.type);
          }
          break;
        case 2: // NAME
          if (field.type == TType.STRING) {
            this.name = iprot.readString();
          } else { 
            TProtocolUtil.skip(iprot, field.type);
          }
          break;
        default:
          TProtocolUtil.skip(iprot, field.type);
      }
      iprot.readFieldEnd();
    }
    iprot.readStructEnd();

    // check for required fields of primitive type, which can't be checked in the validate method
    if (!isSetAge()) {
      throw new TProtocolException("Required field 'age' was not found in serialized data! Struct: " + toString());
    }
    validate();
  }

코드도 간단명료하다. 먼저 바이트 그룹에서 TField(5바이트, 1바이트 유형+4바이트 id)를 읽은 다음에 id에 따라 해당하는 필드에 값을 부여한다.
그중에 많은 세부 사항이 있으니 일일이 소개하지 않겠다.나도 원본 코드를 정확하게 쓰는 것보다 못하다.
Google Protocol Buffers와 비교
Google Protocol Buffers의 서열화 바이트 코드, Google Protocol Buffers 서열화 알고리즘 분석을 분석한 적이 있습니다.서열화된 바이트 배열의 차이가 여전히 매우 크다고 느낀다.
  • Thrift의 바이트 코드는 치밀하지 않다. 예를 들어 필드마다 id가 4바이트를 차지하고 유형이 1바이트를 차지한다.Google Protocol Buffers의 필드 id와 형식은 같은 바이트를 차지하고, i32와 같은 형식은 varint를 사용하여 그룹의 길이를 줄일 수 있습니다.
  • Thrift에서 생성한 자바 코드는 간결하고 코드 실현도 간결하다.Google Protocol Buffers에서 생성한 Java 코드는 걸핏하면 수천 줄...
  • Thrift는 단순히 서열화 프로토콜이 아니라 rpc 호출 프레임워크이다.그런 면에서 Google Protocol Buffers는 전혀 할 수 없습니다.
  • 좋은 웹페이지 즐겨찾기