Apache Thrift netstd 跨语言实战:在 .NET 环境下构建、运行并联动 Python 的 Calculator 教程
2026/9/15 15:18:13 网站建设 项目流程

Apache Thrift netstd 跨语言实战:在 .NET 环境下构建、运行并联动 Python 的 Calculator 教程

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

本指南以仓库中的 tutorial/netstd/README.md 为核心,系统讲解 Apache Thrift 在 .NET(netstd)平台上的完整落地流程:从环境准备、命令行构建,到 Server/Client 可执行程序的多传输(tcp/namedpipe/http/tcptls)、多缓冲(none/buffered/framed)与多协议(binary/compact/json)组合运行,再到与 Python 端进行真实的跨语言 RPC 联调。读完本文,你将能够独立完成一个 netstd 示例工程的构建与调参,并借助源码理解 Thrift 分层(传输层、协议层、处理层)在 .NET 端的实际装配方式。

一、示例工程概述

tutorial/netstd是 Apache Thrift 仓库中面向 .NET 的官方入门示例,基于tutorial.thrift中定义的Calculator服务(继承自shared.thriftSharedService)构建。整个示例由三个 C# 工程组成,统一收录在 tutorial/netstd/Tutorial.slnx 解决方案中:

  • Interfaces:承载由 Thrift IDL 生成的服务接口与数据契约代码(tutorialshared命名空间);
  • Server:实现CalculatorAsyncHandler并启动异步 Thrift 服务端;
  • Client:连接服务端并依次调用ping / add / calculate / getStruct / zip等方法验证通信。

三个工程均通过ProjectReference直接引用核心库 lib/netstd/Thrift/Thrift.csproj,服务端额外引用 lib/netstd/Thrift.AspNetCore/Thrift.AspNetCore.csproj(用于 HTTP 宿主场景)。接口契约则来源于 tutorial/tutorial.thrift 与 tutorial/shared.thrift。

二、环境要求与前置准备

按原文档要求,构建与运行本示例需要满足:

  • .NET Core Standard 3.1(LTS)运行时或 SDK:SDK 仅在需要编译程序时必须安装,仅运行已编译产物时安装运行时即可;
  • 支持 netstd 代码生成的thrift.exe编译器,并将其所在目录加入PATH环境变量,供生成代码阶段调用。

需要说明的是,原文档撰写时以 .NET Core 3.1 为基线;从当前仓库源码看,核心库 lib/netstd/Thrift/Thrift.csproj 已多目标编译netstandard2.1;netstandard2.0;net8.0;net9.0;net10.0,示例工程 Client/Client.csproj 与 Server/Server.csproj 的目标框架为net10.0,因此实际操作中建议安装较新的 .NET SDK,以同时满足最低要求与当前仓库的实际构建需求。

三、构建步骤

按照原文档,构建流程非常简洁:

  1. 下载并安装适用于你平台的 .NET Core SDK(官方 .NET 下载页,此处不展开外部链接);
  2. 确认拥有支持 netstd 的thrift.exe且已加入PATH
  3. 进入tutorial/netstd目录;
  4. 运行 build.sh(Linux/macOS)或 build.cmd(Windows);
  5. 检查src/Tests下的测试(仓库中对应的集成测试位于 test/netstd);
  6. 继续阅读 tutorial/netstd/README.md 完成后续运行步骤。

两个构建脚本的内容完全一致,仅做了两件事:

dotnet --info dotnet build

其中dotnet build会自动拾取当前目录下的 Tutorial.slnx 解决方案。此外 tutorial/netstd/Makefile.am 也提供了 automake 接入方式($(DOTNETCORE) build -c Release),说明该示例可被纳入 Apache Thrift 的整体构建体系。

代码生成是如何触发的?

一个值得关注的源码细节是:Interfaces工程在编译前会自动调用 thrift 编译器生成契约代码。在 tutorial/netstd/Interfaces/Interfaces.csproj 中定义了一个PreBuildTarget,其命令本质为:

