ApacheThrift跨语言服务开发框架 ApacheThrift是一种支持多种编程语言的远程服务调用框架,由Facebook于2007年开发,并于2008年进入Apache开源项目管理。ApacheThrift通过IDL来定义RPC的接口和数据类型,然后通过代码生成工具来生成针对不同编程语言的代码,目前支持C++,Java,Python,PHP,Ruby,Erlang,Perl,Haskell,C#,Cocoa,JavaScript,Node.js,Smalltalk,OCaml,Delphi等。
本文将从C#开发人员的角度介绍基于ApacheThrift的服务开发过程。
在文章《开源跨平台数据格式化框架概览》中主要介绍了各开源框架的数据格式化处理部分,但并没有描述消息的传输和RPC服务的定义。而实际上,ApacheThrift与GoogleProtocolBuffers的一大不同点就是,GoogleProtocolBuffers仅支持定义RPC服务接口,而ApacheThrift不仅支持定义RPC服务接口,还提供了支持RPC服务实现的完整的堆栈结构,并为RPC服务的Server端和Client端直接生成了可用代码。 传输层(Transport) 传输层提供对网络I/O的抽象,通过Transport对客户端进行抽象,ServerTransport对服务端进行抽象。
TTransport TBufferedTransport TFramedTransport TStreamTransport TSocket TTLSSocket THttpClient TMemoryBuffer TNamedPipeClientTransport TServerTransport TServerSocket TTLSServerSocket TNamedPipeServerTransport 其实,看一眼TSocket的源代码就可以了解事情的真相了。
复制代码 1publicTSocket(stringhost,intport,inttimeout) 2{ 3this.host=host; 4this.port=port; 5this.timeout=timeout; 6 7InitSocket(); 8} 9 10privatevoidInitSocket() 11{ 12client=newTcpClient(); 13client.ReceiveTimeout=client.SendTimeout=timeout; 14client.Client.NoDelay=true; 15} 复制代码 协议层(Protocol) 协议层抽象了数据结构的定义,描述了如何组织数据以进行传输,包括Encode和Decode数据处理。所以,协议层负责实现数据的序列化和反序列化机制,例如序列化Json,XML,PlainText,Binary,CompactBinary等。
协议层抽象了大量的读写操作接口,以供扩展。
复制代码 1publicabstractvoidWriteMessageBegin(TMessagemessage); 2publicabstractvoidWriteMessageEnd(); 3publicabstractvoidWriteStructBegin(TStructstruc); 4publicabstractvoidWriteStructEnd(); 5publicabstractvoidWriteFieldBegin(TFieldfield); 6publicabstractvoidWriteFieldEnd(); 7publicabstractvoidWriteFieldStop(); 8publicabstractvoidWriteMapBegin(TMapmap); 9publicabstractvoidWriteMapEnd(); 10publicabstractvoidWriteListBegin(TListlist); 11publicabstractvoidWriteListEnd(); 12publicabstractvoidWriteSetBegin(TSetset); 13publicabstractvoidWriteSetEnd(); 14publicabstractvoidWriteBool(boolb); 15publicabstractvoidWriteByte(sbyteb); 16publicabstractvoidWriteI16(shorti16); 17publicabstractvoidWriteI32(inti32); 18publicabstractvoidWriteI64(longi64); 19publicabstractvoidWriteDouble(doubled); 20publicvirtualvoidWriteString(strings); 21publicabstractvoidWriteBinary(byte[]b); 22 23publicabstractTMessageReadMessageBegin(); 24publicabstractvoidReadMessageEnd(); 25publicabstractTStructReadStructBegin(); 26publicabstractvoidReadStructEnd(); 27publicabstractTFieldReadFieldBegin(); 28publicabstractvoidReadFieldEnd(); 29publicabstractTMapReadMapBegin(); 30publicabstractvoidReadMapEnd(); 31publicabstractTListReadListBegin(); 32publicabstractvoidReadListEnd(); 33publicabstractTSetReadSetBegin(); 34publicabstractvoidReadSetEnd(); 35publicabstractboolReadBool(); 36publicabstractsbyteReadByte(); 37publicabstractshortReadI16(); 38publicabstractintReadI32(); 39publicabstractlongReadI64(); 40publicabstractdoubleReadDouble(); 41publicvirtualstringReadString(); 42publicabstractbyte[]ReadBinary(); 复制代码 处理层(Processor) Processor封装了对输入输出流的读写操作,输入输出流也就代表着协议层处理的对象。Processor接口定义的极其简单。
publicinterfaceTProcessor { boolProcess(TProtocoliprot,TProtocoloprot); } 服务层(Server) Server将所有功能整合到一起:
创建一个Transport; 创建Transport使用的I/OProtocol; 为I/OProtocol创建Processor; 启动服务,等待客户端的连接; 通过抽象TServer类来提供上述整合。
TServer TSimpleServer--Simplesingle-threadedserverfortesting. TThreadedServer--Serverwww.wang027.comthatusesC#threads (asopposedtotheThreadPool)whenhandlingrequests. TThreadPoolServer--ServerthatusesC#built-inThreadPooltospawnthreadswhenhandlingrequests. 复制代码 1publicTServer(TProcessorprocessor, 2TServerTransportserverTransport, 3TTransportFactoryinputTransportFactory, 4TTransportFactoryoutputTransportFactory, 5TProtocolFactoryinputProtocolFactory, 6TProtocolFactoryoutputProtocolFactory, 7LogDelegatelogDelegate) 8{ 9} 10 11publicabstractvoidServe(); 12publicabstractvoidStop(); 复制代码 Thrift实例 使用Thrift的过程:
编写类似于结构体的消息格式定义,使用类似于IDL的语言定义。 使用代码生成工具,生成目标语言代码。 在程序中直接使用这些代码。
这里我们从一个简单的Thrift实例开始,对Thrift服务的构建进行直观的展示。创建一个简单的CalculatorService,通过Calculate接口来支持"+-x/"简单的计算。Thrift文件名为calculator.thrift。
复制代码 namespacecppcom.contracts.calculator namespacejavacom.contracts.calculator namespacecsharpContracts
enumOperation{ Add=1, Subtract=2, Multiply=3, Divide=4 }
exceptionDivideByZeroException{ 1:stringMessage; }
serviceCalculatorService{ i32Calculate(1:i32x,2:i32y,3:Operationop)throws(1:DivideByZeroExceptiondivisionByZero); } 复制代码 上面的calculator.thrift实例中,
namespace定义针对不同编程语言的名空间或者包; enum定义了Calculate需要支持的枚举类型; exception定义了Calculate中可能发生的异常类型; service定义了CalculatorService服务接口; ApacheThrift与GoogleProtocolBuffers的另一个不同点就是,ApacheThrift支持对Exception的定义,使得在定义服务和实现服务接口时可以方便的处理服务端异常。
在命令行使用Thrift代码生成工具为C#编程语言生成代码:
thrift--gencsharpcalculator.thrift 代码生成工具根据calculator.thrift中的定义会生成3个C#代码文件:
CalculatorService.cs DivideByZeroException.cs Operation.cs 有了这些生成的代码文件,就可以设计服务端和客户端代码了。这里,创建3个solution文件:
Contracts:存放生成的代码文件,共享给Server和Client; Server:实现服务端代码,为客户端提供服务; Client:实现客户端代码,调用服务端;
Contracts代码 由于在calculator.thrift文件中定义了C#的名空间:
namespacecsharpContracts 所以生成的代码的namespace即为Contracts。
复制代码 namespaceContracts { publicenumOperation { Add=1, Subtract=2, Multiply=3, Divide=4, } } 复制代码 相应的,在CalculatorService文件中也生成了名为Iface的接口定义,也就是Server端需要为Client端实现的接口。
publicpartialclassCalculatorService{ publicinterfaceIface{ intCalculate(intx,inty,Operationop); } } Server端实现 为了实现CalculatorService,需要实现一个CalculatorServiceHandler类来实现生成的Contracts中的CalculatorService.Iface接口。
复制代码 1publicclassCalculatorServiceHandler:CalculatorService.Iface 2{ 3publicintCalculate(intx,inty,Operationop) 4{ 5switch(op) 6{ 7caseOperation.Add: 8returnx+y; 9caseOperation.Subtract: 10returnx-y; 11caseOperation.Multiply: 12returnxy; 13caseOperation.Divide: 14if(y==0) 15thrownewContracts.DivideByZeroException() 16{ 17Message="Cannotdividebyzero." 18}; 19returnx/y; 20} 21 22thrownewNotImplementedException(); 23} 24} 复制代码 上面代码中的Operation.Divide段,判断了当除数为0时将抛出Contracts.DivideByZeroException异常。
然后,需要启动Server来提供CalculatorService服务。将CalculatorServiceHandler类的实例传递给CalculatorService.Processor的构造函数,指定Socket绑定端口8888,然后启动服务。
复制代码 1classProgram 2{ 3staticvoidMain(string[]args) 4{ 5varhandler=newCalculatorServiceHandler(); 6varprocessor=newCalculatorService.Processor(handler); 7 8TServerTransporttransport=newTServerSocket(8888); 9TServerserver=newTThreadPoolServer(processor,transport); 10 11server.Serve(); 12 13Console.ReadKey(); 14} 15} 复制代码 Client端实现 Client端消费Server端的代码更加简单,基本上Thrift都已提供了默认的实现,需要做的就是指定地址、端口和协议。
复制代码 1classProgram 2{ 3staticvoidMain(string[]args) 4{ 5vartransport=newTSocket("localhost",8888); 6varprotocol=newTBinaryProtocol(transport); 7varclient=newCalculatorService.Client(protocol); 8 9transport.Open(); 10 11vartest1=client.Calculate(100,2,Operation.Add); 12Console.WriteLine(test1); 13 14vartest2=client.Calculate(100,2,Operation.Subtract); 15Console.WriteLine(test2); 16 17vartest3=client.Calculate(100,2,Operation.Multiply); 18Console.WriteLine(test3); 19 20vartest4=client.Calculate(100,2,Operation.Divide); 21Console.WriteLine(test4); 22 23try 24{ 25vartest5=client.Calculate(100,0,Operation.Divide); 26Console.WriteLine(test5); 27} 28catch(Contracts.DivideByZeroExceptionex) 29{ 30Console.WriteLine(ex.Message); 31} 32 33Console.ReadKey(); 34} 35} 复制代码 然后,就可以启动Server端和Client端程序,实现简单的服务调用了。 |
|