Thrift 协议层简单介绍。


协议层类关系图

协议层相关的类主要实现与数据传输格式封装的协议相关的内容。可以发现,所有的协议类都直接或间接继承 TProtocol 类,协议层类关系图主要分为三部分来看:

  • 抽象基类 Tprotocol,它是所有协议类的基类,有很大一部分类直接从它继承实现它定义的接口函数(纯虚函数)
  • TProtocol 的默认实现 TProtocolDefaults 类和虚拟协议类 TVirtualProtocol 及其子类
  • 各种协议类的对象生成工厂类,负责某一种具体协议类对象的生产

Tprotocol

源码路径:thrift/lib/cpp/src/thrift/protocol/TProtocol.h

抽象类 TProtocol 对于每一种数据类型都提供了读写的开始和结束的方法,这里的读写方法应该是针对网络 I/O 读写,不过真正实现网络读写的是 TTransport 相关类,此处的读写方法主要针对数据的处理(如数据格式调整)。除了有针对具体的数据类型的读写方法,消息也可以通过网络传递。所以也定义了消息的读写方法。此外,还定义了一些公用的功能,例如跳过某一个结构不读,大小端数据格式调整、主机字节序和网络字节序的相互转换等。

支持的数据结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// thrift 协议支持的数据类型
enum TType {
  T_STOP       = 0,
  T_VOID       = 1,
  T_BOOL       = 2,
  T_BYTE       = 3,
  T_I08        = 3,
  T_I16        = 6,
  T_I32        = 8,
  T_U64        = 9,
  T_I64        = 10,
  T_DOUBLE     = 4,
  T_STRING     = 11,
  T_UTF7       = 11,
  T_STRUCT     = 12,
  T_MAP        = 13,
  T_SET        = 14,
  T_LIST       = 15,
  T_UTF8       = 16,
  T_UTF16      = 17
};

// thrift 支持的消息类型
enum TMessageType {
  T_CALL       = 1,
  T_REPLY      = 2,
  T_EXCEPTION  = 3,
  T_ONEWAY     = 4
};

写函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 纯虚函数
virtual unit32_t write(*)Begin_virt(const parameters) = 0;
virtual unit32_t write(*)End_virt() = 0;
virtual unit32_t writeFiledStop_virt() = 0; // 只有 Filed 有 stop
virtual unit32_t write(TtypeName)_virt(const parameter) = 0;

// 调用相应纯虚函数的函数
unit32_t functionName(<const parameters>){
    T_VIRTUAL_CALL();       // 调用打印日志函数
    return functionName_virt(<const parameters);  // 调用纯虚函数
}

读函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 纯虚函数
virtual unit32_t read(*)Begin_virt(parameters) = 0;
virtual unit32_t read(*)End_virt() = 0;
virtual unit32_t read(TtypeName)_virt(parameter) = 0;

// 另一个 readBool() 函数,
// std::vector is specialized for bool, and its elements are individual bits
// rather than bools.   We need to define a different version of readBool()
// to work with std::vector<bool>.
virtual uint32_t readBool_virt(std::vector<bool>::reference value) = 0; 

// 调用相应纯虚函数的函数
unit32_t functionName(<parameters>){
    T_VIRTUAL_CALL();       // 调用打印日志函数
    return functionName_virt(<parameters);  // 调用纯虚函数
}

构造函数

1
2
3
4
protected:
  TProtocol(std::shared_ptr<TTransport> ptrans)
    : ptrans_(ptrans), input_recursion_depth_(0), output_recursion_depth_(0), recursion_limit_(DEFAULT_RECURSION_LIMIT)
  {}

其他函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 跳过任意数据类型的函数
uint32_t skip(TType type) {
    T_VIRTUAL_CALL();
    return skip_virt(type);
}

virtual uint32_t skip_virt(TType type){
    // 调用此命名空间下全局函数实现
    // 在该头文件的最后定义模板函数 skip 以适用于不同的 Protocol 类
    return ::apache::thrift::protocol::skip(*this, type);
}

inline std::shared_ptr<TTransport> getTransport() { return ptrans_; }

此外还有一些与输入,输出递归深度有关的函数在上面并未列出。

TProtocolDefaults

源码路径:thrift/lib/cpp/src/thrift/protocol/TVirtualProtocol.h

继承关系

1
class TProtocolDefaults : public TProtocol {};

主要操作

  1. 重写了父类非虚拟的读写函数,这些函数都会抛出一个 TProtocolException::NOT_IMPLEMENTED 异常
  2. 重写了父类的 skip 函数,直接调用父类中的 skip 模板函数
  3. 构造函数直接调用父类的构造函数

TVirtualProtocol

源码路径:thrift/lib/cpp/src/thrift/protocol/TVirtualProtocol.h

继承关系

1
2
template <class Protocol_, class Super_ = TProtocolDefaults>
class TVirtualProtocol : public Super_ {};

读写函数

TVirtualProtocol 类中的读写函数会重写父类中的读写纯虚函数,其都是通过 this 指针调用模板参数 class Protocol_ 中相应的实现方法来实现自己的读写函数,例如:

1
2
3
4
5
uint32_t writeMessageBegin_virt(const std::string& name,
                                const TMessageType messageType,
                                const int32_t seqid) override { // override 关键字
    return static_cast<Protocol_*>(this)->writeMessageBegin(name, messageType, seqid);
}

其他操作

  1. 提供了 skip 函数和针对 std::vector<boo>readBool 函数的默认实现
  2. 构造函数直接调用父类的构造函数

为何要有 TProtocolDefaultsTVirtualProtocol

下面来分析如果不从定义的默认实现类继承,直接从抽象类继承怎样会产生无限递归调用。现在我们假设直接从抽象类继承,那么如果一个指向子类对象的父类(TProtocol)调用 writeMessageBegin 方法,因为这个方法不是虚拟函数(不会动态绑定)所以就会调用父类的 writeMessageBegin 方法,然后父类会直接调用它的纯虚函数 writeMessageBegin_virt,这个函数就会动态绑定,就会执行子类的实现,在这里就是通过虚协议类 TVirtualProtocol 实现的,而这个函数又会调用之类的 writeMessageBegin 方法,如果子类没有实现这个方法,那么就又回到父类的这个方法了,从而产生无限递归调用。那么如果默认是从默认实现类 TProtocolDefaults 继承,那么就会执行它的 writeMessageBegin 方法,从而抛出一个异常,就不会产生无限递归调用。