thrift -out <项目目录> -gen netstd:wcf,union,serial,net10 -r ./../../tutorial.thrift

其中:

  • -gen netstd指定生成 .NET(netstd)语言代码,wcf,union,serial为附加的生成选项;
  • -r表示递归处理include的 IDL 文件(此处会一并生成shared.thrift的代码);
  • 脚本会依次尝试PATH中的thrift、仓库内 compiler/cpp/thrift 等位置,找到可用的编译器即执行。

也就是说,只要满足“thrift 编译器可用”这一前置条件,dotnet build会自动完成 IDL → C# 代码生成 → 编译的整条链路,无需手工先跑一次thrift

四、运行示例

编译完成后,输出目录中会生成可执行文件:Linux 下名为Client/Server,Windows 下名为Client.exe/Server.exe。运行方式为:先启动服务端,再从第二个控制台启动客户端

运行顺序与命令示例:

# 控制台 1:启动服务端(使用默认 tcp + binary) ./Server # 控制台 2:启动客户端 ./Client

若目标机器只安装了运行时而未安装 SDK,直接运行上述可执行文件即可,SDK 仅在编译时需要。

五、NetCore Server 命令行详解

服务端支持通过命令行参数自由组合传输、缓冲与协议。完整用法:

Server -help 将显示帮助信息 Server -tr:<transport> -pr:<protocol> 以指定参数运行服务端(默认使用 tcp 传输与 binary 协议)

传输选项(-tr)

取值说明
tcp(默认)TCP 传输,host 为localhost,端口9090
namedpipe命名管道传输,管道地址为.test
httpHTTP 传输,地址为localhost:9090
tcptls基于 TLS 的 TCP 传输,host 为localhost,端口9090

缓冲选项(-bf)

取值说明
none(默认)不启用传输工厂
buffered启用缓冲传输工厂
framed启用帧传输工厂(客户端必须与此一致)

协议选项(-pr)

取值说明
binary(默认)二进制协议
compact紧凑二进制协议
jsonJSON 协议
multiplexed多路复用协议

源码层面的更新:原文档将multiplexed列为-pr的可选值,但当前 Server/Program.cs 中GetProtocol()仅解析binary/compact/json三者(Server/Program.cs),多路复用改由独立的-multiplex开关控制(Server/Program.cs 中通过TMultiplexedProcessor注册Calculator处理器实现)。这是仓库演进带来的差异,以源码为准。

命令行示例

Server -tr:tcp

TcpTls 模式注意事项

启用tcptls时,证书文件ThriftTest.pfx必须位于可执行文件所在目录(命令行方式运行),或位于工程目录(从 IDE 调试运行)。证书密码为ThriftTest

从源码看,Server/Program.cs 的GetCertificate()使用X509CertificateLoader.LoadPkcs12FromFile(certFile, "ThriftTest")加载证书,且GetCertPath()会从当前目录向上最多回溯 6 级父目录递归查找ThriftTest.pfx——这正是“证书放在项目根附近即可被找到”的实现原因。服务端通过TTlsServerSocketTransport启用 TLS(Server/Program.cs)。

六、NetCore Client 命令行详解

客户端在服务端参数的基础上,额外支持多客户端并发:

Client -help 将显示帮助信息 Client -tr:<transport> -pr:<protocol> -mc:<numClients> 以指定参数运行客户端(默认 tcp 传输与 binary 协议,1 个客户端)

传输选项(-tr)

取值说明
tcp(默认)TCP 传输,host 为localhost,端口9090
namedpipe命名管道传输,管道地址为.test
httpHTTP 传输,地址为http://localhost:9090
tcptls基于 TLS 的 TCP 传输,host 为localhost,端口9090

缓冲选项(-bf)

取值说明
none(默认)不启用传输工厂
buffered启用缓冲传输工厂
framed启用帧传输工厂(客户端必须与此一致)

