这个thrift的简单示例, 来源于官网 (http://thrift.apache.org/tutorial/cpp), 因为我觉得官网的例子已经很简单了, 所以没有写新的示例, 关于安装的教程, 可以参考https://www.cnblogs.com/albizzia/p/10838646.html, 关于thrift文件的语法, 可以参考: https://www.cnblogs.com/albizzia/p/10838646.html.

thrift文件

首先给出shared.thrift文件的定义:

/**
* 这个Thrift文件包含一些共享定义
*/ namespace cpp shared struct SharedStruct {
1: i32 key
2: string value
} service SharedService {
SharedStruct getStruct(1: i32 key)
}

然后给出tutorial.thrift的定义:

/** 
 * Thrift引用其他thrift文件, 这些文件可以从当前目录中找到, 或者使用-I的编译器参数指示.
 * 引入的thrift文件中的对象, 使用被引入thrift文件的名字作为前缀, 例如shared.SharedStruct.
 */
include "shared.thrift" namespace cpp tutorial // 定义别名
typedef i32 MyInteger /**
 * 定义常量. 复杂的类型和结构体使用JSON表示法.
 */
const i32 INT32CONSTANT = 9853
const map<string,string> MAPCONSTANT = {'hello':'world', 'goodnight':'moon'} /**
 * 枚举是32位数字, 如果没有显式指定值,从1开始.
 */
enum Operation {
  ADD = 1,
  SUBTRACT = 2,
  MULTIPLY = 3,
  DIVIDE = 4
} /**
 * 结构体由一组成员来组成, 一个成员包括数字标识符, 类型, 符号名, 和一个可选的默认值.
 * 成员可以加"optional"修饰符, 用来表明如果这个值没有被设置, 那么他们不会被串行化到
 * 结果中. 不过这个在有些语言中, 需要显式控制.
 */
struct Work {
  1: i32 num1 = 0,
  2: i32 num2,
  3: Operation op,
  4: optional string comment,
} // 结构体也可以作为异常
exception InvalidOperation {
  1: i32 whatOp,
  2: string why
} /**
 * 服务需要一个服务名, 加上一个可选的继承, 使用extends关键字
 */
service Calculator extends shared.SharedService {   /**
  * 方法定义和C语言一样, 有返回值, 参数或者一些它可能抛出的异常, 参数列表和异常列表的
  * 写法与结构体中的成员列表定义一致.
  */    void ping(),    i32 add(1:i32 num1, 2:i32 num2),    i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch),     /**
   * 这个方法有oneway修饰符, 表示客户段发送一个请求, 然后不会等待回应. Oneway方法
   * 的返回值必须是void
   */
   oneway void zip() }

将上述文件放置在同一个文件夹, 然后编译上述的thrift文件:

$ thrift -r --gen cpp tutorial.thrift

生成的文件会出现在gen-cpp子文件夹中, 因为编译时使用了-r参数, 所以shared.thrift也会被编译.

我们可以考虑查看一下thrift编译之后生成的文件, 这里, 我们可以考虑先编译shared.thrift, 编译后, 会生成7个文件, 分别是shared_constants.h, shared_constants.cpp, shared_types.h, shared_types.cpp, SharedService.h, SharedService.cpp, SharedService_server.skeleton.cpp.

我们先查看shared_constants.h和shared.constants.cpp, 这两个文件的命名是原先的thrift文件名, 加上_constants, 换种方式说, 就是xxx.thrift会生成xxx_constants.h和xxx_constants.cpp. 查看一下这两个文件中的内容: 其中会有一个类叫做xxxConstants, 在这个类中, 会将常量作为公有成员, 然后可以通过一个全局变量g_xxx_constants访问. 而xxx_constants.cpp为类函数的定义, 以及全局变量的定义, 应该比较容易理解.

关于shared_types.h和shared_types.cpp文件, 查看shared_types.h中的内容可以看出, shared_types.h中是thrift文件中各种类型的定义, 这个根据文件名应该可以大致猜出. 其中每一个结构体对应两部分, 假设这个结构体叫yyy, 那么第一个部分是结构体_yyy__isset,这个结构体会为thrift中yyy的每个字段添加一个对应的bool值, 名字相同. 第二部分是结构体yyy. 这个结构体中包括thrift中yyy的每个字段, 加上_yyy__isset对象. 这个对象用于yyy读取输入给自身赋值时, 标识某个字段是否被赋值. yyy中的函数主要有如下几个: (1) 默认构造函数 (2) 析构函数 (3) 设置成员变量值的函数 (4) 比较函数 (5) read函数, 用来读取TProtocol对象的内容, 来给自己赋值 (6) write函数, 将自身的值写入到TProtocol的对象中. 最后还有一个swap函数.

