协议和编解码是一个网络应用程序的核心问题之一,客户端和服务器通过约定的协议来传输消息(数据),通过特定的格式来编解码字节流,并转化成业务消息,提供给上层框架调用。

Thrift的协议比较简单,它把协议和编解码整合在了一起。抽象类TProtocol定义了协议和编解码的顶层接口。个人感觉采用抽象类而不是接口的方式来定义顶层接口并不好,TProtocol关联了一个TTransport传输对象,而不是提供一个类似getTransport()的接口,导致抽象类的扩展性比接口差。

TProtocol主要做了两个事情:

1. 关联TTransport对象

2.定义一系列读写消息的编解码接口,包括两类,一类是复杂数据结构比如readMessageBegin, readMessageEnd,  writeMessageBegin, writMessageEnd.还有一类是基本数据结构,比如readI32, writeI32, readString, writeString

  1. public abstract class TProtocol {
  2. /**
  3. * Transport
  4. */
  5. protected TTransport trans_;
  6.  public abstract void writeMessageBegin(TMessage message) throws TException;
  7. public abstract void writeMessageEnd() throws TException;
  8. public abstract void writeStructBegin(TStruct struct) throws TException;
  9. public abstract void writeStructEnd() throws TException;
  10. public abstract void writeFieldBegin(TField field) throws TException;
  11. public abstract void writeFieldEnd() throws TException;
  12. public abstract void writeFieldStop() throws TException;
  13. public abstract void writeMapBegin(TMap map) throws TException;
  14. public abstract void writeMapEnd() throws TException;
  15. public abstract void writeListBegin(TList list) throws TException;
  16. public abstract void writeListEnd() throws TException;
  17. public abstract void writeSetBegin(TSet set) throws TException;
  18. public abstract void writeSetEnd() throws TException;
  19. public abstract void writeBool(boolean b) throws TException;
  20. public abstract void writeByte(byte b) throws TException;
  21. public abstract void writeI16(short i16) throws TException;
  22. public abstract void writeI32(int i32) throws TException;
  23. public abstract void writeI64(long i64) throws TException;
  24. public abstract void writeDouble(double dub) throws TException;
  25. public abstract void writeString(String str) throws TException;
  26. public abstract void writeBinary(ByteBuffer buf) throws TException;
  27. /**
  28. * Reading methods.
  29. */
  30. public abstract TMessage readMessageBegin() throws TException;
  31. public abstract void readMessageEnd() throws TException;
  32. public abstract TStruct readStructBegin() throws TException;
  33. public abstract void readStructEnd() throws TException;
  34. public abstract TField readFieldBegin() throws TException;
  35. public abstract void readFieldEnd() throws TException;
  36. public abstract TMap readMapBegin() throws TException;
  37. public abstract void readMapEnd() throws TException;
  38. public abstract TList readListBegin() throws TException;
  39. public abstract void readListEnd() throws TException;
  40. public abstract TSet readSetBegin() throws TException;
  41. public abstract void readSetEnd() throws TException;
  42. public abstract boolean readBool() throws TException;
  43. public abstract byte readByte() throws TException;
  44. public abstract short readI16() throws TException;
  45. public abstract int readI32() throws TException;
  46. public abstract long readI64() throws TException;
  47. public abstract double readDouble() throws TException;
  48. public abstract String readString() throws TException;
  49. public abstract ByteBuffer readBinary() throws TException;
  50. /**
  51. * Reset any internal state back to a blank slate. This method only needs to
  52. * be implemented for stateful protocols.
  53. */
  54. public void reset() {}
  55. /**
  56. * Scheme accessor
  57. */
  58. public Class<? extends IScheme> getScheme() {
  59. return StandardScheme.class;
  60. }
  61. }

所谓协议就是客户端和服务器端约定传输什么数据,如何解析传输的数据。对于一个RPC调用的协议来说,要传输的数据主要有:

调用方

1. 方法的名称,包括类的名称和方法的名称

2. 方法的参数,包括类型和参数值

3.一些附加的数据,比如附件,超时事件,自定义的控制信息等等

返回方

