Apache Thrift:在列表之前使用“可选”时,C ++服务器似乎不能正确返回

时间:2015-12-05 02:02:54

标签: c++ perl thrift

嗨!每个人,我都遇到与此类似的问题:

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

1 个答案:

答案 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有三种要求:requiredoptional和默认值。如果未指定requiredoptional,则隐含地假定后者(这就是为什么没有特定关键字用于默认要求)。阅读和写作这些领域的语义如下:

    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;
}

我的假设是不应该访问尚未设置的可选字段,但事实证明这似乎是设计的。我仍然认为这里的设计容易出错。