关于SharedService.h和SharedService.cpp文件, 查看SharedService.h中的内容可以看出, 这个文件的文件名来自于thrift中的service SharedService, 假设服务名叫做zzz, 那么就会生成对应的zzz.h和zzz.cpp文件, 用来实现这个服务的接口相关的内容. 查看SharedService.h文件, 可以看到如下内容:

(1) class SharedServiceIf用来实现thrift文件中SharedService的接口,

(2) SharedServiceIfFactory用来实现SharedServiceIf的工厂接口, 构建函数为getHandler, 释放函数为releaseHandler, 其中SharedServiceIf在工厂类中定义别名Handler.

(3) SharedServiceIfSingletonFactory是SharedServiceIfFactory的一个具体实现, 用来实现返回单例的SharedServiceIf对象.

(4) SharedServiceNull是SharedServiceIf的不做任何行为的实现.

(5) _SharedService_getStruct_args__isset是SharedService服务的getStruct函数的参数对应的isset类, 用来表示这些参数是否存在.

(6) SharedService_getStruct_args是SharedService服务的getStruct函数的参数对应的类, 用来表示一个服务的函数的参数, 实现内容和thrift文件中的结构体的实现基本一致.

(7) SharedService_getStruct_pargs用处不太清楚.

(8) _SharedService_getStruct_result__isset是SharedService服务的getStruct函数的返回值对应的isset函数, 用来表示返回值是否设置.

(9) SharedService_getStruct_result是SharedService服务的getStruct函数的返回值对应的类, 用来表示一个服务的函数的返回值.

(10) _SharedService_getStruct_presult__isset和SharedService_getStruct_presult用处不太清楚.

(11) SharedServiceClient 是thrift中SharedService服务的客户端实现. SharedServiceClient包括以下内容:

  1) 构造函数

  2) 获取输入和输出Protocol的函数

  3) 服务中定义的方法, 这里是getStruct函数, 以及getStruct函数实现的两个函数,

 void SharedServiceClient::getStruct(SharedStruct& _return, const int32_t key)
 {
   send_getStruct(key);
   recv_getStruct(_return);
 }

  (12) SharedServiceProcessor为编译器自动生成的对象, 位于Protocol层之上, Server层之下, 实现从输入protocol中读取数据, 然后交给具体的Handler处理, 然后再将结果写入到输出protocol中. 关于这些联系可以参考 https://www.cnblogs.com/albizzia/p/10884907.html.

  (13) SharedServiceProcessorFactory是SharedServiceProcessor的工厂类.

  (14) SharedServiceMultiface是SharedServiceIf的具体实现, 实现了类似于chain of responsiblity的效果, 也就是依次调用构造函数中传入的多个

SharedServiceIf对象的对应方法. 参考代码:

  void getStruct(SharedStruct& _return, const int32_t key) {
size_t sz = ifaces_.size();
size_t i = ;
for (; i < (sz - ); ++i) {
ifaces_[i]->getStruct(_return, key);
}
ifaces_[i]->getStruct(_return, key);
return;
}

关于SharedService_server.skeleton.cpp文件, 假设thrift中定义的服务名叫做zzz, 那么这个文件名叫做zzz_server.skeleton.cpp, skeleton的含义是框架, 这个文件的作用是告诉我们如何写出thrift服务器的代码. 这个文件包括两部分:

(1) 类SharedServiceHandler, 这个类用来实现SharedServiceIf, 假设thrift中的服务名叫做zzz, 那么这个类的名字就相对的叫做zzzHandler. 这个类会给出如果你想要实现SharedServiceIf, 那么你需要实现的具体的函数, 对于这个类来说, 需要实现构造函数和getStruct函数, getStruct函数是服务中定义的函数, 有时候, 你也需要实现析构函数吧.

(2) 然后是一个main函数, main函数中的内容, 告诉你怎样实现一个简单的thrift服务器. 你可以考虑把这个文件拷贝一份, 然后根据这个拷贝进行修改, 实现服务器的功能.

如果把shared.thrift和tutorial.thrift一起编译, 那么就会出现14个文件, 每个thrift文件对应7个, 文件的布局和作用和之前说明的一致.

服务端代码