1. 调用的返回码

2. 返回值

3.异常信息

从TProtocol的定义我们可以看出Thrift的协议约定如下事情:

1. 先writeMessageBegin表示开始传输消息了,写消息头。Message里面定义了方法名,调用的类型,版本号,消息seqId

2. 接下来是写方法的参数,实际就是写消息体。如果参数是一个类,就writeStructBegin

3. 接下来写字段,writeFieldBegin, 这个方法会写接下来的字段的数据类型和顺序号。这个顺序号是Thrfit对要传输的字段的一个编码,从1开始

4. 如果是一个集合就writeListBegin/writeMapBegin,如果是一个基本数据类型,比如int, 就直接writeI32

5. 每个复杂数据类型写完都调用writeXXXEnd,直到writeMessageEnd结束

6. 读消息时根据数据类型读取相应的长度

每个writeXXX都是采用消息头+消息体的方式。我们来看TBinaryProtocol的实现。

1. writeMessgeBegin方法写了消息头,包括4字节的版本号和类型信息,字符串类型的方法名,4字节的序列号seqId

2. writeFieldBegin,写了1个字节的字段数据类型,和2个字节字段的顺序号

3. writeI32,写了4个字节的字节数组

4. writeString,先写4字节消息头表示字符串长度,再写字符串字节

5. writeBinary,先写4字节消息头表示字节数组长度,再写字节数组内容

6.readMessageBegin时,先读4字节版本和类型信息,再读字符串,再读4字节序列号

7.readFieldBegin,先读1个字节的字段数据类型,再读2个字节的字段顺序号

8. readString时,先读4字节字符串长度,再读字符串内容。字符串统一采用UTF-8编码

  1. public void writeMessageBegin(TMessage message) throws TException {
  2. if (strictWrite_) {
  3. int version = VERSION_1 | message.type;
  4. writeI32(version);
  5. writeString(message.name);
  6. writeI32(message.seqid);
  7. } else {
  8. writeString(message.name);
  9. writeByte(message.type);
  10. writeI32(message.seqid);
  11. }
  12. }
  13. public void writeFieldBegin(TField field) throws TException {
  14. writeByte(field.type);
  15. writeI16(field.id);
  16. }
  17. private byte[] i32out = new byte[4];
  18. public void writeI32(int i32) throws TException {
  19. i32out[0] = (byte)(0xff & (i32 >> 24));
  20. i32out[1] = (byte)(0xff & (i32 >> 16));
  21. i32out[2] = (byte)(0xff & (i32 >> 8));
  22. i32out[3] = (byte)(0xff & (i32));
  23. trans_.write(i32out, 0, 4);
  24. }
  25. public void writeString(String str) throws TException {
  26. try {
  27. byte[] dat = str.getBytes("UTF-8");
  28. writeI32(dat.length);
  29. trans_.write(dat, 0, dat.length);
  30. } catch (UnsupportedEncodingException uex) {
  31. throw new TException("JVM DOES NOT SUPPORT UTF-8");
  32. }
  33. }
  34. public void writeBinary(ByteBuffer bin) throws TException {
  35. int length = bin.limit() - bin.position();
  36. writeI32(length);
  37. trans_.write(bin.array(), bin.position() + bin.arrayOffset(), length);
  38. }
  39. public TMessage readMessageBegin() throws TException {
  40. int size = readI32();
  41. if (size < 0) {
  42. int version = size & VERSION_MASK;
  43. if (version != VERSION_1) {
  44. throw new TProtocolException(TProtocolException.BAD_VERSION, "Bad version in readMessageBegin");
  45. }
  46. return new TMessage(readString(), (byte)(size & 0x000000ff), readI32());
  47. } else {
  48. if (strictRead_) {
  49. throw new TProtocolException(TProtocolException.BAD_VERSION, "Missing version in readMessageBegin, old client?");
  50. }
  51. return new TMessage(readStringBody(size), readByte(), readI32());
  52. }
  53. }
  54. public TField readFieldBegin() throws TException {
  55. byte type = readByte();
  56. short id = type == TType.STOP ? 0 : readI16();
  57. return new TField("", type, id);
  58. }
  59. public String readString() throws TException {
  60. int size = readI32();
  61. if (trans_.getBytesRemainingInBuffer() >= size) {
  62. try {
  63. String s = new String(trans_.getBuffer(), trans_.getBufferPosition(), size, "UTF-8");
  64. trans_.consumeBuffer(size);
  65. return s;
  66. } catch (UnsupportedEncodingException e) {
  67. throw new TException("JVM DOES NOT SUPPORT UTF-8");
  68. }
  69. }
  70. return readStringBody(size);
  71. }

