最近同时用了thrift和protobuf,进行通信传输。之前已写过关于protobuf的一篇博文,具体请点击:http://blog.csdn.net/zsk4232000/article/details/50300201 ,现在就总结一下thrift的编写,并与protobuf进行一些比较。没有进行深入的研究,只描述一下我用thrift的流程与方法。
thrift本身封装了一些通讯的库,支持完整的client/server RPC框架,所以用thrift编写网络通讯会非常方便,而且支持const、typedef等,所以比较流行用thrift进行网络通讯协议的编写。
1. .thrift文件格式
company.thrift 代码:
namespace cpp com.company.project
enum HOBBY
{
RUNNING = 0,
BADMINTION = 1,
PINGPONG = 3,
}
struct Person
{
1:required string name;
2:required i32 age ;
3:required string home;
4:optional HOBBY hobby = HOBBY.RUNNING;
}
enum FIELD
{
IT = 0,
MECHINE = 1,
SECURITY = 2,
}
struct Company
{
1:required list staff;
2:required FIELD field;
3:required i32 staff_num;
4:optional i32 rank = 1;
}
service TCompanyService
{
Person GetStaffInfo(1:i32 index)
void PrintAllStaffName(1:list staffs)
}
注意点:
namespace cpp com.company.project //a
namespace java com.company.project //b
a : 为C++语言格式的命令空间,转化成namespace com { namespace company{ namespace project {
b : 为java语言格式的命令空间,转化成package com.example.project
struct Person
{
1:required string name;
2:required i32 age ;
3:required string home;
enum HOBBY
{
RUNNING = 0,
BADMINTION = 1,
PINGPONG = 3,
}
4:optional HOBBY hobby = HOBBY.RUNNING;
}
List< t1 >:一系列t1类型的元素组成的有序表,元素可以重复
Set< t1 >:一系列t1类型的元素组成的无序表,元素唯一
Map < t1 , t2 >:key/value对(key的类型是t1且key唯一,value类型是t2)
//thrift
1:required string name;
2:optional HOBBY hobby = HOBBY.RUNNING;
//protobuf
required string name = 1;
optional HOBBY hobby = 2[default = RUNNING];
2 . thrift编译
thrift --gen cpp company.thrift //结果代码存放在gen-cpp目录下
thrift --gen java company.thrift //结果代码存放在gen-java目录下
在C++中,生成7个文件:
company_constants.h company_constants.cpp :存放常量
company_types.h company_types.cpp :存放定义的类型
TCompanyService.h TCompanyService.cpp :客户端服务接口的声明及定义
TCompanyService_server.skeleton.cpp :服务端接口实现,具体的需要自己修改和添加,只给出一个模板。
我们需要关注的是:
TCompanyService.h TCompanyService.cpp TCompanyService_server.skeleton.cpp
其中前两者用于客户端,后者用于服务端,且名字可以修改,它只是一个模板.
3. 序列化/反序列化
Transport类:负责数据传输
可用选项有:
下面几个类主要是对上面几个类地装饰(采用了装饰模式),以提高传输效率。
我没有具体研究过,用的都是最常用的。
以下代码为其中的一种:
//序列化
template<typename T>
void object2String(const T &object, string &buf)
{
boost::shared_ptr membuffer(new TMemoryBuffer());
boost::shared_ptr protocol(new TBinaryProtocol(membuffer));
object.write(protocol.get());
buf.clear();
buf = membuffer->getBufferAsString();
}
//反序列化
template<typename T>
void string2object(const string &buf , T &object)
{
uint8_t *p = (uint8_t *)(buf.data());
uint32_t size = buf.size();
boost::shared_ptr membuffer(new TMemoryBuffer(p,size));
boost::shared_ptr protocol(new TBinaryProtocol(membuffer));
object.read(protocol.get());
}
4. 客户端编写
1. 需要包含头文件TCompanyService.h,它本身已经包含了头文件company_types.h,所以我们可以用在.thrift定义的类。
2. 在TCompanyService.h 和 TCompanyService.cpp中,已经帮我们生成了一个应用于客户端的类:TCompanyServiceClient,由它实现我们声明的接口。
3. 打开TCompanyService.cpp中,可以看到接口实现的函数体:
void TCompanyServiceClient::GetStaffInfo(Person& _return, const int32_t index)
{
send_GetStaffInfo(index);
recv_GetStaffInfo(_return);
}
void TCompanyServiceClient::PrintAllStaffName(const std::vector & staffs)
{
send_PrintAllStaffName(staffs);
recv_PrintAllStaffName();
}
函数体已经帮我们实现了发送和接受,非常非常方便。
需要注意的是,我们在.thrift声明的接口的返回值在此作为参数进行返回。
客户端代码:
#include "gen-cpp/TCompanyService.h"
#include
#include
#include
#include
using namespace std;
using namespace boost;
using namespace com::company::project; //对应于.thrift中的namespace com.company.project
using namespace ::apache::thrift;
using namespace ::apache::thrift::protocol;
using namespace ::apache::thrift::transport;
void CSocketClient::Start()
{
//进行网络通讯初始化
auto mySock = boost::make_shared("10.17.128.19",7200);
boost::shared_ptr myTransport(new TBufferedTransport(mySock));
boost::shared_ptr myProtoc(new TBinaryProtocol(myTransport));
TCompanyServiceClient client(myProtoc); //创建客户端实例
myTransport->open();
while(true)
{
char *p = (char*)malloc(100);
if(!cin.getline(p,100))
{
cout<<"please enter some words!"<continue;
}
Person person;
//服务端需要返回全体员工中编号为2的员工;注:person在此为返回信息,不需要赋值
client.GetStaffInfo(person,2);
cout<close();
}
5. 服务端编写
其实编译器已经生成了一个服务端的总体网络通讯框架,即文件TCompanyService_server.skeleton.cpp,我们可以把它拷贝到服务端项目中,改成你想要的名字。因为此文件已经实现了main函数,所以需要把原来的main函数删了。
TCompanyService_server.skeleton.cpp 代码:
class TCompanyServiceHandler : virtual public TCompanyServiceIf {
public:
TCompanyServiceHandler() {
// Your initialization goes here
}
void GetStaffInfo(Person& _return, const int32_t index) {
// Your implementation goes here
printf("GetStaffInfo\n");
}
void PrintAllStaffName(const std::vector & staffs) {
// Your implementation goes here
printf("PrintAllStaffName\n");
}
};
int main(int argc, char **argv) {
int port = 7200;
shared_ptr handler(new TCompanyServiceHandler());
shared_ptr processor(new TCompanyServiceProcessor(handler));
shared_ptr serverTransport(new TServerSocket(port));
shared_ptr transportFactory(new TBufferedTransportFactory());
shared_ptr protocolFactory(new TBinaryProtocolFactory());
TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory);
server.serve();
return 0;
}
我们只需要做的就是实现服务端接口GetStaffInfo()和PrintAllStaffName(),当然main()函数给出的实现只是其中一种,我们可以根据需要修改,如将简单的单线程服务器TSimpleServer换为标准阻塞式IO的多线程服务器TThreadPoolServer。在此我们假设已知一Company类型实例mycompany,且有1000个员工staff,则我们可以实现接口
void GetStaffInfo(Person& _return, const int32_t index) {
if(index > 1000)
{
cout<<"can not find the staff"<return;
}
_return = mycompany.staff.at(index);
}
main()函数会一直阻塞在server.serve()上,进行接收和发送。比如我们在客户端调用的接口为GetStaffInfo(),server()函数接收到后进行判断是哪个接口在发送,知道是接口GetStaffInfo()后,就调用服务器上的接口实现GetStaffInfo()进行数据处理,之后再进行发送成果物。这些所有的一切都已经帮我们实现了,我们不用再去操心通讯的过程,只需好好的写服务端的数据处理。