thrift简单示例 (基于C++)

这个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 = 9853const 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 = 0;
    for (; i < (sz - 1); ++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 == 0) {
        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>(9090), //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 0;
}

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

客户端代码

#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", 9090));
  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(1, 1) << endl;

    Work work;
    work.op = Operation::DIVIDE;
    work.num1 = 1;
    work.num2 = 0;

    try {
      client.calculate(1, 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 = 15;
    work.num2 = 10;
    int32_t diff = client.calculate(1, 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, 1);
    cout << "Received log: " << ss << endl;

    transport->close();
  } catch (TException& tx) {
    cout << "ERROR: " << tx.what() << endl;
  }
}

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

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

原文地址:https://www.cnblogs.com/albizzia/p/10884320.html

时间: 2024-11-05 16:41:38

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

thrift简单示例 (go语言)

这个thrift的简单示例来自于官网 (http://thrift.apache.org/tutorial/go), 因为官方提供的例子简单易懂, 所以没有必要额外考虑新的例子. 关于安装的教程, 可以参考https://www.cnblogs.com/albizzia/p/10838646.html, 关于thrift文件的语法, 可以参考: https://www.cnblogs.com/albizzia/p/10838646.html. 运行下面的示例, 除了需要安装thrift外, 还有

RPC学习----Thrift快速入门和Java简单示例

一.什么是RPC? RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议. RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据.在OSI网络通信模型中,RPC跨越了传输层和应用层.RPC使得开发包括网络分布式多程序在内的应用程序更加容易. 二.什么是Thrift? thrift是一个软件框架,用来进行可扩展且跨语言的服务的开发.它结合了功能强大的软件堆栈和

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

===================================================== 最简单的基于DirectShow的示例文章列表: 最简单的基于DirectShow的示例:视频播放器 最简单的基于DirectShow的示例:视频播放器图形界面版 最简单的基于DirectShow的示例:视频播放器自定义版 最简单的基于DirectShow的示例:获取Filter信息 =====================================================

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

本文记录一个简单的基于DirectShow的自定义的视频播放器.这里所说的"自定义播放器",实际上指的是自己在Filter Graph中手动逐个添加Filter,并且连接这些Filter的后运行的播放器.这么做相对于使用RenderFile()这种"智能"创建Filter Graph的方法来说要复杂不少,但是可以让我们更加了解DirectShow的体系. 流程图 最简单的基于DirectShow的自定义的视频播放器的流程如下图所示. 该流程图中包含如下变量: IGr

最简单的基于DirectShow的示例:视频播放器

本文记录一个最简单的基于DirectShow的视频播放器.DirectShow是一个庞大的框架,可以在Windows下实现多种多样的视频处理需求.但是它的"庞大"也使得新手不太容易学习它的使用.本文的例子正是为解决这一问题而做的,它只包含了使用DirectShow播放一个视频文件所需要的最重要的函数. 流程图 最简单的使用DirectShow播放视频文件的流程如下图所示. 流程图中涉及到几个接口如下所示. IGraphBuilder:继承自IFilterGraph,用于构建Filter

最简单的基于librtmp的示例:发布(FLV通过RTMP发布)

本文记录一个基于libRTMP的发布流媒体的程序:Simplest libRTMP Send FLV.该程序可以将本地FLV文件发布到RTMP流媒体服务器.是最简单的基于libRTMP的流媒体发布示例. 流程图 使用librtmp发布RTMP流的可以使用两种API:RTMP_SendPacket()和RTMP_Write().使用RTMP_SendPacket()发布流的时候的函数执行流程图如下图所示.使用RTMP_Write()发布流的时候的函数执行流程图相差不大. 流程图中关键函数的作用如下

最简单的基于FFmpeg的libswscale的示例

本文记录一个基于FFmpeg的libswscale的示例.Libswscale里面实现了各种图像像素格式的转换,以及图像大小缩放功能.而且libswscale还做了相应指令集的优化,因此它的转换效率比自己写的C语言的转换效率高很多. 流程 简单的初始化方法 Libswscale使用起来很方便,最主要的函数只有3个:(1)       sws_getContext():使用参数初始化SwsContext结构体.(2)       sws_scale():转换一帧图像.(3)       sws_f

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

本文记录一些基于Flash的流媒体处理的例子.Flash平台最常见的流媒体协议是RTMP.此前记录的一些基于C/C++的RTMP播放器/推流器,但是没有记录过基于Flash中的ActionScript的RTMP播放器/推流器.其实基于Flash的RTMP播放器/推流器才能算得上是RTMP技术中的"正规军".RTMP本身设计出来就是用于Flash平台之间通信的,而且RTMP最大的优势--"无插件直播",也是得益于广泛安装在客户端的Flash Player.因此本文分别

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

本文记录一个最简单的基于DirectShow的图形界面的视频播放器.基于DirectShow的图形界面的播放器的例子还是比较多的,但是大部分都是"层层封装"的例子."层层封装"的例子相对来说更加稳定,但是却不是很容易理解.因为DirectShow本身的接口函数的数量就比较多,如果再加上封装DirectShow的函数,合起来的函数数量是非常大的,很容易让人搞不清哪些才是真正的DirectShow接口函数.本播放器剥去了DirectShow例子中的"层层封装&