#include <thrift/concurrency/ThreadManager.h>
#include <thrift/concurrency/PlatformThreadFactory.h>
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/server/TSimpleServer.h>
#include <thrift/server/TThreadPoolServer.h>
#include <thrift/server/TThreadedServer.h>
#include <thrift/transport/TServerSocket.h>
#include <thrift/transport/TSocket.h>
#include <thrift/transport/TTransportUtils.h>
#include <thrift/TToString.h>
#include <thrift/stdcxx.h> #include <iostream>
#include <stdexcept>
#include <sstream> #include "../gen-cpp/Calculator.h" using namespace std;
using namespace apache::thrift;
using namespace apache::thrift::concurrency;
using namespace apache::thrift::protocol;
using namespace apache::thrift::transport;
using namespace apache::thrift::server; using namespace tutorial;
using namespace shared; class CalculatorHandler : public CalculatorIf {
public:
CalculatorHandler() {} void ping() { cout << "ping()" << endl; } int32_t add(const int32_t n1, const int32_t n2) {
cout << "add(" << n1 << ", " << n2 << ")" << endl;
return n1 + n2;
} int32_t calculate(const int32_t logid, const Work& work) {
cout << "calculate(" << logid << ", " << work << ")" << endl;
int32_t val; switch (work.op) {
case Operation::ADD:
val = work.num1 + work.num2;
break;
case Operation::SUBTRACT:
val = work.num1 - work.num2;
break;
case Operation::MULTIPLY:
val = work.num1 * work.num2;
break;
case Operation::DIVIDE:
if (work.num2 == ) {
InvalidOperation io;
io.whatOp = work.op;
io.why = "Cannot divide by 0";
throw io;
}
val = work.num1 / work.num2;
break;
default:
InvalidOperation io;
io.whatOp = work.op;
io.why = "Invalid Operation";
throw io;
} SharedStruct ss;
ss.key = logid;
ss.value = to_string(val); log[logid] = ss; return val;
} void getStruct(SharedStruct& ret, const int32_t logid) {
cout << "getStruct(" << logid << ")" << endl;
ret = log[logid];
} void zip() { cout << "zip()" << endl; } protected:
map<int32_t, SharedStruct> log;
}; /*
CalculatorIfFactory is code generated.
CalculatorCloneFactory is useful for getting access to the server side of the
transport. It is also useful for making per-connection state. Without this
CloneFactory, all connections will end up sharing the same handler instance.
*/
class CalculatorCloneFactory : virtual public CalculatorIfFactory {
public:
virtual ~CalculatorCloneFactory() {}
virtual CalculatorIf* getHandler(const ::apache::thrift::TConnectionInfo& connInfo)
{
stdcxx::shared_ptr<TSocket> sock = stdcxx::dynamic_pointer_cast<TSocket>(connInfo.transport);
cout << "Incoming connection\n";
cout << "\tSocketInfo: " << sock->getSocketInfo() << "\n";
cout << "\tPeerHost: " << sock->getPeerHost() << "\n";
cout << "\tPeerAddress: " << sock->getPeerAddress() << "\n";
cout << "\tPeerPort: " << sock->getPeerPort() << "\n";
return new CalculatorHandler;
}
virtual void releaseHandler( ::shared::SharedServiceIf* handler) {
delete handler;
}
}; int main() {
TThreadedServer server(
stdcxx::make_shared<CalculatorProcessorFactory>(stdcxx::make_shared<CalculatorCloneFactory>()),
stdcxx::make_shared<TServerSocket>(), //port
stdcxx::make_shared<TBufferedTransportFactory>(),
stdcxx::make_shared<TBinaryProtocolFactory>()); /*
// if you don't need per-connection state, do the following instead
TThreadedServer server(
stdcxx::make_shared<CalculatorProcessor>(stdcxx::make_shared<CalculatorHandler>()),
stdcxx::make_shared<TServerSocket>(9090), //port
stdcxx::make_shared<TBufferedTransportFactory>(),
stdcxx::make_shared<TBinaryProtocolFactory>());
*/ /**
* Here are some alternate server types... // This server only allows one connection at a time, but spawns no threads
TSimpleServer server(
stdcxx::make_shared<CalculatorProcessor>(stdcxx::make_shared<CalculatorHandler>()),
stdcxx::make_shared<TServerSocket>(9090),
stdcxx::make_shared<TBufferedTransportFactory>(),
stdcxx::make_shared<TBinaryProtocolFactory>()); const int workerCount = 4; stdcxx::shared_ptr<ThreadManager> threadManager =
ThreadManager::newSimpleThreadManager(workerCount);
threadManager->threadFactory(
stdcxx::make_shared<PlatformThreadFactory>());
threadManager->start(); // This server allows "workerCount" connection at a time, and reuses threads
TThreadPoolServer server(
stdcxx::make_shared<CalculatorProcessorFactory>(stdcxx::make_shared<CalculatorCloneFactory>()),
stdcxx::make_shared<TServerSocket>(9090),
stdcxx::make_shared<TBufferedTransportFactory>(),
stdcxx::make_shared<TBinaryProtocolFactory>(),
threadManager);
*/ cout << "Starting the server..." << endl;
server.serve();
cout << "Done." << endl;
return ;
}

