嗨!每个人,我都遇到与此类似的问题:
regarding thrift function return list
当列表具有“可选”修饰符时,thrift C ++服务器将始终将其作为null / undef返回。
此外,如果可选列表包含的结构,任何结构的字段,都不能设置为“可选”,或者将返回null / undef值。
删除所有“可选”修饰符后,一切正常。
有人可以告诉我为什么我不能在列表前使用“可选”吗?
以下是节俭文件:
namespace cpp thrifttest
namespace perl thrifttest
struct Pair {
1: optional string a,
2: optional string b
}
struct Result {
1: optional list<string> strList,
2: optional list<Pair> pairList
}
service Test {
Result listTest()
}
这是C ++代码(服务器):
#include "gen-cpp/Test.h"
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/server/TSimpleServer.h>
#include <thrift/transport/TServerSocket.h>
#include <thrift/transport/TBufferTransports.h>
#include <iostream>
using namespace std;
using namespace ::apache::thrift;
using namespace ::apache::thrift::protocol;
using namespace ::apache::thrift::transport;
using namespace ::apache::thrift::server;
using boost::shared_ptr;
using namespace ::thrifttest;
class TestHandler : virtual public TestIf {
public:
TestHandler() {
// Your initialization goes here
}
void listTest(Result& _return) {
_return.strList.push_back("Test");
_return.strList.push_back("one level list");
cout << "strList size: " << _return.strList.size() << endl;
Pair pair;
pair.a = "Test";
pair.b = "two level list";
_return.pairList.push_back(pair);
cout << "pairList size: " << _return.pairList.size() << endl;
printf("call listTest\n");
}
};
int main(int argc, char **argv) {
int port = 9595;
shared_ptr<TestHandler> handler(new TestHandler());
shared_ptr<TProcessor> processor(new TestProcessor(handler));
shared_ptr<TServerTransport> serverTransport(new TServerSocket(port));
shared_ptr<TTransportFactory> transportFactory(new TBufferedTransportFactory());
shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory());
TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory);
server.serve();
return 0;
}
这是perl代码(客户端):
#!/usr/bin/perl
use v5.12;
use warnings;
use autodie;
use utf8;
use Data::Dumper;
use lib 'gen-perl';
use thrifttest::Test;
use thrifttest::Constants;
use thrifttest::Types;
use Thrift;
use Thrift::BinaryProtocol;
use Thrift::Socket;
use Thrift::BufferedTransport;
my $socket = new Thrift::Socket('localhost', 9595);
my $transport = new Thrift::BufferedTransport($socket, 1024, 1024);
my $protocol = new Thrift::BinaryProtocol($transport);
my $client = new thrifttest::TestClient($protocol);
eval {
$transport->open();
my $result = $client->listTest;
say Dumper($result);
$transport->close();
};
say $@ if $@;
C ++服务器输出:
strList size: 2
pairList size: 1
call listTest
perl客户端输出:
$VAR1 = bless( {
'pairList' => undef,
'strList' => undef
}, 'thrifttest::Result' );
PS:我的开发环境是CentOS 7,GCC 4.8.3,Perl 5.16,节俭0.9.3
答案 0 :(得分:5)
我错了,这不是一个真正的错误,只是一些不幸的设计并不是真正的傻瓜证明,并允许用户制作Dumb Things™。问题在于optional
运算符的语义以及它是如何为C ++实现的。
让我们假设我们有这条IDL:
struct Xtruct2
{
1: i8 byte_thing,
2: Xtruct struct_thing,
3: i32 i32_thing
}
生成的代码,特别是write()
方法,如下所示:
uint32_t Xtruct2::write(::apache::thrift::protocol::TProtocol* oprot) const {
//...
xfer += oprot->writeFieldBegin("struct_thing", ::apache::thrift::protocol::T_STRUCT, 2);
xfer += this->struct_thing.write(oprot);
xfer += oprot->writeFieldEnd();
//...
}
如果我们现在修改IDL并添加optional
说明符:
struct Xtruct2
{
1: i8 byte_thing,
2: optional Xtruct struct_thing,
3: i32 i32_thing
}
生成的代码看起来略有不同:
uint32_t Xtruct2::write(::apache::thrift::protocol::TProtocol* oprot) const {
//...
if (this->__isset.struct_thing) {
xfer += oprot->writeFieldBegin("struct_thing", ::apache::thrift::protocol::T_STRUCT, 2);
xfer += this->struct_thing.write(oprot);
xfer += oprot->writeFieldEnd();
}
//...
}
Thrift有三种要求:required
,optional
和默认值。如果未指定required
和optional
,则隐含地假定后者(这就是为什么没有特定关键字用于默认要求)。阅读和写作这些领域的语义如下:
requiredness write field? read field?
----------------------------------------------------------------------
required always always, must be present
(default) always if present, may be missing
optional only if set if present, may be missing
因此,与默认值相比,optional
更改的是write方法的行为。虽然始终会写入默认字段,但optional
字段仅写入有条件。这是通过__isset
标志检查的,标志由一个位集组成,每个字段一位不是required
。如果设置__isset
内的相应位标志,则可以使用字段值。如果位标志不存在,则字段值尚未初始化,因此不应使用。
到目前为止,这不会是一个很大的问题。但是有一个陷阱,你设法击中:默认和optional
字段可以在C ++ 中访问和使用,即使没有设置位标志因为它们就在那里。在默认要求的情况下,这不是什么大问题:您分配您的值,并且由于该字段始终被写入,基本上没有任何不好的事情发生。当字段被反序列化时,字段的位标志被设置(参见生成的代码)。
但是,当您选择加入optional
时,情况会发生变化:现在突然你负责正确设置标记,方法是直接访问__isset
或通过生成的setter方法,在我们的例子中这一个:
void Xtruct2::__set_struct_thing(const Xtruct& val) {
this->struct_thing = val;
__isset.struct_thing = true;
}
我的假设是不应该访问尚未设置的可选字段,但事实证明这似乎是设计的。我仍然认为这里的设计容易出错。