news 2026/9/15 23:03:10

Apache Thrift 官方教程实战:从 .thrift IDL 到多语言客户端/服务器

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Apache Thrift 官方教程实战:从 .thrift IDL 到多语言客户端/服务器

Apache Thrift 官方教程实战:从 .thrift IDL 到多语言客户端/服务器

【免费下载链接】thriftApache Thrift项目地址: https://gitcode.com/GitHub_Trending/thr/thrift

本教程是 Apache Thrift 仓库中 tutorial/ 目录的完整实战指南。它以官方 tutorial/README.md 的六步流程为主线,带你走完「安装编译器 → 阅读 IDL 语法 → 生成代码 → 编写客户端/服务器」的完整链路。读完本文,你将能够读懂并编写 .thrift 接口定义文件,用thrift编译器为任意支持的语言生成代码,并在 C++、Python、Node.js 等语言中跑通自己的第一个 RPC 服务。

Thrift 的分层抽象(传输层 Transport → 协议层 Protocol → 服务层 Server),tutorial 中所有示例的客户端与服务器代码都建立在这条栈之上。

一、教程全景:tutorial 目录里有什么

官方教程位于仓库的 tutorial/ 目录,核心文件与角色如下:

文件/目录作用
tutorial.thrift教学用 IDL 文件,覆盖 Thrift 语法的主要特性,是教程的主体
shared.thrift被 tutorial.thriftinclude的公共定义文件,演示跨文件引用
README.md官方教程操作说明(本文基于它展开)
cpp / py / java / nodejs / rb / go / rs / netstd / haxe / dart / erl / ocaml / perl / php / cl / d / delphi / c_glib 等子目录各语言的示例客户端与服务器实现
Makefile.am构建脚本:按编译开关(WITH_CPPWITH_PYTHONWITH_JAVA等)决定参与构建的语言子目录,并默认执行thrift --gen html -r tutorial.thrift生成 HTML 版文档

官方 tutorial/README.md 将学习路径浓缩为六步:

  1. 安装 Thrift 编译器与所需语言的运行时库(依据顶层 README.md 的说明);
  2. 通读tutorial.thrift,学习 Thrift 文件的语法;
  3. 用编译器为目标语言生成代码;
  4. 查看生成代码;
  5. 在各语言目录中查看示例客户端/服务器代码;
  6. 完成,开始构建自己的项目。

下文逐一展开。

二、环境准备:安装编译器与语言库

教程第一步要求安装 Thrift 编译器(thrift命令)与目标语言的运行时库。编译器的安装方式请以仓库顶层 README.md 为准,其核心流程为标准的 autotools 构建:

./bootstrap.sh # 生成 configure 脚本(从源码首次构建时需要) ./configure # 探测依赖并生成 Makefile make # 编译编译器与各语言库 make install # 安装到系统路径(如 /usr/local/bin)

几点补充:

  • 若 Boost 安装在非标准路径(如/usr/local),可在configure时显式指定;Python 模块的安装路径可通过PY_PREFIX变量调整;
  • make check会运行跨语言测试,即使某个语言构建失败,整体仍会继续并输出汇总报告;
  • 需要卸载时执行make uninstall
  • 部分语言的包必须使用各自的构建工具手动安装(如 Java 的 Gradle、Go 的go mod等)。

tutorial.thrift的注释中提到,运行本教程前应保证编译器已安装到/usr/local/bin。安装完成后可用thrift -version验证。

三、阅读 tutorial.thrift:IDL 语法速成

tutorial/tutorial.thrift 是一份「会说话的语法教材」——它把 Thrift 语言的主要特性都写进了注释里。逐节拆解如下。

3.1 注释风格

.thrift文件支持三类注释,与 C/C++ 完全一致:

  • #行注释(shell 风格);
  • //行注释;
  • /* ... *//** ... */块注释(后者常被用作文档注释)。

官方注释还提示了一个技巧:可以在文件首行使用#让 .thrift 文件本身可执行,并把编译步骤写在首行。

3.2 基础类型

Thrift 内置的基础类型如下(原文完整列表):

类型说明
bool布尔值,占一个字节
i8byte有符号 8 位整数
i16有符号 16 位整数
i32有符号 32 位整数
i64有符号 64 位整数
double64 位浮点数
string字符串
binary字节数组(Blob)
map<t1,t2>键值映射
list<t1>有序列表
set<t1>唯一元素集合

3.3 include:跨文件引用

Thrift 文件可以引用其他 Thrift 文件,以复用公共的 struct 与 service 定义:

include "shared.thrift"