上述代码应该很容易理解, 在这里就不解释了.

客户端代码

#include <iostream>

#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/transport/TSocket.h>
#include <thrift/transport/TTransportUtils.h>
#include <thrift/stdcxx.h> #include "../gen-cpp/Calculator.h" using namespace std;
using namespace apache::thrift;
using namespace apache::thrift::protocol;
using namespace apache::thrift::transport; using namespace tutorial;
using namespace shared; int main() {
stdcxx::shared_ptr<TTransport> socket(new TSocket("localhost", ));
stdcxx::shared_ptr<TTransport> transport(new TBufferedTransport(socket));
stdcxx::shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport));
CalculatorClient client(protocol); try {
transport->open(); client.ping();
cout << "ping()" << endl; cout << "1 + 1 = " << client.add(, ) << endl; Work work;
work.op = Operation::DIVIDE;
work.num1 = ;
work.num2 = ; try {
client.calculate(, work);
cout << "Whoa? We can divide by zero!" << endl;
} catch (InvalidOperation& io) {
cout << "InvalidOperation: " << io.why << endl;
// or using generated operator<<: cout << io << endl;
// or by using std::exception native method what(): cout << io.what() << endl;
} work.op = Operation::SUBTRACT;
work.num1 = ;
work.num2 = ;
int32_t diff = client.calculate(, work);
cout << "15 - 10 = " << diff << endl; // Note that C++ uses return by reference for complex types to avoid
// costly copy construction
SharedStruct ss;
client.getStruct(ss, );
cout << "Received log: " << ss << endl; transport->close();
} catch (TException& tx) {
cout << "ERROR: " << tx.what() << endl;
}
}

从上面的客户端调用来看, 方法调用和本地的类对象的调用很相似, thrift的设计算是很巧妙的. 里面的代码应该不复杂, 所以也不进行具体的讲解了.

查看一下CMakeLists.txt文件:

cmake_minimum_required(VERSION 2.8)

#include_directories(SYSTEM "${Boost_INCLUDE_DIRS}")

#Make sure gen-cpp files can be included
include_directories("${CMAKE_CURRENT_BINARY_DIR}")
include_directories("${CMAKE_CURRENT_BINARY_DIR}/gen-cpp")
include_directories("${PROJECT_SOURCE_DIR}/lib/cpp/src") set(tutorialgencpp_SOURCES
gen-cpp/Calculator.cpp
gen-cpp/SharedService.cpp
gen-cpp/shared_constants.cpp
gen-cpp/shared_types.cpp
gen-cpp/tutorial_constants.cpp
gen-cpp/tutorial_types.cpp
)
add_library(tutorialgencpp STATIC ${tutorialgencpp_SOURCES})
target_link_libraries(tutorialgencpp thrift) add_custom_command(OUTPUT gen-cpp/Calculator.cpp gen-cpp/SharedService.cpp gen-cpp/shared_constants.cpp gen-cpp/shared_types.cpp gen-cpp/tutorial_constants.cpp gen-cpp/tutorial_types.cpp
COMMAND ${THRIFT_COMPILER} --gen cpp -r ${PROJECT_SOURCE_DIR}/tutorial/tutorial.thrift
) add_executable(TutorialServer CppServer.cpp)
target_link_libraries(TutorialServer tutorialgencpp)
if (ZLIB_FOUND)
target_link_libraries(TutorialServer ${ZLIB_LIBRARIES})
endif () add_executable(TutorialClient CppClient.cpp)
target_link_libraries(TutorialClient tutorialgencpp)
if (ZLIB_FOUND)
target_link_libraries(TutorialClient ${ZLIB_LIBRARIES})
endif ()

编译运行, 我这边启动客户端和服务端的命令分别是:

$ LD_LIBRARY_PATH=/usr/local/lib ./TutorialServer
$ LD_LIBRARY_PATH=/usr/local/lib ./TutorialClient

注: 上述代码可以在thrift源代码中的tutorial/cpp文件夹找到.

