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
参考资料
[1] Apache Thrift
编译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代码。
编译命令不对,正解如下:
g++ *.cpp -I/usr/include/thrift -I. -DHAVE_INTTYPES_H -DHAVE_NETINET_IN_H -lthrift -lpthread