原因
经常使用thrift来编写rpc通信,但是对下面两个问题还是有些疑惑
- thrift 的required、optional和不写有什么区别
- optional不设置isset的话被传输后值?
实验
今天就自己编写代码测试了一下。如下:
定义book.thrift 如下:
1: namespace cpp codelab
2:
3: struct Book {
4: 1: i32 book_id
5: 2: string name
6: 3: optional string optional_attr,
7: 4: optional string optional_default_val_attr = "optional_default_val_attr",
8: 5: string default_attr,
9: 8: string default_val_attr = "default_val_attr",
10: 10: required string required_attr,
11: 11: required string required_default_val_attr = "equired_default_val_attr",
12: }
client代码如下:
1: int main(int argc, char **argv) {
2: boost::shared_ptr<TSocket> socket(new TSocket("localhost", 9090));
3: boost::shared_ptr<TTransport> transport(new TFramedTransport(socket));
4: boost::shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport));
5:
6: HelloBookClient client(protocol);
7: transport->open();
8: Book book;
9: book.name = "hello thrift";
10: printf("book __isset.name: %d\n", book.__isset.name);
11: printf("book name: %s\n", book.name.c_str());
12: printf("book __isset.optional_attr: %d\n", book.__isset.optional_attr);
13: printf("book optional_attr: %s\n", book.optional_attr.c_str());
14: printf("book __isset.optional_default_val_attr: %d\n",
15: book.__isset.optional_default_val_attr);
16: printf("book optional_default_val_attr: %s\n",
17: book.optional_default_val_attr.c_str());
18: printf("book __isset.default_attr: %d\n", book.__isset.default_attr);
19: printf("book default_attr: %s\n", book.default_attr.c_str());
20: printf("book __isset.default_val_attr: %d\n",
21: book.__isset.default_val_attr);
22: printf("book default_val_attr: %s\n", book.default_val_attr.c_str());
23: // printf("book __isset.required_attr: %d\n",
24: // book.__isset.required_attr);
25: printf("book required_attr: %s\n", book.required_attr.c_str());
26: // printf("book __isset.required_default_val_attr: %d\n",
27: // book.__isset.required_default_val_attr);
28: printf("book required_default_val_attr: %s\n",
29: book.required_default_val_attr.c_str());
30:
31: client.ping(book);
32: transport->close();
33:
34: return 0;
35:
Server端代码:
1: class HelloBookHandler : virtual public HelloBookIf {
2: public:
3: HelloBookHandler() {
4: // Your initialization goes here
5: }
6:
7: void ping(const codelab::Book& book) {
8: // Your implementation goes here
9: printf("book __isset.name: %d\n", book.__isset.name);
10: printf("book name: %s\n", book.name.c_str());
11: printf("book __isset.optional_attr: %d\n", book.__isset.optional_attr);
12: printf("book optional_attr: %s\n", book.optional_attr.c_str());
13: printf("book __isset.optional_default_val_attr: %d\n",
14: book.__isset.optional_default_val_attr);
15: printf("book optional_default_val_attr: %s\n",
16: book.optional_default_val_attr.c_str());
17: printf("book __isset.default_attr: %d\n", book.__isset.default_attr);
18: printf("book default_attr: %s\n", book.default_attr.c_str());
19: printf("book __isset.default_val_attr: %d\n",
20: book.__isset.default_val_attr);
21: printf("book default_val_attr: %s\n", book.default_val_attr.c_str());
22: // printf("book __isset.required_attr: %d\n",
23: // book.__isset.required_attr);
24:
25: printf("book required_attr: %s\n", book.required_attr.c_str());
26: // printf("book __isset.required_default_val_attr: %d\n",
27: // book.__isset.required_default_val_attr);
28: printf("book required_default_val_attr: %s\n",
29: book.required_default_val_attr.c_str());
30: }
31: };
32:
33: int main(int argc, char **argv) {
34: int port = 9090;
35: boost::shared_ptr<HelloBookHandler> handler(new HelloBookHandler());
36: boost::shared_ptr<TProcessor> processor(new HelloBookProcessor(handler));
37: boost::shared_ptr<TServerTransport> serverTransport(new TServerSocket(port));
38: boost::shared_ptr<TTransportFactory> transportFactory(
39: new TFramedTransportFactory());
40: boost::shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory());
41:
42: TSimpleServer server(processor, serverTransport,
43: transportFactory, protocolFactory);
44: server.serve();
45: return 0;
46: }
Client端执行结果:
Server端执行结果:
而对client代码修改如下,
server端的执行结果分别如下:
即没有设置isset为true的时候optional_attr的值到server被丢失了。而设置为true之后才能在server获取到
经过上面的测试,得到以下结论:
- required字段没有__isset属性, 而默认的(就是既没有required,也没有optional)和optional的属性有该方法。
- 创建对象的时候,optional和默认的__isset属性为false,同样不设置该属性,而在经过thrift rpc传输之后,server端的默认的__isset属性为true,而optional的__isset的属性为true。
- 有默认值的属性,不赋值的话,其值就是thrift中的默认值。
- optional的如果没有设置__isset为true,则经过rpc传输(或者说是经过序列化再反序列化)之后,其值会丢失。
为什么会有上面的结果呢:
原因分析
查看thrift生成的文件:
其中book_types.h 如下:
1: /**
2: * Autogenerated by Thrift
3: *
4: * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
5: */
6: #ifndef book_TYPES_H
7: #define book_TYPES_H
8:
9: #include <Thrift.h>
10: #include <TApplicationException.h>
11: #include <protocol/TProtocol.h>
12: #include <transport/TTransport.h>
13:
14:
15:
16: namespace codelab {
17:
18: typedef struct _Book__isset {
19: _Book__isset() : book_id(false), name(false), optional_attr(false), optional_default_val_attr(false), default_attr(false), default_val_attr(false) {}
20: bool book_id;
21: bool name;
22: bool optional_attr;
23: bool optional_default_val_attr;
24: bool default_attr;
25: bool default_val_attr;
26: } _Book__isset;
27:
28: class Book {
29: public:
30:
31: static const char* ascii_fingerprint; // = "EC22AAB82386E1FAA959FB075574467D";
32: static const uint8_t binary_fingerprint[16]; // = {0xEC,0x22,0xAA,0xB8,0x23,0x86,0xE1,0xFA,0xA9,0x59,0xFB,0x07,0x55,0x74,0x46,0x7D};
33:
34: Book() : book_id(0), name(""), optional_attr(""), optional_default_val_attr("optional_default_val_attr"), default_attr(""), default_val_attr("default_val_attr"), required_attr(""), required_default_val_attr("equired_default_val_attr") {
35: }
36:
37: virtual ~Book() throw() {}
38:
39: int32_t book_id;
40: std::string name;
41: std::string optional_attr;
42: std::string optional_default_val_attr;
43: std::string default_attr;
44: std::string default_val_attr;
45: std::string required_attr;
46: std::string required_default_val_attr;
47:
48: _Book__isset __isset;
49:
50: bool operator == (const Book & rhs) const
51: {
52: if (!(book_id == rhs.book_id))
53: return false;
54: if (!(name == rhs.name))
55: return false;
56: if (__isset.optional_attr != rhs.__isset.optional_attr)
57: return false;
58: else if (__isset.optional_attr && !(optional_attr == rhs.optional_attr))
59: return false;
60: if (__isset.optional_default_val_attr != rhs.__isset.optional_default_val_attr)
61: return false;
62: else if (__isset.optional_default_val_attr && !(optional_default_val_attr == rhs.optional_default_val_attr))
63: return false;
64: if (!(default_attr == rhs.default_attr))
65: return false;
66: if (!(default_val_attr == rhs.default_val_attr))
67: return false;
68: if (!(required_attr == rhs.required_attr))
69: return false;
70: if (!(required_default_val_attr == rhs.required_default_val_attr))
71: return false;
72: return true;
73: }
74: bool operator != (const Book &rhs) const {
75: return !(*this == rhs);
76: }
77:
78: bool operator < (const Book & ) const;
79:
80: uint32_t read(::apache::thrift::protocol::TProtocol* iprot);
81: uint32_t write(::apache::thrift::protocol::TProtocol* oprot) const;
82:
83: };
84:
85: } // namespace
86:
87: #endif
book_types.cpp 文件如下:
1: /**
2: * Autogenerated by Thrift
3: *
4: * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
5: */
6: #include "codelab/thrift/proto/gen-cpp/book_types.h"
7:
8: namespace codelab {
9:
10: const char* Book::ascii_fingerprint = "EC22AAB82386E1FAA959FB075574467D";
11: const uint8_t Book::binary_fingerprint[16] = {0xEC,0x22,0xAA,0xB8,0x23,0x86,0xE1,0xFA,0xA9,0x59,0xFB,0x07,0x55,0x74,0x46,0x7D};
12:
13: uint32_t Book::read(::apache::thrift::protocol::TProtocol* iprot) {
14:
15: uint32_t xfer = 0;
16: std::string fname;
17: ::apache::thrift::protocol::TType ftype;
18: int16_t fid;
19:
20: xfer += iprot->readStructBegin(fname);
21:
22: using ::apache::thrift::protocol::TProtocolException;
23:
24: bool isset_required_attr = false;
25: bool isset_required_default_val_attr = false;
26:
27: while (true)
28: {
29: xfer += iprot->readFieldBegin(fname, ftype, fid);
30: if (ftype == ::apache::thrift::protocol::T_STOP) {
31: break;
32: }
33: switch (fid)
34: {
35: case 1:
36: if (ftype == ::apache::thrift::protocol::T_I32) {
37: xfer += iprot->readI32(this->book_id);
38: this->__isset.book_id = true;
39: } else {
40: xfer += iprot->skip(ftype);
41: }
42: break;
43: case 2:
44: if (ftype == ::apache::thrift::protocol::T_STRING) {
45: xfer += iprot->readString(this->name);
46: this->__isset.name = true;
47: } else {
48: xfer += iprot->skip(ftype);
49: }
50: break;
51: case 3:
52: if (ftype == ::apache::thrift::protocol::T_STRING) {
53: xfer += iprot->readString(this->optional_attr);
54: this->__isset.optional_attr = true;
55: } else {
56: xfer += iprot->skip(ftype);
57: }
58: break;
59: case 4:
60: if (ftype == ::apache::thrift::protocol::T_STRING) {
61: xfer += iprot->readString(this->optional_default_val_attr);
62: this->__isset.optional_default_val_attr = true;
63: } else {
64: xfer += iprot->skip(ftype);
65: }
66: break;
67: case 5:
68: if (ftype == ::apache::thrift::protocol::T_STRING) {
69: xfer += iprot->readString(this->default_attr);
70: this->__isset.default_attr = true;
71: } else {
72: xfer += iprot->skip(ftype);
73: }
74: break;
75: case 8:
76: if (ftype == ::apache::thrift::protocol::T_STRING) {
77: xfer += iprot->readString(this->default_val_attr);
78: this->__isset.default_val_attr = true;
79: } else {
80: xfer += iprot->skip(ftype);
81: }
82: break;
83: case 10:
84: if (ftype == ::apache::thrift::protocol::T_STRING) {
85: xfer += iprot->readString(this->required_attr);
86: isset_required_attr = true;
87: } else {
88: xfer += iprot->skip(ftype);
89: }
90: break;
91: case 11:
92: if (ftype == ::apache::thrift::protocol::T_STRING) {
93: xfer += iprot->readString(this->required_default_val_attr);
94: isset_required_default_val_attr = true;
95: } else {
96: xfer += iprot->skip(ftype);
97: }
98: break;
99: default:
100: xfer += iprot->skip(ftype);
101: break;
102: }
103: xfer += iprot->readFieldEnd();
104: }
105:
106: xfer += iprot->readStructEnd();
107:
108: if (!isset_required_attr)
109: throw TProtocolException(TProtocolException::INVALID_DATA);
110: if (!isset_required_default_val_attr)
111: throw TProtocolException(TProtocolException::INVALID_DATA);
112: return xfer;
113: }
114:
115: uint32_t Book::write(::apache::thrift::protocol::TProtocol* oprot) const {
116: uint32_t xfer = 0;
117: xfer += oprot->writeStructBegin("Book");
118: xfer += oprot->writeFieldBegin("book_id", ::apache::thrift::protocol::T_I32, 1);
119: xfer += oprot->writeI32(this->book_id);
120: xfer += oprot->writeFieldEnd();
121: xfer += oprot->writeFieldBegin("name", ::apache::thrift::protocol::T_STRING, 2);
122: xfer += oprot->writeString(this->name);
123: xfer += oprot->writeFieldEnd();
124: if (this->__isset.optional_attr) {
125: xfer += oprot->writeFieldBegin("optional_attr", ::apache::thrift::protocol::T_STRING, 3);
126: xfer += oprot->writeString(this->optional_attr);
127: xfer += oprot->writeFieldEnd();
128: }
129: if (this->__isset.optional_default_val_attr) {
130: xfer += oprot->writeFieldBegin("optional_default_val_attr", ::apache::thrift::protocol::T_STRING, 4);
131: xfer += oprot->writeString(this->optional_default_val_attr);
132: xfer += oprot->writeFieldEnd();
133: }
134: xfer += oprot->writeFieldBegin("default_attr", ::apache::thrift::protocol::T_STRING, 5);
135: xfer += oprot->writeString(this->default_attr);
136: xfer += oprot->writeFieldEnd();
137: xfer += oprot->writeFieldBegin("default_val_attr", ::apache::thrift::protocol::T_STRING, 8);
138: xfer += oprot->writeString(this->default_val_attr);
139: xfer += oprot->writeFieldEnd();
140: xfer += oprot->writeFieldBegin("required_attr", ::apache::thrift::protocol::T_STRING, 10);
141: xfer += oprot->writeString(this->required_attr);
142: xfer += oprot->writeFieldEnd();
143: xfer += oprot->writeFieldBegin("required_default_val_attr", ::apache::thrift::protocol::T_STRING, 11);
144: xfer += oprot->writeString(this->required_default_val_attr);
145: xfer += oprot->writeFieldEnd();
146: xfer += oprot->writeFieldStop();
147: xfer += oprot->writeStructEnd();
148: return xfer;
149: }
150:
151: } // namespace
仔细查看代码,上面的问题都可以得到答案:
针对第一个问题:可以从 book_types.h中看到,有一个Book_isset的结构体的成员,
而我们看下该_Book__isset结构体的定义如下:
可有看到,该结构体重只包含了非required的属性。因此只有非required属性才有__isset的属性。
针对第二个问题:从_Book_isset的构造函数可以看出,对象在构造的时候,其__isset的属性都是false。而rpc传出的时候就涉及到对象的序列化和反序列化。
就设计该对象的read和write,我们看下相应的函数:
write函数将对象转成字符串。代码如下(限于屏幕值截取部分分析)
从代码可以看出,整个操作以writeStructBegin开始,以writeStructEnd结束。
其对于非optional的字段,是字节调用writeFieldBegin(fileld_name, FieldType, id); writeType() writeFieldEnd 方法处理的。
注意这里write时涉及了thrift中定义的序号、类型、和名字。因此thrift序号是不可以随意变动的。
而对于optional的字段则略有不同,其首先需要判断属性的__isset.是否为true,只有为true时才调用相应的write处理。 因此如果optional
字段不设置其__isset,则序列化的时候会不处理,(解答了第四个问题)
反序列就是read操作:
read是write的逆过程:
然后是一个fid的switch逻辑,根据fid来处理对应的属性
在读到STOP的时候终止while循环:
这个read的逻辑是对应的,read的结束如下:
在read操作中对于非required字段,读取时将其isset设置为true。这也就是为什么server端isset的值是设置的。
而对于required字段,注意read 最后
其对于required字段会进行check,如果没有,则抛出异常。
因此required字段还有此效果,即必须有该字段。
而第三个问题则从头文件中对象的构造函数可以看出原因。
到这里,上面的问题就全部都清楚了。