协议选项(-pr)

取值说明
binary(默认)二进制协议
compact紧凑二进制协议
jsonJSON 协议
multiplexed多路复用协议

(同服务端一致,当前源码中多路复用通过-multiplex开关控制,参见 Client/Program.cs。)

多客户端选项(-mc)

取值说明
<numClients>并发连接服务端的客户端数量,最大 100,默认 1

客户端主流程会依据-mc创建对应数量的并发任务同时执行 RPC 调用(Client/Program.cs),可用于简单的并发压测。参数解析有严格的合法性校验:GetNumberOfClients()仅接受0 < numClients <= 100的值,否则回退为 1(Client/Program.cs)。

命令行示例

Client -tr:tcp -pr:binary -mc:10

上述命令将以 TCP + 二进制协议启动 10 个并发客户端访问本地 9090 端口。

TcpTls 模式注意事项

与服务端相同:ThriftTest.pfx需位于可执行文件目录(命令行方式)或工程目录(IDE 调试),证书密码为ThriftTest。客户端通过TTlsSocketTransport建立安全连接,且CertValidator回调直接返回true(Client/Program.cs),表明示例中未做严格的证书链校验,仅用于演示 TLS 通道的搭建方式。

七、传输、缓冲与协议的源码装配逻辑

理解 Server/Client 的-tr/-bf/-pr参数如何落到 Thrift 分层架构上,是掌握 netstd 用法的关键。以服务端 Server/Program.cs 为例,装配过程分为四步:

  1. 端点传输(Endpoint Transport):根据-tr选择TServerSocketTransport(tcp)、TNamedPipeServerTransport(namedpipe,管道名.test,最大实例数 64)、TTlsServerSocketTransport(tcptls);
  2. 分层传输工厂(Layered Transport Factory):根据-bf可选地包上TBufferedTransport.FactoryTFramedTransport.Factory
  3. 协议工厂(Protocol Factory):根据-pr选择TBinaryProtocol.FactoryTCompactProtocol.FactoryTJsonProtocol.Factory
  4. 服务端装配:将CalculatorAsyncHandler+Calculator.AsyncProcessor(经TSingletonProcessorFactory包装)与上述传输、协议一起交给TSimpleAsyncServer,最后调用ServeAsync()启动。

客户端 Client/Program.cs 的MakeTransport()对称地构造端点传输(TSocketTransport/TNamedPipeTransport/THttpTransport/TTlsSocketTransport),再按需叠加TBufferedTransportTFramedTransportMakeProtocol()则直接new TBinaryProtocol(transport)等(Client/Program.cs)。若传入-multiplex,客户端还会用TMultiplexedProtocol包装协议并指定服务名Calculator(Client/Program.cs),与服务端的TMultiplexedProcessor一一对应。

这些类分别位于核心库的 lib/netstd/Thrift/Transport/Client、lib/netstd/Thrift/Transport/Server、lib/netstd/Thrift/Transport/Layered、lib/netstd/Thrift/Protocol 与 lib/netstd/Thrift/Server/TSimpleAsyncServer.cs 中,读者可以对照阅读。

HTTP 模式的服务端实现

-tr:http时,服务端走独立的HttpServerSample分支(Server/Program.cs):使用 ASP.NET Core Kestrel 监听http://localhost:9090,通过THttpServerTransport中间件把 HTTP 请求转换为 Thrift 调用。中间件本体位于 lib/netstd/Thrift.AspNetCore/Transport/Server/THttpServerTransport.cs。需要注意,示例代码目前不允许http-multiplex同时使用(Server/Program.cs),虽然 Thrift 本身支持,但示例未做演示。

八、客户端调用序列与异步处理模型

