Thrift 学习笔记 (1)

Thrift 是 Facebook 开发的一个二进制通信协议,现在托管在 Apache 基金会。它使用一种自定义的接口描述语言描述使用的数据结构和接口,通过编译可以生成各种语言的接口,如 c++,python,php 等。由于支持生成多种语言,Thrift 可以作为多种语言之间的通信接口,更详细的介绍见参考资料 [1]。

这里使用的 Thrift版本是 0.8.0。

一个简单的echo server

首先编写对应的数据结构和接口描述文件:

namespace cpp Test

service EchoServer {
    string echo(1: string msg);
}

使用下面的命令生成对应的 c++ 接口文件:

thrift --gen cpp echoapi.thrift

默认会生成一个“gen-cpp”的文件夹,里面包含下列文件:

echoapi_constants.cpp echoapi_constants.h echoapi_types.cpp echoapi_types.h EchoServer.cpp EchoServer.h EchoServer_server.skeleton.cpp

其中 echoapi_constants.* 是在 .thrift 描述文件中的常量定义(例子里没有定义常量);echoapi_types.* 是在描述文件中自定义的数据结构(这里也没有自定义的数据结构);EchoServer.* 是接口代码(即上面的 service 结构);最后一个文件 EchoServer_server.skeleton.cpp 是 Thrift 生成的示例 server 程序。

进入 gen-cpp 文件夹,使用下面的命令来编译(Thrift 生成的 c++ 程序依赖于 Boost):

g++ *.cpp -I/usr/include/thrift -I. -DHAVE_INTTYPES_H -DHAVE_NETINET_IN_H -lthrift

.thrift 文件结构分析

对外提供的接口由 service 结构指定:

service SERVICE_NAME {
    RETURN_VALUE func(1: TYPE arg1, 2: TYPE arg2, ...);
}

其中 {SERVICE_NAME} 是对外提供接口的结构体名称,会生成同名的 {SERVICE_NAME}.cpp 和 {SERVICE_NAME}.h。在 {SERVICE_NAME}.h 中有一个基类 {SERVICE_NAME}If,其中定义了在 service 结构中描述的接口,每一个都是纯虚函数。例如在这里的 EchoServer.h 中有以下定义:

namespace Test {

class EchoServerIf {
 public:
  virtual ~EchoServerIf() {}
  virtual void echo(std::string& _return, const std::string& msg) = 0;
};

......
}

其中“msg”是传递给 echo() 的参数,“_return”是返回值。

在 .thrift 描述文件中支持以下数据类型:bool,byte,i16,i32,i64,double,string;支持以下容器:list,set,map,其中 list 对应于 c++ 中的 vector。

server端实现

要实现我们自己的服务,需要继承 EchoServerIf 类,实现其中的接口。例如在生成的 server 示例代码中有以下代码:

class EchoServerHandler : virtual public EchoServerIf {
 public:
  EchoServerHandler() {
    // Your initialization goes here
  }

  void echo(std::string& _return, const std::string& msg) {
    // Your implementation goes here
    printf("echo\n");
  }

};

要实现简单的 echo 功能,只需在 echo() 函数中添加一句:

_return = msg;

client端实现

在 EchoServer.h 和 EchoServer.cpp 中,Thrift 已经为我们生成了相应的 client 类 EchoServerClient,我们只需使用即可:

#include "EchoServer.h"

#include <iostream>
#include <string>
using namespace std;

#include <transport/TSocket.h>
#include <transport/TBufferTransports.h>
#include <protocol/TBinaryProtocol.h>

using namespace apache::thrift;
using namespace apache::thrift::protocol;
using namespace apache::thrift::transport;

using namespace Test;

int main(int argc, char **argv) {
    boost::shared_ptr<TSocket> socket(new TSocket("localhost", 9090));
    boost::shared_ptr<TTransport> transport(new TBufferedTransport(socket));
    boost::shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport));

    string ret;

    EchoServerClient client(protocol);
    transport->open();
    client.echo(ret, "hello, world");
    cout << "client get value -> " << ret << endl;
    transport->close();

    return 0;
}

编译命令和编译 server 示例代码一样。编译成功后先运行 server 端,然后运行 client 端,即可看到结果。

自定义数据结构

下面是对一个数组中的每个数都乘以 n 的服务 Scale:

namespace cpp Test

struct NumList {
    1: required list<i32> numlist,
}

service NumOps {
    NumList scale(1: NumList nl, 2: i32 multiplier);
}

这里定义了一个结构体 NumList,包含了一个整数 list,并且这个元素是必须的(由“定义前的 required”指定)。接口 scale() 接收两个参数,第一个是一个整数 list,第二个是乘数。

使用“thrift --gen cpp numops.thrift”生成代码后,在 gen-cpp/NumOps.h 中可以找到接口的定义,在 numops_types.h 中可以找到 NumList 的定义:

class NumList {
 public:

  static const char* ascii_fingerprint; // = "A803C54EAD95E24D90C5E66FB98EA72B";
  static const uint8_t binary_fingerprint[16]; // = {0xA8,0x03,0xC5,0x4E,0xAD,0x95,0xE2,0x4D,0x90,0xC5,0xE6,0x6F,0xB9,0x8E,0xA7,0x2B};

  NumList() {
  }

  virtual ~NumList() throw() {}

  std::vector<int32_t>  numlist;

  void __set_numlist(const std::vector<int32_t> & val) {
    numlist = val;
  }

可以看到,在 .thrift 描述文件中定义的“list numlist”已经转化成 c++ 中的“std::vector numlist”。其它各个部分和 echo server 类似。

参考资料

[1] Apache Thrift

Comments (2)

  1. 编译client时用如下编译命令:
    g++ EchoServer_client.cpp echoapi_constants.cpp echoapi_types.cpp EchoServer.cpp -o EchoClient -I/usr/include/thrift -I. -DHAVE_INTTYPES_H -DHAVE_NETINET_IN_H -lthrift -lpthread

    EchoServer_client.cpp是client代码。

  2. 编译命令不对,正解如下:
    g++ *.cpp -I/usr/include/thrift -I. -DHAVE_INTTYPES_H -DHAVE_NETINET_IN_H -lthrift -lpthread

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注