原因
經常使用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字段還有此效果,即必須有該字段。
而
第三個問題則從頭文件中對象的構造函數可以看出原因。
到這里,上面的問題就全部都清楚了。