前言
Thrift是Facebook提供的一个跨语言的服务部署框架,可以实现客户端和服务器远程过程调用。相较于Google的grpc框架,Thrift对三方库依赖更少,编译更简单,并且运行效率也更高。Thrift只依赖boost、openssl和libevent三个库,本次测试编译的都是静态库。下面详细介绍thrift 0.15.0的编译过程。
一、编译thrift
1.1 编译前准备
编译环境:win11+vs2019
编译前下载源码库
boost_1_78_0.tar.gz boost下载地址
openssl-1.1.1.tar.gz openssl下载地址
libevent-2.1.10-stable.tar.gz libevent下载地址
thrift-0.15.0.tar.gz thrift-0.15.0下载地址 源代码包
thrift-0.15.0.exe thrift-0.15.0.exe 用于在Windows下生成目标语言的桩代码
网上教程太多太多,可能别人博主能成功,你自己不能编译成功,所以还是的自己实实在在的编译一遍。我主要参考如下几个才编译出来Thrift框架-Windows-C++ x64编译、thrift在windows上编译
但是现象也不是和上述博主一样,所以将这次编译过程记录一下,以供参考。
1.2 thrift源码编译
进入thrift-0.15.0\lib\cpp,打开thrift.sln解决方案,有libthrift,libthriftnb两个工程。两个工程的区别是,libthriftnb工程是非阻塞(non-blocking)模式的服务器,非阻塞模式需要依赖libevent库。
1.2.1 libthrift工程配置
修改代码:
(1)修改thrift-0.15.0\lib\cpp\src\thrift\thrift_export.h,添加#define thrift_EXPORTS
打开<thrift目录>\lib\cpp\src\thrift\thrift_export.h,在文件中添加#define thrift_EXPORTS。
由于我们需要编译thrift生成静态链接库,因此需要添加定义:#define thrift_EXPORTS,将THRIFT_EXPORT设置为__declspec(dllexport)。
(2)如果出现下面的错误,则将报错的地方的#include <thrift/config.h>
则修改为#include <thrift/thrift-config.h>
(3)如果concurrency筛选器下面的部分源文件名称与源码目录下的文件名称不一致,需要移除,我的已经移除了。
1.2.2 libthriftnb工程配置
二、C++应用开发
- 写自己的接口描述文件(Interface Description File)
- 用thrift-0.15.0.exe生成目标语言(C++)代码
- 服务端程序引入生成的代码,实现RPC业务代码
- 客户端程序引入生成的代码,调用远程服务接口
2.1 thrift IDL文件
thrift IDL不支持无符号的数据类型,因为很多编程语言中不存在无符号类型,thrift支持一下几种基本的数据类型
- byte: 有符号字节
- i16: 16位有符号整数
- i32: 32位有符号整数
- i64: 63位有符号整数
- double: 64位浮点数
- string: 字符串
此外thrift还支持以下容器类型:
- list: 一系列由T类型的数据组成的有序列表,元素可以重复;
- set: 一系列由T类型的数据组成的无序集合,元素不可重复;
- map: 一个字典结构,Key为K类型,Value为V类型,相当于c++中;
thrift容器中元素的类型可以是除了service之外的任何类型,包括exception。thrift也支持文件包含,相当于CPP中的include。
#
、//
、/**/
都可以作为thrift文件中的注释。
thrift提供两个关键字required
和optional
,分别用于表示对应的字段是必填的还是可选的(推荐尽量使用optional
)
这里给出一个thrift的IDL基本语法列表,详细用法可以去官网查找
namespace cpp thrift.Test
//typedef 用法
typedef i32 MyInt32;
typedef string MyString;
typedef i32 UserId;
//struct 结构定义
struct TypedefTestStruct
{
1: MyInt32 field_MyInt32;
2: MyString field_MyString;
3: i32 field_Int32;
4: string filed_string;
}
//enum 枚举定义
enum Numberz
{
ONE = 1,
TWO,
THREE,
FIVE = 5,
SIX,
EIGHT = 8
}
//const 用法
const Numberz myNumberz = Numberz.ONE;
struct Bonk
{
1: string message,
2: i32 type
}
//类型嵌套
struct Xtruct
{
1: string string_thing,
2: i8 byte_thing,
3: i32 i32_thing,
4: i64 i64_thing
}
struct Xtruct2
{
1: i8 byte_thing,
2: Xtruct struct_thing,
3: i32 i32_thing
}
//支持map list set类型分别对应C++中的 map = stl::map list = stl::vector set = stl::set
typedef map<string, Bonk> MapType
struct Insanity
{
1: map<Numberz, UserId> userMap;
2: list<Xtruct> xtructs;
}
struct CrazyNesting
{
1: string string_field,
2: optional set<Insanity> set_field;
3: required list<map<set<i32>, map<i32,set<list<map<Insanity,string>>>>>> list_field,
4: binary binary_field
}
//union用法
union SomeUnion
{
1: map<NumberZ, UserId> map_thing,
2: string string_thing,
3: i32 i32_thing,
4: Xtruct3 xtruct_thing,
5: Insanity insanity_thing
}
//exception 异常
exception Xception
{
1: i32 errorCode,
2: string message
}
exception Xception2
{
1: i32 errorCode,
2: Xtruct struct_thing
}
// empty struct
struct EmptyStruct{}
struct OneField
{
1: EmptyStruct field;
}
//service 定义的一组rpc服务,一般是抽象出来的接口调用
service ThriftTest
{
void testVoid(),
string testString(1: string thing),
bool testBool(1: bool thing),
i8 testByte(1: i8 thing),
i32 testI32(1: i32 thing),
i64 testI64(1: i64 thing),
Xtruct testStruct(1: Xtruct thing),
Xtruct2 testNest(1: Xtruct2 thing),
map<string, string> testStringMap(1: map<string, string> thing),
set<i32> testSet(1: set<i32> thing),
list<i32> testList(1: list<i32> thing),
Numberz testEnum(1: Numberz thing),
map<i32, map<i32,i32>> testMapMap(1: i32 hello),
map<UserId, map<Numberz,Insanity>> testInsanity(1: Insanity argument),
Xtruct testMulti(1: i8 arg0, 2: i32 arg1, 3: i64 arg2, 4: map<i16, string> arg3, 5: Numberz arg4, 6: UserId arg5),
void testException(1: string arg) throws(1: Xception err1),
Xtruct testMultiException(1: string arg0, 2: string arg1) throws(1: Xception err1, 2: Xception2 err2),
oneway void testOneway(1:i32 secondsToSleep)
}
对于返回void的函数,thrift仍然会确保函数返回,这样表示这个函数被正确执行,且服务端已有返回信息了。但是如果给void的函数前加上oneway,此函数以异步模式执行,这样在调用此函数后,函数会立即返回,那么此函数的返回只能表示数据已经进入传输层,并不能表示服务器端已经接收到并返回了数据。
所以oneway不安全,但是效率高一些,在不要求一定要发送成功的情况下(可靠性要求不高)可以使用。
2.2 定义IDL文件并生成目标代码
namespace cpp thrift.Test
service ThriftTest
{
i32 add(1: i32 arg1,2: i32 arg2),
void showName(),
}
thrift-0.15.0.exe -r --gen cpp test.thrift生成对应的c++代码,成功后生成一个gen-cpp文件夹,将代码引入服务端和客户端。
2.3 服务端配置和客户端配置
新建client.cpp源代码:
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/server/TSimpleServer.h>
#include <thrift/transport/TSocket.h>
#include <thrift/transport/TBufferTransports.h>
#include "ThriftTest.h"
using namespace ::apache::thrift;
using namespace ::apache::thrift::protocol;
using namespace ::apache::thrift::transport;
using namespace ::apache::thrift::server;
using namespace ::thrift::Test;
//链接库文件
#pragma comment(lib,"libthrift.lib")
int main(int argc, char** argv)
{
int port = 9090;
std::shared_ptr<TSocket> socket(new TSocket("127.0.0.1", port));
std::shared_ptr<TBufferedTransport> transport(new TBufferedTransport(socket));
std::shared_ptr<TBinaryProtocol> protocol(new TBinaryProtocol(transport));
ThriftTestClient client(protocol);
try
{
transport->open();
client.showName();
auto result = client.add(1, 2);
printf("result = %d", result);
transport->close();
}
catch (TException& tx)
{
printf("ERROR:%s\n", tx.what());
}
return 0;
}
服务端ThriftTest_server.skeleton.cpp代码
// This autogenerated skeleton file illustrates how to build a server.
// You should copy it to another filename to avoid overwriting it.
#include "ThriftTest.h"
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/server/TSimpleServer.h>
#include <thrift/transport/TServerSocket.h>
#include <thrift/transport/TBufferTransports.h>
using namespace ::apache::thrift;
using namespace ::apache::thrift::protocol;
using namespace ::apache::thrift::transport;
using namespace ::apache::thrift::server;
using namespace ::thrift::Test;
//链接库文件
#pragma comment(lib,"libthrift.lib")
class ThriftTestHandler : virtual public ThriftTestIf {
public:
ThriftTestHandler() {
// Your initialization goes here
}
int32_t add(const int32_t arg1, const int32_t arg2) {
// Your implementation goes here
printf("客户端调用add\n");
return arg1 + arg2;
}
void showName() {
// Your implementation goes here
printf("客户端调用showName\n");
}
};
int main(int argc, char **argv) {
int port = 9090;
::std::shared_ptr<ThriftTestHandler> handler(new ThriftTestHandler());
::std::shared_ptr<TProcessor> processor(new ThriftTestProcessor(handler));
::std::shared_ptr<TServerTransport> serverTransport(new TServerSocket(port));
::std::shared_ptr<TTransportFactory> transportFactory(new TBufferedTransportFactory());
::std::shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory());
TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory);
server.serve();
return 0;
}
客户端可以像调用本地的方法一样调用服务端的方法,这就是RPC调用。