Thrift的required和optional源码分析

| 2014年1月11日

thrift的数据类型定义的时候和protobuf(后面简称pb)一样也有requried和optional标签,意义是类似的,required标示改消息在序列化之前必须设置该字段值,如果不设置则无法序列化,当然也就更无法反序列化,该标签标示的字段是必填字段;而optional字段则标示该字段可赋值也可以不赋值,当然不赋值的结果是在序列化之后的数据中该字段被标示为无值字段,就是说直接访问获取该值是不行的,必须先判断是否设置了该字段的值,再去读值;这样作的好处是在协议扩充和变更时可以较为灵活的设计程序,而且在传输上也就减少了不必要的字段传输。

pb是必须选择requied或optional之一,如不没有标示,用proto编译是会报错的:

protoc --cpp_out ./ test.proto

test.proto:4:5: Expected "required", "optional", or "repeated".

1 message Person {

2 required string name = 1;

3 required int32 id = 2;

4 string email = 3;

5 }

    但是thrift还有无标签类型数据,也是因为thrift支持了跟多的协议,而pb可以说只是支持一种数据传输协议。thrift的无标签字段如果没有赋值那么就是空的,在传输过程中也会继续传输该值,今天来分析一下。首先写一个测试的thrift文件来编译:

namespace cpp xlight

struct StUser {

1: required i32 userId;

2: optional string userName;

4: string language;

}

/// 编译

helight:rpc$thrift --gen cpp -out ./ test.thrift

上面的测试文件可以看出,有些字段设置了requied,有些字段设置了optional,有些字段是什么也没有设置,就只是字段类型和字段名。先看看编译后的文件:test_types.h

typedef struct _StUser__isset {

_StUser__isset() : userName(false), language(false) {}

bool userName;

bool language;

} _StUser__isset;



class StUser {

public:



static const char* ascii_fingerprint; // = "76285C3D933C871361DFACF1222DDAAE";

static const uint8_t binary_fingerprint[16]; // = {0x76,0x28,0x5C,0x3D,0x93,0x3C,0x87,0x13,0x61,0xDF,0xAC,0xF1,0x22,0x2D,0xDA,0xAE};



StUser() : userId(0), userName(), language() {

}



virtual ~StUser() throw() {}



int32_t userId;

std::string userName;

std::string language;

_StUser__isset __isset;



void __set_userId(const int32_t val) {

userId = val;

}



void __set_userName(const std::string& val) {

userName = val;

__isset.userName = true;

}



void __set_language(const std::string& val) {

language = val;

}

bool operator == (const StUser & rhs) const

{

if (!(userId == rhs.userId))

return false;

if (__isset.userName != rhs.__isset.userName)

return false;

else if (__isset.userName && !(userName == rhs.userName))

return false;

if (!(language == rhs.language))

return false;

return true;

}

bool operator != (const StUser &rhs) const {

return !(*this == rhs);

}



bool operator < (const StUser &amp; ) const;



uint32_t read(::apache::thrift::protocol::TProtocol* iprot);

uint32_t write(::apache::thrift::protocol::TProtocol* oprot) const;



};

可以看出thrift在cpp中把struct转换成了class,生成的代码中都把成员变量进行初始化设置,同时在代码中引入了另外一个结构体_StUser__isset 来记录这些字段的标签行为,对于requried的字段isset中没有bool变量标示,但是对于optional和无标签字段都有,然后我们再来看看它的序列化和反序列化函数—write和read函数。在test_types.cpp:

    代码过多就不粘贴了:主要是在write的时候会判断optional是否被设置,如果没有被设置则不进行序列化。其它两种字段都会序列化。

if (this->__isset.userName) {

xfer += oprot->writeFieldBegin("userName", ::apache::thrift::protocol::T_STRING, 2);

xfer += oprot->writeString(this->userName);

xfer += oprot->writeFieldEnd();

}

在反序列化函数中:对requried字段做了标示,如果这个字段没有则会抛出异常,其它两个字段在反序列化之后都会设置其isset变量。从代码中可以看出thrift对requried的字段只是作了默认值处理,让它在数据结构初始化的时候就有值,避免在序列化的时候无值。而pb对这块的处理是如果不设置requried字段,程序运行会失败。

case 1:

if (ftype == ::apache::thrift::protocol::T_I32) {

xfer += iprot->readI32(this->userId);

isset_userId = true;

} else {

xfer += iprot->skip(ftype);

}

break;

case 2:

if (ftype == ::apache::thrift::protocol::T_STRING) {

xfer += iprot->readString(this->userName);

this->__isset.userName = true;

} else {

xfer += iprot->skip(ftype);

}

break;

case 4:

if (ftype == ::apache::thrift::protocol::T_STRING) {

xfer += iprot->readString(this->language);

this->__isset.language = true;

} else {

xfer += iprot->skip(ftype);

}

break;

default:

xfer += iprot->skip(ftype);

break;

。。。

if (!isset_userId)

throw TProtocolException(TProtocolException::INVALID_DATA);
看完本文有收获?请分享给更多人

关注「黑光技术」,关注大数据+微服务