客户端对每个 RPC 的调用集中在ExecuteCalculatorClientOperations()(Client/Program.cs),完整复现了教程服务的典型调用场景:

  1. OpenTransportAsync()打开传输通道;
  2. ping():最简 void 方法;
  3. add(1, 1):整数运算并返回结果;
  4. calculate(1, work)构造Work { Op = DIVIDE, Num1 = 1, Num2 = 0 },触发服务端抛出InvalidOperation异常,客户端捕获并打印——验证跨进程异常传播;
  5. 改为SUBTRACT运算再次calculate,验证15-10=5
  6. getStruct(1):读取服务端内存日志结构体;
  7. zip():调用oneway单向方法(服务端Task.Delay(100)模拟耗时后立即返回,客户端不等响应)。

对应的服务端异步处理器CalculatorAsyncHandler(Server/Program.cs)实现了Calculator.IAsync接口,方法签名统一携带CancellationToken,体现了 netstd 强制async/await模型的特性——在 lib/netstd/README.md 的迁移说明中明确指出,netstd 不再支持同步模型,async是必选项。这也是 lib/netstd/Thrift/Processor 目录下ITAsyncProcessorTMultiplexedProcessor等异步处理器存在的根本原因。

九、跨语言联调:NetCore 与 Python

原文档专门给出了 netstd 与 Python 互通的完整流程,这是验证 Thrift 跨语言能力最直接的方式。操作步骤如下:

  1. 使用最新版thrift工具生成 Python 代码,确保存在gen-py目录(内含tutorialshared等生成包);
  2. 将下方client.pyserver.py保存到gen-py所在目录;
  3. 分别启动 netstd 示例(Client/Server)与 Python 示例(client/server)即可互通。

注意事项

  • 示例中的客户端/服务端代码使用了与.thrift生成契约一致的方法(operations)与字段(properties),跨语言时务必保证 IDL 一致;
  • Windows 10 上若 Python 服务端以主机名testserver监听,需要在C:\Windows\System32\drivers\etc\hosts文件中添加记录127.0.0.1 testserver,否则名称解析会失败。

Python Client(client.py)

import sys import glob sys.path.append('gen-py') from tutorial import Calculator from tutorial.ttypes import InvalidOperation, Operation, Work from thrift import Thrift from thrift.transport import TSocket from thrift.transport import TTransport from thrift.protocol import TBinaryProtocol def main(): # Make socket transport = TSocket.TSocket('127.0.0.1', 9090) # Buffering is critical. Raw sockets are very slow transport = TTransport.TBufferedTransport(transport) # Wrap in a protocol protocol = TBinaryProtocol.TBinaryProtocol(transport) # Create a client to use the protocol encoder client = Calculator.Client(protocol) # Connect! transport.open() client.Ping() print('ping()') sum = client.Add(1, 1) print(('1+1=%d' % (sum))) work = Work() work.Op = Operation.Divide work.Num1 = 1 work.Num2 = 0 try: quotient = client.Calculate(1, work) print('Whoa? You know how to divide by zero?') print('FYI the answer is %d' % quotient) except InvalidOperation as e: print(('InvalidOperation: %r' % e)) work.Op = Operation.Substract work.Num1 = 15 work.Num2 = 10 diff = client.Calculate(1, work) print(('15-10=%d' % (diff))) log = client.GetStruct(1) print(('Check log: %s' % (log.Value))) client.Zip() print('zip()') # Close! transport.close() if __name__ == '__main__': try: main() except Thrift.TException as tx: print('%s' % tx.message)

Python Server(server.py)

