thrift在windows系统下C++环境搭建/QT使用(记录,踩坑合集)
1. 依赖库
thrift的编译依赖第三方库(boost、libevent、openssl)
采用第一种编译方法可暂时不下载依赖库!!!,这里也更推荐第一种编译方法,可以省略很多麻烦的编译过程,boost是必须编译的,因为c++和qt也需要单独引用。
boost
可以从boost官网上下载boost库。
个人经历,推荐下载1_72_0,不要使用1_74_0,某些资料里说74版本在windows下有未知bug,尚不完善。我自己下载了74,在qt内引入使用时缺少了部分hpp文件,所以重新下载了72版本。
下载后解压,运行bootstrap.bat文件,会在当前目录下生成b2.exe。
编译命令: 参数含义以及配置可以参考boost编译参数
1.71.0开始,bjam由b2代替
msvc-14.1代表vs2017
b2 stage --toolset=msvc-14.1 --without-python link=static runtime-link=shared runtime-link=static threading=multi debug release
其中版本参考
MSVC++ 4.x _MSC_VER == 1000
MSVC++ 5.0 _MSC_VER == 1100
MSVC++ 6.0 _MSC_VER == 1200
MSVC++ 7.0 _MSC_VER == 1300
MSVC++ 7.1 _MSC_VER == 1310 (Visual Studio 2003)
MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005)
MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008)
MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010)
MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012)
MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013)
MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015)
MSVC++ 14.1 _MSC_VER == 1911 (Visual Studio 2017)
大概十几分钟(慢的话半小时?)编译完成,在当前目录下生成stage目录,即为编译后的文件,之后可以作为外部库被引用,boost文件夹和使用编译命令的install产生的hpp相同也会被作为外部依赖被引入,所以不采用install方式。
libevent
libthrift工程和libthriftnb工程。前者为阻塞的,后者为非阻塞模式的服务器,非阻塞模式的服务器需要libevent库。
我们使用的是libthrift,可以暂时不安装libevent库。
下载地址:https://libevent.org
openssl
下载Win64OpenSSL_Light-1_1_0f.exe
、Win64OpenSSL- 1_1_0f.exe
,可以选择安装后者,前者是轻量级的不含源码的目标文件。
下载地址可选:
http://slproweb.com/products/Win32OpenSSL.html
https://oomake.com/download/openssl
如果想自行编译,具体细节可参考此博文
2. 编译过程
首先需要下载thrift源码以及windows下可执行程序,官网最新版本是thrift-0.13.0,但是windows下的版本有比较多的问题,所以推荐使用thrift-0.12.0版本。其中tar.gz用于编译,exe用来直接生成目标语言的代码。
解压tar.gz,thrift的c++编译需要打开.sln文件,目录是 xx\thrift\thrift-0.12.0\lib\cpp
下的thrift.sln
,使用VS2017打开(本机装的是VS2017)进行编译。
(1) 使用VS2017内置插件库编译
编译libthrift工程和libthriftnb工程。前者为阻塞的,后者为非阻塞模式的服务器,非阻塞模式的服务器需要libevent库。
用VS2017 VS自带的Nuget安装boost、libevent、openssl 然后编译,我们这里选择x64的编译器。
菜单栏—工具—NuGet包管理器—管理解决方案的NuGet程序包,分别搜索上述3个库(版本可参考下面截图)并安装(可以全局安装也可以只为libthrift安装),安装完之后会在xx:\thrift\thrift-0.12.0\lib\cpp
下的packages目录找到这些依赖。
然后在libthrift项目中引入依赖,不引入的话,会报很多hpp文件No such file or directory的错误。
右键libthrift—属性
此处将依赖库引入项目,选择packages下的对应依赖库即可。
代码引入方式(会自动生成),选择文件请参考下图路径
D:\Apache\thrift\thrift-0.12.0\lib\cpp\packages\libevent_vc120.2.1.4.0\build\native\include
D:\Apache\thrift\thrift-0.12.0\lib\cpp\packages\boost.1.72.0.0
D:\Apache\thrift\thrift-0.12.0\lib\cpp\packages\openssl-vc141-static-x86_64.1.1.0\build\native\include
%(AdditionalIncludeDirectories)
代码引入(自行生成),选择文件请参考下图路径
D:\Apache\thrift\thrift-0.12.0\lib\cpp\packages\openssl-vc141-static-x86_64.1.1.0
D:\Apache\thrift\thrift-0.12.0\lib\cpp\packages\boost.1.72.0.0
%(AdditionalLibraryDirectories)
引入之后点击生成开始编译,可能会报如下错误:
\thttpclient.cpp(25): fatal error C1083: 无法打开包括文件: “thrift/config.h”: No such file or directory
是因为没有正常生成config.h文件。可以自己手动写一个,放到xx:\thrift\thrift-0.12.0\lib\cpp\src\thrift
目录下,内容如下
#include <stdlib.h>
#include <string.h>
#define PACKAGE_VERSION "0.12.0"
重新编译生成即可,成功之后会在cpp文件夹下生成x64文件夹,即为编译后的thrift,qt或c++需要引入的是xx:\thrift\thrift-0.12.0\lib\cpp\x64\Debug
目录下的libthrift.lib !!!。
thrift到此编译成功。
(2) 自行加载库进行编译
将libthrift的依赖库修改为自己编译的目录,但是可能会存在很多不适配的问题,所以不推荐。
最终也会生成上面的libthrift.lib
3. 服务器和客户端开发实例
(1) 生成接口文件
使用前面下载的thrift-0.12.0.exe(建议修改为thrift.exe)来生成对应语言的接口文件
在thrift-0.12.0.exe同级目录手写add.thrift(这里我们就以a+b作为例子,客户端发送a和b,服务器端计算并返回结果)
//add.thrift
//a + b
service AddService{
i32 add(1: i32 num1, 2: i32 num2)
}
编译命令
thrift -r --gen cpp add.thrift
生成文件gen-cpp,其中含有AddService_server.skeleton.cpp为服务器框架。
(2) C++环境下使用/QT creator
这里我们使用QT creator来实现,新建QT项目,选择64bit,将gen-cpp下的.cpp和.h都引用进来。
C++同理,只需要下面的client和server代码即可。
在xxx.pro中引入依赖库,boost和thrift
INCLUDEPATH += D:\Apache\thrift\boost_1_72_0
INCLUDEPATH += D:\Apache\thrift\thrift-0.12.0\lib\cpp\src
LIBS += -LD:\Apache\thrift\boost_1_72_0\stage\lib //目录
LIBS += D:\Apache\thrift\thrift-0.12.0\lib\cpp\x64\Debug\libthrift.lib
client.cpp
#include "AddService.h"
#include <thrift/transport/TSocket.h>
#include <thrift/transport/TBufferTransports.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 std::shared_ptr;
#include <iostream>
int addAandB()
{
shared_ptr<TSocket> socket(new TSocket("127.0.0.1",9090)); //端口自定义
shared_ptr<TTransport> transport(new TBufferedTransport(socket));
shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport));
AddServiceClient client(protocol);
try
{
transport->open();
}
catch(TTransportException)
{
transport->close();
}
std::int32_t res;
res = client.add(1, 2);
std::cout<<res<<std::endl;
return res;
}
client.h
#ifndef CLIENT_H
#define CLIENT_H
class client
{
public:
client();
};
int addAandB();
#endif // CLIENT_H
server.cpp
thrift已经给我们生成了一个服务器端实例(AddService_server.skeleton.cpp),修改一下即可用
#include "AddService.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;
class AddServiceHandler : virtual public AddServiceIf {
public:
AddServiceHandler() {
// Your initialization goes here
}
int32_t add(const int32_t num1, const int32_t num2) {
// Your implementation goes here
printf("add\n");
return (num1 + num2);
}
};
void startServer(){
int port = 9090;
::std::shared_ptr<AddServiceHandler> handler(new AddServiceHandler());
::std::shared_ptr<TProcessor> processor(new AddServiceProcessor(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();
}
server.h
#ifndef SERVER_H
#define SERVER_H
class server
{
public:
server();
};
void startServer();
#endif // SERVER_H
由于本文只需要验证环境搭建,所以只考虑运行成功,不考虑线程问题,具体客户端和服务器端实现,可重新构建。
其中客户端和服务器端可以使用任何语言实现,只要对应端口相同即可调用,这也就实现了thrift的跨语言调用和交互。
thrift在java环境下的实现下一篇继续。