RPC作为一种特殊的网络编程,会封装一层传输层来支持底层的网络通信。Thrift使用了Transport来封装传输层,但Transport不仅仅是底层网络传输,它还是上层流的封装。
关于Transport的设计,从架构上看,IO流和网络流都是IO的范畴,用一个统一的接口来抽象并无不可,但是个人感觉看Thrift的代码时,都用的Transport来表示流,不知道是普通IO流还是底层的网络流。还不如用Java的方式,把普通IO和网络接口用不同抽象隔离,至少代码逻辑比较清晰
废话不多说,看看Trasport的类结构。 TTransport作为顶层的抽象,使用了抽象类,没有使用接口。个人感觉这种做法还是没有使用接口作为顶层抽象来得好,接口扩展性更好。
有几个关注点:
1. TIOStreamTransport和TSocket这两个类的结构对应着阻塞同步IO, TSocket封装了Socket接口
2. TNonblockingTrasnsort,TNonblockingSocket这两个类对应着非阻塞IO
3. TMemoryInputTransport封装了一个字节数组byte[]来做输入流的封装
4. TMemoryBuffer使用字节数组输出流ByteArrayOutputStream做输出流的封装
5. TFramedTransport则封装了TMemoryInputTransport做输入流,封装了TByteArryOutPutStream做输出流,作为内存读写缓冲区的一个封装。TFramedTransport的flush方法时,会先写4个字节的输出流的长度作为消息头,然后写消息体。和FrameBuffer的读消息对应起来。FrameBuffer对消息时,先读4个字节的长度,再读消息体
6. TFastFramedTransport是内存利用率更高的一个内存读写缓存区,它使用自动增长的byte[](不够长度才new),而不是每次都new一个byte[],提高了内存的使用率。其他和TFramedTransport一样,flush时也会写4个字节的消息头表示消息长度。
和Java的IO一样,Thrift的Transport也采用了装饰器模式实现了所谓的包装流。我们也使用包装流和节点流的概念来区分一下各个Transport。
节点流表示自身采用byte[]来提供IO读写的类:
AutoExpandingBufferReadTransport
AutoExpandingBufferWriteTransport
TMemoryInputTransport
TByteArrayOutputStream
TMemoryBuffer
两个网络相关的比较特殊,我们也可以认为它们是节点流,它们是直接操作网络读写的对象
TNonblockingSocket
TSocket
包装流表示封装了其他Transport,流来提供IO读写的类:
TFramedTransport
TFastFramedTransport
Thrift提供的包装流主要就是两个以TFrame开头的Transort,这两个Transport在写完消息flush的时候,会加上4字节表示长度的消息头,读消息是会先读4字节表示长度的消息头。
既然Thrift的NIO服务器端读消息时,使用了FrameBuffer来做缓冲区,并且解码时先读4字节长度的消息头,那么可以推断出,客户端发消息时,是使用TFramedXXXTransport包装流来传输数据的。
我们来一个实际的客户端对象构造情况
TSocket socket = new TSocket(host, port); socket.setTimeout(timeout); TTransport transport = new TFramedTransport(socket); TProtocol protocol = new TCompactProtocol(transport); transport.open();
另外我们在讲Thrift协议的时候说了,Thrift的协议是和具体的传输对象绑定的,协议使用具体的Transport来读写数据