import glob import sys sys.path.append('gen-py') from tutorial import Calculator from tutorial.ttypes import InvalidOperation, Operation from shared.ttypes import SharedStruct from thrift.transport import TSocket from thrift.transport import TTransport from thrift.protocol import TBinaryProtocol from thrift.server import TServer class CalculatorHandler: def __init__(self): self.log = {} def Ping(self): print('ping()') def Add(self, n1, n2): print('add(%d,%d)' % (n1, n2)) return n1 + n2 def Calculate(self, logid, work): print('calculate(%d, %r)' % (logid, work)) if work.Op == Operation.Add: val = work.Num1 + work.Num2 elif work.Op == Operation.Substract: val = work.Num1 - work.Num2 elif work.Op == Operation.Multiply: val = work.Num1 * work.Num2 elif work.Op == Operation.Divide: if work.Num2 == 0: raise InvalidOperation(work.Op, 'Cannot divide by 0') val = work.Num1 / work.Num2 else: raise InvalidOperation(work.Op, 'Invalid operation') log = SharedStruct() log.Key = logid log.Value = '%d' % (val) self.log[logid] = log return val def GetStruct(self, key): print('getStruct(%d)' % (key)) return self.log[key] def Zip(self): print('zip()') if __name__ == '__main__': handler = CalculatorHandler() processor = Calculator.Processor(handler) transport = TSocket.TServerSocket(host="testserver", port=9090) tfactory = TTransport.TBufferedTransportFactory() pfactory = TBinaryProtocol.TBinaryProtocolFactory() server = TServer.TSimpleServer(processor, transport, tfactory, pfactory) print('Starting the server...') server.serve() print('done.') # You could do one of these for a multithreaded server # server = TServer.TThreadedServer(processor, transport, tfactory, pfactory) # server = TServer.TThreadPoolServer(processor, transport, tfactory, pfactory)

注意两端在栈结构上的对应关系:Python 客户端使用TSocket + TBufferedTransport + TBinaryProtocol,netstd 客户端默认的tcp传输恰好也是未启用分层缓冲的裸 socket——因此若 Python 端启用了缓冲而 netstd 端未启用,反而会影响吞吐,但协议层(binary)必须一致才能互通。这正是文档强调“framed 必须与客户端匹配”的深层原因:传输栈任一端不匹配都会导致协议解析错位。

十、已知问题与运行提示

  • Trace 日志中的内部异常:以 Trace 级别记录日志时,可能看到一些无关紧要的内部异常(如连接关闭时的预期异常),属正常现象,不影响功能;
  • 仅运行时环境:没有 SDK 的机器只需安装 .NET Core 运行时即可运行已编译的 Client/Server,SDK 仅用于编译阶段;
  • 服务端日志:服务端启动时会打印所选传输、缓冲、协议与 multiplex 配置(Server/Program.cs),可用作运行配置的即时核对。

十一、延伸阅读:netstd 命名体系与迁移要点

若你希望进一步了解 netstd 库本身的组织方式,lib/netstd/README.md 是官方补充文档,其中要点包括:

  • 核心库发布为两个 NuGet 包:ApacheThrift(协议、传输、处理器与TSimpleAsyncServer/TThreadPoolAsyncServer服务端,仅依赖Microsoft.Extensions.Logging.Abstractions)与ApacheThrift.AspNetCore(ASP.NET Core HTTP 中间件THttpServerTransport,仅做 HTTP 宿主时需要额外引用);
  • 命名体系相较旧版 netcore/csharp 库有系统性调整:T*ClientTransport更名为T*TransportTBaseServer更名为TServerSingletonTProcessorFactory更名为TSingletonProcessorFactoryAsyncBaseServer更名为TSimpleAsyncServerTServerSocket更名为TServerSocketTransportServe()更名为ServeAsync()等;
  • 代码生成命令统一为thrift -gen netstd,旧的hashcode/nullable/async生成标志已不再需要。

结合本教程的 tutorial/tutorial.thrift(Calculator服务、Operation枚举、Work结构体、InvalidOperation异常)与 tutorial/shared.thrift(SharedStructSharedService)两份 IDL,即可完整串起“IDL 定义 → 代码生成 → 服务实现 → 多传输/多协议运行 → 跨语言联调”的 Thrift 全流程,为后续在自己的 .NET 项目中集成 Thrift 提供可直接照搬的工程模板。

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

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询