查找规则:先在当前路径查找,也可通过编译器的-I参数指定额外搜索路径。被包含文件中的对象使用「文件名前缀」访问,例如 shared.thrift 中定义的SharedStruct,在引用方写作shared.SharedStruct

3.4 namespace:控制各语言输出包名

namespace cl tutorial namespace cpp tutorial namespace d tutorial namespace dart tutorial namespace java tutorial namespace php tutorial namespace perl tutorial namespace haxe tutorial namespace netstd tutorial

namespace用于为不同目标语言指定生成的包/模块/命名空间。注意语言间有差异:D 语言中shared与关键字冲突,因此 shared.thrift 特意使用namespace d share规避。

3.5 typedef 与 const

typedef i32 MyInteger const i32 INT32CONSTANT = 9853 const map<string,string> MAPCONSTANT = {'hello':'world', 'goodnight':'moon'}
  • typedef为类型起别名,C 风格;
  • const定义跨语言共享的常量。复杂类型(map、list、struct)的常量使用 JSON 风格字面量书写。

3.6 字符串字面量与转义规则

tutorial.thrift用大段注释给出了字符串字面量的精确规则:

  • 可用双引号或单引号包裹,两种写法等价,适用于包括include与注解值在内的所有出现字符串的位置;
  • 字面量必须在起始行内结束;不包裹字面量的引号字符可直接使用,如"don't"'say "hi"'
  • 支持四种转义序列:\"(双引号)、\'(单引号)、\\(反斜杠)、\n(换行)、\r(回车)、\t(制表符);
  • 反斜杠后跟其他任何字符都是错误,字面意义上的反斜杠必须写双份:"C:\\Temp"表示字符串C:\Temp
  • 不支持\x41\u00e4这类数字转义,非 ASCII 字符直接书写。

3.7 enum:32 位整数枚举

enum Operation { ADD = 1, SUBTRACT = 2, MULTIPLY = 3, DIVIDE = 4 }

枚举本质上是 32 位整数;值可省略,省略时从 1 开始递增(C 风格)。

3.8 struct:结构化数据

struct Work { 1: i32 num1 = 0, 2: i32 num2, 3: Operation op, 4: optional string comment, }

每个字段由四部分组成:整数编号、类型、符号名、可选的默认值。字段可声明为optional,其语义是:未设置时不会出现在序列化输出中。官方注释提醒,这在部分语言里需要手动管理字段的「是否已设置」状态(例如 C++ 生成的__isset位标志)。

3.9 exception:可抛出的结构

exception InvalidOperation { 1: i32 whatOp, 2: string why }

exception是特殊的 struct,语法与 struct 完全相同,但语义上是 RPC 方法可能抛出的异常。在 C++ 生成代码中它会继承TException,客户端可以用catch (InvalidOperation& io)捕获(见 CppClient.cpp)。

3.10 service:定义 RPC 接口

service Calculator extends shared.SharedService { void ping(), i32 add(1:i32 num1, 2:i32 num2), i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch), oneway void zip() }

要点:

  • 服务可以继承其他服务(extends shared.SharedService,此时 Calculator 自动获得getStruct方法);
  • 方法定义形似 C 函数:返回类型 + 参数列表 + 可选的throws异常列表;参数列表与异常列表的书写语法和 struct 字段列表完全一致;
  • oneway void zip()oneway修饰符表示客户端只发送请求、完全不等响应,因此oneway方法返回值必须是void

tutorial.thrift结尾的注释指出:更完整的示例可继续阅读仓库的 test/ 目录(如 test/ThriftTest.thrift),生成代码会出现在gen-<language>目录中。

四、编译生成代码:thrift 命令

教程第三步给出了两条命令(第一条为展示命令本身,第二条为实际编译指令):

$ thrift $ thrift -r --gen cpp tutorial.thrift

参数含义:

  • -r--recurse):递归处理include的文件——由于tutorial.thrift引用了shared.thrift,该参数会一并为其生成代码;
  • --gen cpp:指定目标语言生成器,这里是 C++;
  • 若被包含文件不在当前目录,可用-I <path>追加搜索路径(对应include一节提到的查找规则)。

cpp换成其他语言名即可切换生成器:--gen java--gen py--gen js:node--gen go--gen rs--gen netstd--gen rb--gen php--gen haxe--gen dart--gen lua等。生成结果输出到gen-<language>目录(如gen-cpp/gen-py/),这也是各语言示例代码中#include "../gen-cpp/Calculator.h"sys.path.append('gen-py')require("./gen-nodejs/Calculator")等引用的来源。

五、生成代码长什么样