thrift简单示例 (基于C++)的更多相关文章

  1. Python Thrift 简单示例

    本文基于Thrift-0.10,使用Python实现服务器端,使用Java实现客户端,演示了Thrift RPC调用示例.Java客户端提供两个字符串参数,Python服务器端计算这两个字符串的相似度 ...

  2. DotNetty关键概念及简单示例(基于NET5)

    DotNetty关键概念及简单示例(基于NET5) 目录 DotNetty关键概念及简单示例(基于NET5) 1.DotNetty 设计的关键 1.1 核心组件 1.1.1 Channel 1.1.2 ...

  3. thrift简单示例 (go语言)

    这个thrift的简单示例来自于官网 (http://thrift.apache.org/tutorial/go), 因为官方提供的例子简单易懂, 所以没有必要额外考虑新的例子. 关于安装的教程, 可 ...

  4. 最简单的基于Flash的流媒体示例:网页播放器(HTTP,RTMP,HLS)

    http://blog.csdn.net/leixiaohua1020/article/details/43936415 ======================================= ...

  5. 基于.NET CORE微服务框架 -surging的介绍和简单示例 (开源)

    一.前言 至今为止编程开发已经11个年头,从 VB6.0,ASP时代到ASP.NET再到MVC, 从中见证了.NET技术发展,从无畏无知的懵懂少年,到现在的中年大叔,从中的酸甜苦辣也只有本人自知.随着 ...

  6. 最简单的基于Flash的流媒体示例:RTMP推送和接收(ActionScript)

    ===================================================== Flash流媒体文章列表: 最简单的基于Flash的流媒体示例:RTMP推送和接收(Acti ...

  7. 最简单的基于DirectShow的示例:获取Filter信息

    ===================================================== 最简单的基于DirectShow的示例文章列表: 最简单的基于DirectShow的示例:视 ...

  8. 最简单的基于DirectShow的示例:视频播放器自定义版

    ===================================================== 最简单的基于DirectShow的示例文章列表: 最简单的基于DirectShow的示例:视 ...

  9. 最简单的基于DirectShow的示例:视频播放器图形界面版

    ===================================================== 最简单的基于DirectShow的示例文章列表: 最简单的基于DirectShow的示例:视 ...

随机推荐

  1. (转)ES6系列——let和const深入理解

    原文:https://juejin.im/post/59e6a86d518825422c0cbb6f https://www.cnblogs.com/slly/p/9234797.html-----l ...

  2. MacOSX 虚拟机与宿主机的网络配置

    环境:MacOSX.VMware Fusion11.5.CentOS6 比较重要的两个网卡 安装虚拟机后,宿主机会多出几个网卡,其中vmnet1对应的Bridge(桥接方式),vmnet8对应NAT方 ...

  3. PHP 字符串和十六进制互转

    今天在做项目中,因为要调用别人网站的接口,结果需要对请求和返回的时间进行十六进制加密处理,于是在网上查了下资料谢了一个转换Demo做个记录. 如果在TP下使用可以将下面函数放到common.php中 ...

  4. python时间戳,获取当前时间,时间格式转换,求出前几天或后几天的时间

    import time import datetime import locale import random class TimeUtil: def __init__(self, curtime=N ...

  5. Could not get lock /var/lib/dpkg/lock - open (11: Resource temporarily unavailable) 解决办法

    1:我遇到的问题: 在开机运行apt install vim 命令的时候,如下报错: 2:参考博客: 在Ubuntu中,有时候运用sudo  apt-get install 安装软件时,会出现一下的情 ...

  6. 关于C#编写x86与x64程序的分析

    电脑硬件CPU可以分为x86与x64, x86的机器只能安装32位的操作系统,如XP, WIN7_86, x64的机器既可以安装32位的系统,又可以安装64位的系统,只是在x64的机器上安装32位的系 ...

  7. 【NPDP笔记】第六章 市场研究

      6.1 产品市场研究 客户心声Voice of Customer,为了找出问题的解决方法,引导消费者经历一系列的情景并进行结构化的深度访谈,以提炼客户需求的过程 在产品开发决策中,需要获取的信息 ...

  8. 验证Prometheus alertmanager邮件发送

    新环境上配置alertmanager时出现了“Client was not authenticated to send anonymous mail during MAIL FROM”错误,但老环境上 ...

  9. LeetCode 921. 使括号有效的最少添加(Minimum Add to Make Parentheses Valid) 48

    921. 使括号有效的最少添加 921. Minimum Add to Make Parentheses Valid 题目描述 给定一个由 '(' 和 ')' 括号组成的字符串 S,我们需要添加最少的 ...

  10. 在有nginx做反向代理时候,如何获取用户真实Ip信息

    在获取用户的Ip地址时,不一定可以获取到用户真实的地址信息,这要看代理服务器的类型,代理服务器有普通匿名代理服务器,高匿代理服务器,像这种情况很难获取到用户真实的Ip地址 假如用户没有使用匿名代理服务 ...