TProtocol定义了基本的协议信息,包括传输什么数据,如何解析传输的数据的基本方法。

还存在一个问题,就是服务器端如何知道客户端发送过来的数据是怎么组合的,比如第一个字段是字符串类型,第二个字段是int。这个信息是在IDL生成客户端时生成的代码时提供了。Thrift生成的客户端代码提供了读写参数的方法,这两个方式是一一对应的,包括字段的序号,类型等等。客户端使用写参数的方法,服务器端使用读参数的方法。

关于IDL生成的客户端代码会在后面的文章具体描述。下面简单看一下自动生成的代码

1. 方法的调用从writeMessageBegin开始,发送了消息头信息

2. 写方法的参数,也就是写消息体。方法参数由一个统一的接口TBase描述,提供了read和write的统一接口。自动生成的代码提供了read, write方法参数的具体实现

3. 写完结束

  1. public void write_args(org.apache.thrift.protocol.TProtocol prot) throws org.apache.thrift.TException {
  2. prot.writeMessageBegin(new org.apache.thrift.protocol.TMessage("handle", org.apache.thrift.protocol.TMessageType.CALL, 0));
  3. handle_args args = new handle_args();
  4. args.setIdentity(identity);
  5. args.setUid(uid);
  6. args.setSid(sid);
  7. args.setType(type);
  8. args.setMessage(message);
  9. args.setParams(params);
  10. args.write(prot);
  11. prot.writeMessageEnd();
  12. }
  13. public interface TBase<T extends TBase<?,?>, F extends TFieldIdEnum> extends Comparable<T>,  Serializable {
  14. public void read(TProtocol iprot) throws TException;
  15. public void write(TProtocol oprot) throws TException;
  16. public static class handle_args <strong>implements org.apache.thrift.TBase</strong><handle_args, handle_args._Fields>, java.io.Serializable, Cloneable   {
  17. private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("handle_args");
  18. private static final org.apache.thrift.protocol.TField IDENTITY_FIELD_DESC = new org.apache.thrift.protocol.TField("identity", org.apache.thrift.protocol.TType.STRING, (short)1);
  19. private static final org.apache.thrift.protocol.TField UID_FIELD_DESC = new org.apache.thrift.protocol.TField("uid", org.apache.thrift.protocol.TType.I64, (short)2);
  20. private static final org.apache.thrift.protocol.TField SID_FIELD_DESC = new org.apache.thrift.protocol.TField("sid", org.apache.thrift.protocol.TType.STRING, (short)3);
  21. private static final org.apache.thrift.protocol.TField TYPE_FIELD_DESC = new org.apache.thrift.protocol.TField("type", org.apache.thrift.protocol.TType.I32, (short)4);
  22. private static final org.apache.thrift.protocol.TField MESSAGE_FIELD_DESC = new org.apache.thrift.protocol.TField("message", org.apache.thrift.protocol.TType.STRING, (short)5);
  23. private static final org.apache.thrift.protocol.TField PARAMS_FIELD_DESC = new org.apache.thrift.protocol.TField("params", org.apache.thrift.protocol.TType.MAP, (short)6);
  24. private static final Map<Class<? extends IScheme>, SchemeFactory> schemes = new HashMap<Class<? extends IScheme>, SchemeFactory>();
  25. static {
  26. schemes.put(StandardScheme.class, new handle_argsStandardSchemeFactory());
  27. schemes.put(TupleScheme.class, new handle_argsTupleSchemeFactory());
  28. }
  29. public String identity; // required
  30. public long uid; // required
  31. public String sid; // required
  32. public int type; // required
  33. public String message; // required
  34. public Map<String,String> params; // required
  35. /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
  36. public enum _Fields implements org.apache.thrift.TFieldIdEnum {
  37. IDENTITY((short)1, "identity"),
  38. UID((short)2, "uid"),
  39. SID((short)3, "sid"),
  40. TYPE((short)4, "type"),
  41. MESSAGE((short)5, "message"),
  42. PARAMS((short)6, "params");
  43. //  自动生成的写方法参数的方法,按照字段顺序写,给客户端代码使用
  44. public void write(org.apache.thrift.protocol.TProtocol oprot, handle_args struct) throws org.apache.thrift.TException {
  45. struct.validate();
  46. oprot.writeStructBegin(STRUCT_DESC);
  47. if (struct.identity != null) {
  48. oprot.writeFieldBegin(IDENTITY_FIELD_DESC);
  49. oprot.writeString(struct.identity);
  50. oprot.writeFieldEnd();
  51. }
  52. oprot.writeFieldBegin(UID_FIELD_DESC);
  53. oprot.writeI64(struct.uid);
  54. oprot.writeFieldEnd();
  55. if (struct.sid != null) {
  56. oprot.writeFieldBegin(SID_FIELD_DESC);
  57. oprot.writeString(struct.sid);
  58. oprot.writeFieldEnd();
  59. }
  60. oprot.writeFieldBegin(TYPE_FIELD_DESC);
  61. oprot.writeI32(struct.type);
  62. oprot.writeFieldEnd();
  63. if (struct.message != null) {
  64. oprot.writeFieldBegin(MESSAGE_FIELD_DESC);
  65. oprot.writeString(struct.message);
  66. oprot.writeFieldEnd();
  67. }
  68. <pre name="code" class="java">//  自动生成的读方法参数的方法,按照字段顺序读,给服务器端代码使用

public void read(org.apache.thrift.protocol.TProtocol iprot, handle_args struct) throws org.apache.thrift.TException {
        org.apache.thrift.protocol.TField schemeField;
        iprot.readStructBegin();
        while (true)
        {
          schemeField = iprot.readFieldBegin();
          if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { 
            break;
          }
          switch (schemeField.id) {
            case 1: // IDENTITY
              if (schemeField.type == org.apache.thrift.protocol.TType.STRING) {
                struct.identity = iprot.readString();
                struct.setIdentityIsSet(true);
              } else { 
                org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
              }
              break;
            case 2: // UID
              if (schemeField.type == org.apache.thrift.protocol.TType.I64) {
                struct.uid = iprot.readI64();
                struct.setUidIsSet(true);
              } else { 
                org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
              }
              break;
            case 3: // SID
              if (schemeField.type == org.apache.thrift.protocol.TType.STRING) {
                struct.sid = iprot.readString();
                struct.setSidIsSet(true);
              } else { 
                org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
              }
              break;
            case 4: // TYPE
              if (schemeField.type == org.apache.thrift.protocol.TType.I32) {
                struct.type = iprot.readI32();
                struct.setTypeIsSet(true);
              } else { 
                org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
              }
              break;

Thrift源码分析(二)-- 协议和编解码的更多相关文章

  1. Tomcat源码分析二:先看看Tomcat的整体架构

    Tomcat源码分析二:先看看Tomcat的整体架构 Tomcat架构图 我们先来看一张比较经典的Tomcat架构图: 从这张图中,我们可以看出Tomcat中含有Server.Service.Conn ...

  2. 十、Spring之BeanFactory源码分析(二)

    Spring之BeanFactory源码分析(二) 前言 在前面我们简单的分析了BeanFactory的结构,ListableBeanFactory,HierarchicalBeanFactory,A ...

  3. Fresco 源码分析(二) Fresco客户端与服务端交互(1) 解决遗留的Q1问题

    4.2 Fresco客户端与服务端的交互(一) 解决Q1问题 从这篇博客开始,我们开始讨论客户端与服务端是如何交互的,这个交互的入口,我们从Q1问题入手(博客按照这样的问题入手,是因为当时我也是从这里 ...

  4. 框架-springmvc源码分析(二)

    框架-springmvc源码分析(二) 参考: http://www.cnblogs.com/leftthen/p/5207787.html http://www.cnblogs.com/leftth ...

  5. Vue源码分析(二) : Vue实例挂载

    Vue源码分析(二) : Vue实例挂载 author: @TiffanysBear 实例挂载主要是 $mount 方法的实现,在 src/platforms/web/entry-runtime-wi ...

  6. 多线程之美8一 AbstractQueuedSynchronizer源码分析<二>

    目录 AQS的源码分析 该篇主要分析AQS的ConditionObject,是AQS的内部类,实现等待通知机制. 1.条件队列 条件队列与AQS中的同步队列有所不同,结构图如下: 两者区别: 1.链表 ...

  7. Netty 源码 ChannelHandler(四)编解码技术

    Netty 源码 ChannelHandler(四)编解码技术 Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) 一.拆包与粘 ...

  8. Thrift源码学习二——Server层

    Thrift 提供了如图五种模式:TSimpleServer.TNonblockingServer.THsHaServer.TThreadPoolServer.TThreadSelectorServe ...

  9. Thrift源码分析(一)-- 基本概念

    我所在的公司使用Thrift作为基础通信组件,相当一部分的RPC服务基于Thrift框架.公司的日UV在千万级别,Thrift很好地支持了高并发访问,并且Thrift相对简单地编程模型也提高了服务地开 ...

随机推荐

  1. php 安装xdebug扩展和配置phpstorm断点

    参考链接:安装xdebug扩展和配置phpstorm断点

  2. 一种小型后台管理系统通用开发框架中的Cache缓存设计

    本篇博客记录一下我在实习的公司的后台管理系统开发框架中学习到的一种关于网站的缓存(Cache)的实现方法,我会在弄懂的基础上,将该方法在.net core上进行实现.因为公司开发都是基于.net fr ...

  3. .NET 导入Excel服务器报未在本地计算机上注册 Microsoft.Jet.Oledb.4.0提供程序

    代码已经在本地运行正常,发布到服务器后,一直报未在本地计算机上注册 Microsoft.Jet.Oledb.4.0提供程序, 解决办法:读取EXCEL文件时最好使用ACE方式,jet对于高版本系统的服 ...

  4. python 数据结构之图的储存方式

    参考链接:https://blog.csdn.net/u014281392/article/details/79120406 所描述的图的结构为: 下面介绍不同的储存方式,我想不必详细分别是每个名称都 ...

  5. 2019 苏州朗动java面试笔试题 (含面试题解析)

      本人5年开发经验.18年年底开始跑路找工作,在互联网寒冬下成功拿到阿里巴巴.今日头条.苏州朗动等公司offer,岗位是Java后端开发,因为发展原因最终选择去了苏州朗动,入职一年时间了,也成为了面 ...

  6. 分享几套2019年各大公司最新的PHP面试题,几斤几两一试便知

    从面试题发现不足,进而查漏补缺,比通过面试更难得 PHP面试题2019年奇虎360面试题和答案解析 PHP面试题2019年京东工程师面试题和答案解析 PHP面试题2019年新浪工程师面试题和答案解析 ...

  7. Redis缓存数据库基础

    思维导图xmind文件:https://files-cdn.cnblogs.com/files/benjieming/Redis.zip

  8. C# DataTable 转实体对象

    DataTable 转实体对象 /// <summary> /// DataTable通过反射获取单个对象 /// </summary> public static T ToS ...

  9. Qt QVector简单用法

    添加元素 QVector<QString> strArray; strArray.append("Hello"); 遍历 QVector<QString>: ...

  10. 配置WDA程序到NWBC

    NWBC是通过角色来做的权限管理,包括菜单项. 如果用户没有分配对应的,登陆后看到的东西很少,或者空白. 这里需要先将BC的个性化参数全部配齐,方便使用.BC最左上角,点击,设置,个性化设置. 如下几 ...