教程第四步是「查看生成代码」。以 C++ 为例,gen-cpp/下会出现:

  • tutorial_types.h/.cpp:enum、struct、exception 的 C++ 数据类型与序列化方法;
  • shared_types.h/.cpp:被 include 的SharedStruct等类型;
  • Calculator.h/.cpp:服务接口(CalculatorIf)、客户端(CalculatorClient)、处理器(CalculatorProcessor)及工厂类;
  • shared_constants.h/.cpptutorial_constants.h/.cpp:常量定义。

生成的接口层设计值得注意:每个服务会生成一对「接口 + 处理器」——服务端业务类继承接口(如CalculatorIf),而生成的CalculatorProcessor负责把线上的协议消息分发到业务方法。正如教程注释所说,生成代码「并不吓人,甚至有着漂亮的缩进」。

六、各语言示例客户端/服务器

教程第五步指向各语言目录的示例代码。所有语言实现都遵循同一套分层栈:Transport(字节传输)→ Protocol(消息编解码)→ Client/Processor(业务)

6.1 C++:完整的分层栈与服务器类型

tutorial/cpp/CppServer.cpp 展示了服务端三件套:

TThreadedServer server( std::make_shared<CalculatorProcessorFactory>(std::make_shared<CalculatorCloneFactory>()), std::make_shared<TServerSocket>(9090), //port std::make_shared<TBufferedTransportFactory>(), std::make_shared<TBinaryProtocolFactory>()); server.serve();
  • 处理器CalculatorProcessorFactory+CalculatorCloneFactory的组合用于每连接一个 handler 实例CalculatorCloneFactory::getHandler中可以从TConnectionInfo取出底层TSocket,打印对端主机、地址、端口等信息,是实现每连接状态(如独立日志)的推荐方式;如果不需要每连接状态,可直接使用CalculatorProcessor+ 单一CalculatorHandler(代码中以注释形式给出);
  • 传输TServerSocket(9090)监听端口,TBufferedTransportFactory提供缓冲;
  • 协议TBinaryProtocolFactory使用二进制协议。

业务 handler 继承生成的CalculatorIf并实现各方法。calculate中展示了服务端抛异常的写法——除数为 0 时构造InvalidOperationthrow,由协议层编码后送回客户端:

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;

同一个文件还以注释形式给出了另外两种服务器类型,方便对比选型:

服务器类型特点
TSimpleServer单连接、不派生线程,最简单
TThreadedServer每连接一个线程(本示例默认)
TThreadPoolServer通过ThreadManager::newSimpleThreadManager(workerCount)维护固定工作线程池,复用线程、限制并发连接数

tutorial/cpp/CppClient.cpp 展示客户端构造的标准四层栈:

std::shared_ptr<TTransport> socket(new TSocket("localhost", 9090)); std::shared_ptr<TTransport> transport(new TBufferedTransport(socket)); std::shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport)); CalculatorClient client(protocol); transport->open();

客户端演示了:ping()无参调用、add()简单 RPC、调用calculate并捕获服务端抛出的InvalidOperationcatch (InvalidOperation& io)后读取io.why)、以及复杂类型返回——C++ 对复杂类型使用引用传参返回以避免昂贵的拷贝(client.getStruct(ss, 1)后读取ss)。

6.2 Python:同样的四层栈

tutorial/py/PythonClient.py 与 tutorial/py/PythonServer.py 结构完全对应:

# 客户端:socket -> buffered transport -> binary protocol -> client transport = TSocket.TSocket('localhost', 9090) transport = TTransport.TBufferedTransport(transport) # 注释强调:缓冲至关重要,裸 socket 很慢 protocol = TBinaryProtocol.TBinaryProtocol(transport) client = Calculator.Client(protocol) transport.open()
# 服务端:processor + server socket + transport factory + protocol factory handler = CalculatorHandler() processor = Calculator.Processor(handler) transport = TSocket.TServerSocket(host='127.0.0.1', port=9090) tfactory = TTransport.TBufferedTransportFactory() pfactory = TBinaryProtocol.TBinaryProtocolFactory() server = TServer.TSimpleServer(processor, transport, tfactory, pfactory) server.serve()

Python 版本同样给出了多线程服务器的备选:TServer.TThreadedServerTServer.TThreadPoolServer。Python 客户端还需把生成代码目录加入sys.pathsys.path.append('gen-py'),并引用生成模块from tutorial import Calculatorfrom tutorial.ttypes import InvalidOperation, Operation, Work

6.3 Node.js:回调风格处理器

tutorial/nodejs/NodeServer.js 展示了 Node.js 的写法——处理器是一组回调函数,每个方法通过result(err, data)返回:

