文章目录
一、什么是Thrift框架
1.1 RPC框架
RPC(Remote Procedure Call,远程过程调用),是使程序A调用程序B中方法的手段,常用于分布式系统间的通信,是C/S(客户端/服务端)架构的。比如图中客户端A想调用服务端B中的方法Add(int a, int b),客户端A先将对象序列化为二进制传输到服务端B中,在服务端B需要做反序列化将收到的二进制转换为对象。同时为确保服务稳定,常采用TCP/IP协议。
1.2Thrift框架
Thrift框架是一个轻量级、跨语言、易部署的RPC框架。于2007年由FaceBook开发,2008年进入Apache开源项目。通过自身的IDL语言生成客户端和服务端的模板代码,使用者仅需要编写IDL语言则可使用。Thrift主要分为四个层级:传输层(Transport Layer)、协议层(Protocol Layer)、处理层(Processor Layer)、服务层(Server Layer)。
层级 | 内容 |
---|---|
传输层 | 负责从网络中读取和写入数据,定义具体的网络传输协议,如TCP/IP |
协议层 | 定义数据传输格式,负责序列化和反序列化,如JSON、XML、二进制数据 |
处理层 | 由IDL(接口描述语言)生成,自动封装了底层网络传输和序列化方式,委托给用户实现的Handler处理 |
服务层 | 整合传输层、协议层、处理层,提供具体网络IO模型(单线程/多线程/事件驱动),形成最终服务 |
1.3Thrift框架特点
Thrift具有以下特点,在我们使用的时候可以做一个选择:
(1)开发速度快
编写RPC接口Thrift IDL文件则可通过编译生成器自动生成服务端骨架(Skeletons)和客户端桩(Stubs)。省去开发者自定义和维护接口编解码、消息传输、服务器多线程模型等工作。对于服务端来说只需要按服务骨架(接口),编写具体业务处理程序(Handler)实现类即可。而对于客户端,只要拷贝好IDL定义好的客户端桩和服务对象,就可以像调用本地方法一样进行远程调用。
(2)接口维护简单
仅需维护Thrift格式的IDL(接口描述语言)则可作为客户端使用的接口文档,同时会自动根据IDL生成相对应的接口代码。
(3)学习成本低
由Google Protobuf开发团队开发,IDL文件类似Google Protobuf。
(4)跨语言支持
支持多种语言,包括C++、Python、Java等热门语言,同时客户端和服务端所使用的语言可以不一致。
(5)稳定
有很多大型企业,如国外的Facebook,国内的百度、美团、小米等都在使用。
二、Thrift框架的简易部署
2.1下载Thrift源码
通过Thrift的官方开源地址可以获取到源码:Thrift源码Github地址
下载之后进入complier里找到cpp工程进行编译得到可执行文件exe,不过这种方式太麻烦,有以下要求:
- VS C++
- Flex and Bison, 即 WinFlexBison 包
- Apache Thrift要求
(内容大致是一些C++、boost等,以及每种语言对应要求的库。C++要求的是boost、libevent)
在搜索过程中还找到两种很棒的安装方法,Windows端通过Vcpkg进行安装,Linux可以通过GNU做辅助安装。
vcpgk和GNU配置环境这里贴上前辈的链接,使用vspkg之后很方便,附上vcpkg的中文使用链接vcpkg中文使用手册,该工具类似Python的pip,使用起来非常顺手。
三、Thrift框架的使用
3.1 支持数据类型
在编写Thrift文件前,先让我们熟悉下这个框架较常见的数据类型,Thrift主要支持三部分的数据类型,分别是基本类型、特殊类型、结构体。
3.1.1 基本类型
Thrift框架支持的基本数据类型主要有(括号为Java对应类型):
类型 | 描述 |
---|---|
bool(boolean) | 布尔类型(TRUE or FALSE) |
byte(byte) | 8位带符号整数 |
i16(short) | 16位带符号整数 |
i32(int) | 32位带符号整数 |
i64(long) | 64位带符号整数 |
double(double) | 64位浮点数 |
string(String) | 采用UTF-8编码的字符串 |
3.1.2 特殊类型
类型 | 描述 |
---|---|
binary(ByteBuffer) | 未经过编码的字节流 |
3.1.3 结构
Thrift支持的结构体和C、C++里的结构体大致相同,不同点在于声明的时候写法不一致,定义了一个OOP(面向对象),但是它不具有继承性。
比如定义一个需要传输和使用的结构体students:
struct students
{
1:i16 age,
2:string name
}
3.1.4 容器
同时在Thrift中还支持List、Map、Set容器。
类型 | 描述 |
---|---|
list<T> | 装有T类型的列表 |
map<T,T> | 装有T类型的表 |
set<T> | 装有T类型的唯一数组 |
3.2 Thrift 脚本编写
编写基本的Thrift脚本可以阅读官网wikiThrift官网wiki。
在使用前,要明确需要的数据。
首先新建一个文件,例如我下面新建的DeviceToUi.thrift。
struct MonitorTemps
{
1:list<double> Temps
}
service DeviceToUi
{
void ReportDeviceTemps(1:MonitorTemps monitortemps);
}
thrift支持很多不同的数据类型,比如上面定义了一个结构体MonitorTemps,包含容器list Temps。然后service定义的是你需要在服务端调用的函数ReportDeviceTemps()。下面我们就可以生成对应的资源文件(.cpp)和头文件(.h)。
虽然定义的是List类型,但是在代码里赋值给它需要是vector的,这里还不明白。
3.3 生成头文件和资源文件
如果是用vcpkg操作,在vcpkg的pakeage里有tool文件夹,进入可以看到thrift.exe可执行工具。
在当前文件夹名字的地方输入cmd命令,执行下面这条cmd语句。
thrift.exe -r -gen cpp DeviceToUi.thrift
之后看到当前目录下产生了文件。
3.4 新建解决方案
在新建的解决方案里建立两个项目,分别是client和sercvice,其余文件都已经给我们生成好了,我们自己需要写的就是作为服务端入口函数的sklern资源文件和client端的资源文件。
注意:只有client的代码需要我们写,server的代码可以根据自己情况来考虑。
client.cpp的代码如下:
#include "DeviceToUi.h" //客户端项目这里引入上面thrift文件定义的接口
#include<thrift/transport/TSocket.h>
#include<thrift/transport/TBufferTransports.h>
#include<thrift/protocol/TBinaryProtocol.h>
#include<vector>
using namespace apache::thrift;
using namespace apache::thrift::protocol;
using namespace apache::thrift::transport;
int main()
{
MonitorTemps sTemps; //定义传输的结构体,前面为Thrift中定义的
::std::shared_ptr<TSocket> socket(new TSocket("127.0.0.1", 9090)); //客户端连接当前端口
::std::shared_ptr<TTransport> transport(new TBufferedTransport(socket));
::std::shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport));
transport->open();
std::vector<double> testData(10,5);
sTemps.Temps = testData;
DeviceToUiClient client(protocol); //调用自动生成的client对象,通过client对象调用服务端方法
printf("客户端开始调用服务端的方法:ServiceFunc()\n传输十个数据为5的数组数据。");
client.ReportDeviceTemps(sTemps); //到这一步客户端已经往服务端发送了数据,服务端的方法,传输结构体
system("pause");
}
server.cpp的代码如下:
// This autogenerated skeleton file illustrates how to build a server.
// You should copy it to another filename to avoid overwriting it.
#include "DeviceToUi.h"
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/server/TSimpleServer.h>
#include <thrift/transport/TServerSocket.h>
#include <thrift/transport/TBufferTransports.h>
#include <algorithm>
#include <iostream>
using namespace ::apache::thrift;
using namespace ::apache::thrift::protocol;
using namespace ::apache::thrift::transport;
using namespace ::apache::thrift::server;
class DeviceToUiHandler : virtual public DeviceToUiIf {
public:
DeviceToUiHandler() {
// Your initialization goes here
}
void ReportDeviceTemps(const MonitorTemps& monitortemps) {
// Your implementation goes here
printf("ReportDeviceTemps\n");
printf("服务端接收到数据,收到的数据是:\n");
std::for_each(monitortemps.Temps.begin(), monitortemps.Temps.end(), [](const auto& i) {std::cout << i << " "; });
}
};
int main(int argc, char **argv) {
int port = 9090;
::std::shared_ptr<DeviceToUiHandler> handler(new DeviceToUiHandler());
::std::shared_ptr<TProcessor> processor(new DeviceToUiProcessor(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;
}
3.5 建立Client项目和Server项目
调试开启两个项目,可以看到之间进行通信了。
启动多个项目,在VS的解决方案中右键属性。
可以看到我们的列表数据已经传送过去。
此时两个项目的文件结构是:
大部分是自动生成的代码,使用起来很方便。
3.6 命名空间
Thrift内部添加命名空间,采用 文件名+结构体,比如同等目录下有test.thrift,里面有结构体hello。则调用的时候为test.hello作为接口变量。
若含在cpp里面,则添加namespace cpp test.hello,代码里可以通过test::hello::xxxx来使用,xxx结构体里的内容。