`
thecloud
  • 浏览: 882088 次
文章分类
社区版块
存档分类
最新评论

thrift之TTransport层的缓存传输类TBufferedTransport和缓冲基类TBufferBase

 
阅读更多
本节主要介绍缓冲相关的传输类,缓存的作用就是为了提高读写的效率。Thrift在实现缓存传输的时候首先建立一个缓存的基类,然后需要实现缓存功能的类都可以直接从这个基类继承。下面就详细分析这个基类以及一个具体的实现类。
  缓存基类TBufferBase
  缓存基类就是让传输类所有的读写函数都提供缓存来提高性能。它在通常情况下采用memcpy来设计和实现快路径的读写访问操作,这些操作函数通常都是小、非虚拟和内联函数。TBufferBase是一个抽象的基类,子类必须实现慢路径的读写函数等操作,慢路径的读写等操作主要是为了在缓存已经满或空的情况下执行。首先看看缓存基类的定义,代码如下:
  class TBufferBase : public TVirtualTransport<TBufferBase> {
   public:
    uint32_t read(uint8_t* buf, uint32_t len) {//读函数
      uint8_t* new_rBase = rBase_ + len;//得到需要读到的缓存边界
      if (TDB_LIKELY(new_rBase <= rBound_)) {//判断缓存是否有足够的数据可读,采用了分支预测技术
        std::memcpy(buf, rBase_, len);//直接内存拷贝
        rBase_ = new_rBase;//更新新的缓存读基地址
        return len;//返回读取的长度
      }
      return readSlow(buf, len);//如果缓存已经不能够满足读取长度需要就执行慢读
    }
    uint32_t readAll(uint8_t* buf, uint32_t len) {
      uint8_t* new_rBase = rBase_ + len;//同read函数
      if (TDB_LIKELY(new_rBase <= rBound_)) {
        std::memcpy(buf, rBase_, len);
        rBase_ = new_rBase;
        return len;
      }
      return apache::thrift::transport::readAll(*this, buf, len);//调用父类的
    }
    void write(const uint8_t* buf, uint32_t len) {//快速写函数
      uint8_t* new_wBase = wBase_ + len;//写入后的新缓存基地址
      if (TDB_LIKELY(new_wBase <= wBound_)) {//判断缓存是否有足够的空间可以写入
        std::memcpy(wBase_, buf, len);//内存拷贝
        wBase_ = new_wBase;//更新基地址
        return;
      }
      writeSlow(buf, len);//缓存空间不足就调用慢写函数
    }
    const uint8_t* borrow(uint8_t* buf, uint32_t* len) {//快速路径借
      if (TDB_LIKELY(static_cast<ptrdiff_t>(*len) <= rBound_ - rBase_)) {//判断是否足够借的长度
        *len = static_cast<uint32_t>(rBound_ - rBase_);
        return rBase_;//返回借的基地址
      }
      return borrowSlow(buf, len);//不足就采用慢路径借
    }
    void consume(uint32_t len) {//消费函数
      if (TDB_LIKELY(static_cast<ptrdiff_t>(len) <= rBound_ - rBase_)) {//判断缓存是否够消费
        rBase_ += len;//更新已经消耗的长度
      } else {
        throw TTransportException(TTransportException::BAD_ARGS,
                                  "consume did not follow a borrow.");//不足抛异常
      }
    }
   protected:
    virtual uint32_t readSlow(uint8_t* buf, uint32_t len) = 0;//慢函数
    virtual void writeSlow(const uint8_t* buf, uint32_t len) = 0;
    virtual const uint8_t* borrowSlow(uint8_t* buf, uint32_t* len) = 0;
    TBufferBase()
      : rBase_(NULL)
      , rBound_(NULL)
      , wBase_(NULL)
      , wBound_(NULL)
    {}//构造函数,把所有的缓存空间设置为NULL
    void setReadBuffer(uint8_t* buf, uint32_t len) {//设置读缓存空间地址
      rBase_ = buf;//读缓存开始地址
      rBound_ = buf+len;//读缓存地址界限
    }
    void setWriteBuffer(uint8_t* buf, uint32_t len) {//设置写缓存地址空间
      wBase_ = buf;//起
      wBound_ = buf+len;//边界
    }
    virtual ~TBufferBase() {}
    uint8_t* rBase_;//读从这儿开始
    uint8_t* rBound_;//读界限
    uint8_t* wBase_;//写开始地址
    uint8_t* wBound_;//写界限
  };
  从TBufferBase定义可以看出,它也是从虚拟类继承,主要采用了memcpy函数来实现缓存的快速读取,在判断是否有足够的缓存空间可以操作时采用了分支预测技术来提供代码的执行效率,且所有快路径函数都是非虚拟的、内联的小代码量函数。下面继续看看一个具体实现缓存基类的一个子类的情况!
  TBufferedTransport
  缓存传输类是从缓存基类继承而来,它对于读:实际读数据的大小比实际请求的大很多,多余的数据将为将来超过本地缓存的数据服务;对于写:数据在它被发送出去以前将被先写入内存缓存。
  缓存的大小默认是512字节(代码:static const int DEFAULT_BUFFER_SIZE = 512;),提供多个构造函数,可以只指定一个传输类(另一层次的)、也可以指定读写缓存公用的大小或者分别指定。因为它是一个可以实际使用的缓存类,所以需要实现慢读和慢写功能的函数。它还实现了打开函数open、关闭函数close、刷新函数flush等,判断是否有数据处于未决状态函数peek定义和实现如下:
    bool peek() {
      if (rBase_ == rBound_) {//判断读的基地址与读边界是否重合了,也就是已经读取完毕
        setReadBuffer(rBuf_.get(), transport_->read(rBuf_.get(), rBufSize_));//是:重新读取底层来的数据
      }
      return (rBound_ > rBase_);//边界大于基地址就是有未决状态数据
    }
  下面继续看看慢读函数和慢写函数的实现细节(快读和快写继承基类的:也就是默认的读写都是直接从缓存中读取,所谓的快读和快写)。慢读函数实现如下(详细注释):
  uint32_t TBufferedTransport::readSlow(uint8_t* buf, uint32_t len) {
    uint32_t have = rBound_ - rBase_;//计算还有多少数据在缓存中
  
    // 如果读取缓存中已经存在的数据不能满足我们,
    // 我们(也仅仅在这种情况下)应该才从慢路径读数据。
    assert(have < len);
  
    // 如果我们有一些数据在缓存,拷贝出来并返回它
    // 我们不得不返回它而去尝试读更多的数据,因为我们不能保证
    // 下层传输实际有更多的数据, 因此尝试阻塞式读取它。
    if (have > 0) {
      memcpy(buf, rBase_, have);//拷贝数据
      setReadBuffer(rBuf_.get(), 0);//设置读缓存,基类实现该函数
      return have;//返回缓存中已经存在的不完整数据
    }
  
    // 在我们的缓存中没有更多的数据可用。从下层传输得到更多以达到buffer的大小。
    // 注意如果len小于rBufSize_可能会产生多种场景否则几乎是没有意义的。
    setReadBuffer(rBuf_.get(), transport_->read(rBuf_.get(), rBufSize_));//读取数据并设置读缓存
  
    // 处理我们已有的数据
    uint32_t give = std::min(len, static_cast<uint32_t>(rBound_ - rBase_));
    memcpy(buf, rBase_, give);
    rBase_ += give;
  
    return give;
  }
  慢读函数主要考虑的问题就是缓存中还有一部分数据,但是不够我们需要读取的长度;还有比较麻烦的情况是虽然现在缓存中没有数据,但是我们从下层传输去读,读取的长度可能大于、小于或等于我们需要读取的长度,所以需要考虑各种情况。下面继续分析慢写函数实现细节:
  void TBufferedTransport::writeSlow(const uint8_t* buf, uint32_t len) {
    uint32_t have_bytes = wBase_ - wBuf_.get();//计算写缓存区中已有的字节数
    uint32_t space = wBound_ - wBase_;//计算剩余写缓存空间
    // 如果在缓存区的空闲空间不能容纳我们的数据,我们采用慢路径写(仅仅)
    assert(wBound_ - wBase_ < static_cast<ptrdiff_t>(len));
  
 	  //已有数据加上需要写入的数据是否大于2倍写缓存区或者缓存区为空
    if ((have_bytes + len >= 2*wBufSize_) || (have_bytes == 0)) {
      if (have_bytes > 0) {//缓存大于0且加上需要再写入数据的长度大于2倍缓存区
        transport_->write(wBuf_.get(), have_bytes);//先将已有数据写入下层传输
      }
      transport_->write(buf, len);//写入这次的len长度的数据
      wBase_ = wBuf_.get();//重新得到写缓存的基地址
      return;
    }
  
    memcpy(wBase_, buf, space);//填充我们的内部缓存区为了写
    buf += space;
    len -= space;
    transport_->write(wBuf_.get(), wBufSize_);//写入下层传输
  
    assert(len < wBufSize_);
    memcpy(wBuf_.get(), buf, len);//拷贝剩余的数据到我们的缓存
    wBase_ = wBuf_.get() + len;//重新得到写缓存基地址
    return;
  }
  慢写函数也有棘手的问题,就是我们应该拷贝我们的数据到我们的内部缓存并且从那儿发送出去,或者我们应该仅仅用一次系统调用把当前内部写缓存区的内容写出去,然后再用一次系统调用把我们当前需要写入长度为len的数据再次写入出去。如果当前缓存区的数据加上我们这次需要写入数据的长度至少是我们缓存区长度的两倍,我们将不得不至少调用两次系统调用(缓存区为空时有可能例外),那么我们就不拷贝了。否则我们就是按顺序递加的。具体实现分情况处理,最后我们在看看慢借函数的实现,借相关函数主要是为了实现可变长度编码。慢借函数实现细节如下:
  const uint8_t* TBufferedTransport::borrowSlow(uint8_t* buf, uint32_t* len) {
    (void) buf;
    (void) len;
    return NULL;//默认返回空
  }


  在这个类我们可以看出,它什么也没有做,只是简单的返回NULL,所以需要阻塞去借。按照官方的说法,下面两种行为应该当前的版本中实现,在将来的版本可能会发生改变:
  如果需要借的长度最多为缓存区的长度,那么永远不会返回NULL。依赖底层传输,它应该抛出一个异常或者永远不会挂掉;
  一些借用请求可能内部字节拷贝,如果借用的长度最多是缓存区的一半,那么不去内部拷贝。为了优化性能保存这个限制。
分享到:
评论

相关推荐

    thrift-netty:Thrift on Netty, 利用Netty重构Thrift的Transport和数据传输协议

    thrift-nettyThrift on Netty, 利用Netty重构Thrift的Transport和数据传输协议。

    Thrift之C++初体验

    thrift是一个软件框架,用来进行可扩展且跨语言的服务的开发。它结合了功能强大的软件堆栈和代码生成引擎,以构建在 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, ...

    thrift初步了解

    不要认为thrift能在*.thrift文件中定义调度之类的工作 thrift通过IDL(接口描述语言)来生成各种语言需要的接口代码。 执行thrift文件命令: java:thrift-0.9.1.exe --gen java test.thrift java:thrift-0.9.1.exe ...

    thrift-0.12.0

    Thrift是一个跨语言的服务部署框架,最初由Facebook于2007年开发,2008年进入Apache开源项目。... Erlang, Perl, Haskell, C#, Cocoa, Smalltalk和OCaml),并由生成的代码负责RPC协议层和传输层的实现。

    apache-thrift-amqp:支持 Apache Thrift 的 AMQP 传输层

    apache-thrift-amqp 支持 Apache Thrift 的 AMQP 传输层该项目为 Ruby 中的 Apache Thrift 实现了 AMQP 传输层,并扩展了 Apache Thrift 教程。 AMQP 传输层依赖 Ruby bunny gem 来连接到 RabbitMQ 代理。 本教程...

    thrift通过http传输的java例子

    thrift通过http实现的一个java例子!

    thrift介绍、各种server的比较、多接口服务实现

    thrift特性、不支持的特性、对各个语言的支持情况、语法参考、Thrift 架构、协议、传输层、服务端类型、各种thriftServer实现的比较、Thrift对多接口服务的支持

    使用thrift、websocket在javascript和cpp之间建立rpc调用

    使用thrift、websocket在javascript和c++之间建立rpc调用机制。 如果要使用js+html来写界面,cpp来写底层业务逻辑,这就非常有用了。 当然,如果底层不用cpp来写,也可以参考本代码的js对thrift的rpc包装。

    thrift-mina:Thrift on Apache Mina, 利用Apache Mina重构Thrift的Transport和数据传输协议

    thrift-mina Thrift on Apache Mina, 利用Apache Mina重构Thrift的Transport和数据传输协议。

    thrift-0.9.1.exe和thrift-0.9.2.exe

    thrift文件生成工具thrift-0.9.1.exe和thrift-0.9.2.exe压缩包

    用C#和C++写的Apache Thrift的小范例

    本例改编自Apache Thrift教程: http://mikecvet.wordpress.com/2010/05/13/apache-thrift-tutorial-the-sequel/ http://chanian.com/2010/05/13/thrift-tutorial-a-php-client/ 原教程使用的是c++ server和...

    the programmer's guide to apache thrift

    Apache Thrift is an open source cross language serialization and RPC framework. With support for over 15 programming languages, Apache Thrift can play an important role in a range of distributed ...

    Thrift的官文翻译

    这个是Thrift的官文翻译版,我从其他地方取到的图片,现在为了方便大家阅读已经作成了PDF版本了,免费下载

    Thrift-java学习小结

    NULL 博文链接:https://onlyor.iteye.com/blog/1700777

    thrift通过openssl加密证书实现双向通信

    thrift框架通过vs2013编译好的静态库,然后通过vs2013实现双向通信代码,通信协议利用openssl加密证书的方式来实现,本资源给出了完整的实现代码,证书可以在网上百度一下,看看如何生成客户端和服务端的,然后编译...

    Windows下QT使用Thrift的样例

    网上一直搜不到Windows下QT使用thrift的例子,自己整了个 QT版本 5.8.0 Boost版本 boost_1_61_0 Thrift版本 thrift-0.10.0

    thrift源码

    thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码...

    使用wireshark抓取thrift协议接口调用

    使用wireshark抓取thrift协议接口调用

    thrift安装

    thrift 安装包。

    thrift-0.9.2.tar.gz

    thrift,Apache Thrift 0.9.2 版本,解压后直接直接安装,可伸缩的跨语言服务开发框架,命令: 解压命令:tar -zxf thrift-0.9.2.tar.gz 安装命令:./configure --with-lua=no && make && make install 查看版本:...

Global site tag (gtag.js) - Google Analytics