var server = thrift.createServer(Calculator, { ping: function (result) { console.log("ping()"); result(null); }, add: function (n1, n2, result) { result(null, n1 + n2); }, calculate: function (logid, work, result) { /* ... */ } });

异常通过把ttypes.InvalidOperation实例传给result的第一个参数抛出。同一目录还提供 Promise 风格版本 NodeClientPromise.js 与 NodeServerPromise.js。

6.4 Java:一条命令跑通

tutorial/java/README.md 给出了 Java 教程的运行方式。先编译 Java 库:

thrift/lib/java$ make # 或 thrift/lib/java$ gradle assemble

然后一键同时启动服务端与客户端:

thrift/tutorial/java$ make tutorial # 或 thrift/tutorial/java$ gradle tutorial

也可以分两个终端分别运行:

thrift/tutorial/java$ make tutorialserver thrift/tutorial/java$ make tutorialclient # 或 thrift/tutorial/java$ gradle tutorialServer thrift/tutorial/java$ gradle tutorialClient

对应的业务实现位于 tutorial/java/src/CalculatorHandler.java。

6.5 其他语言一览

教程目录还覆盖了大量语言,均可按「生成代码 + 运行示例」的同一套路使用:

  • Go:tutorial/go/src/server.go(含handler.goclient.go,目录内自带server.crt/server.key演示 TLS);
  • Rust:tutorial/rs/(Cargo 工程,见 tutorial/rs/README.md);
  • Ruby:RubyServer.rb 与 RubyClient.rb;
  • .NET/C#:tutorial/netstd/(Client/Server 两个工程);
  • Erlang:server.erl、client.erl,并有json_client.erl演示 JSON 协议;
  • Dart:tutorial/dart/(含 Web 客户端与 console 客户端);
  • 以及Haxe(多种目标平台 hxml)、PerlPHPDelphiOCamlCommon Lisp(cl)、D(含async_client.d异步示例)、C 语言(c_glib)等。

Makefile.am 中的SUBDIRS开关说明:构建时各语言目录的参与由./configure阶段的语言开关(WITH_CPPWITH_PYTHONWITH_JAVA等)决定,按需启用即可。

七、运行与验证

教程中的服务端默认监听9090端口。以 C++ 为例,编译运行后:

  • 启动服务端(TThreadedServer每连接一线程,serve()阻塞运行,控制台会打印ping()add(...)calculate(...)等调用日志);
  • 启动客户端,应依次输出ping()1 + 1 = 2、除零时捕获到InvalidOperation: Cannot divide by 015 - 10 = 5、以及Received log: ...(getStruct 从服务端日志中取回结果)。

需要自行验证完整链路时,可在源码根目录执行make check运行整套跨语言测试(即使个别语言失败也会继续并汇总)。若使用 Docker,顶层 README.md 还提供了与 CI 一致的容器构建方式。

八、下一步

教程本身「刻意保持简短」,它只负责把你领进门。继续深入的方向包括:

  • 更完整的 IDL 特性:查看 test/ 下的测试用 .thrift 文件(如 test/ThriftTest.thrift),覆盖联合体 union、容器嵌套、注解等更多语法;
  • 协议与传输:本教程统一使用二进制协议 + 缓冲传输,仓库 doc/specs/ 下有二进制协议、Compact 协议、JSON 等规范文档,各语言库中还提供TCompactProtocolTFramedTransportTSaslTransport等变体;
  • lib 目录:各语言运行时库源码位于 lib/,LANGUAGES.md 列出了语言支持矩阵;
  • 高级服务器模型:对比 TSimpleServer / TThreadedServer / TThreadPoolServer 乃至非阻塞 TNonblockingServer,可阅读 lib/cpp/src/thrift/server/ 下的实现。

至此,官方教程的六步已经全部走完——你现在已经掌握了从编写 IDL、生成代码到搭建多语言 RPC 服务的完整能力,可以开始构建自己的项目了。

【免费下载链接】thriftApache Thrift项目地址: https://gitcode.com/GitHub_Trending/thr/thrift

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 23:01:46

MATLAB混合建模实战:数字孪生中的机理与数据驱动融合方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/15 23:01:01

户外对讲机怎么选?从泉盛K6到宝锋UV-5R的实用指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/15 23:00:04

DeepSeek Harness是什么?从部署配置到实战避坑全攻略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/15 22:57:39

手写番茄钟:从纯前端实现到注意力管理的深度定制

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/15 22:56:11

HarmonyOS路由跳转选型指南:Router与Navigation深度对比与实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华