当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《ApacheThrift:在列表之前使用"可选"时,C++服务器似乎不能正确返回它》经验,为你挑选了1个好方法。

嗨!每一个,我都有类似这个问题:

关于节俭功能返回列表

当列表具有"可选"修饰符时,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 strList,
    2: optional list pairList
}

service Test {

    Result listTest()

}

这是C++代码(服务器):

#include "gen-cpp/Test.h"
#include 
#include 
#include 
#include 

#include 

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 handler(new TestHandler());
    shared_ptr processor(new TestProcessor(handler));
    shared_ptr serverTransport(new TServerSocket(port));
    shared_ptr transportFactory(new TBufferedTransportFactory());
    shared_ptr 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> JensG..:

我错了,这不是一个真正的错误,它只是一些不幸的设计,并非真正的万无一失,并允许用户制作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();
  }
  //...
}

节俭有三种requiredness的:required,optional和默认.如果既未指定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标志来检查的,标志由一个bitset组成,每个字段不包含一位required.如果设置了相应的位标志__isset,则可以使用字段值.如果不存在位标志,则字段值尚未初始化,因此不应使用.

到目前为止,这不会是一个很大的问题.但是有一个陷阱,你设法击中:默认和optional字段可以在C++中访问和使用,即使没有设置位标志因为它们就在那里.在默认要求的情况下,这不是什么大问题:您分配您的值,并且由于该字段始终被写入,基本上没有任何不好的事情发生.当字段被反序列化时,字段的位标志被设置(参见生成的代码).

但是,当您选择加入时,事情会发生变化optional:现在突然有责任通过__isset直接访问或通过生成的setter方法正确设置标志,在我们的例子中这个:

void Xtruct2::__set_struct_thing(const Xtruct& val) {
  this->struct_thing = val;
__isset.struct_thing = true;
}

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

推荐阅读
和谐啄木鸟
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有