本节主要介绍缓冲相关的传输类,缓存的作用就是为了提高读写的效率。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) = ;//慢函数
   virtual void writeSlow(const uint8_t* buf, uint32_t len) = ;
   virtual const uint8_t* borrowSlow(uint8_t* buf, uint32_t* len) = ;
   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 > ) {
   memcpy(buf, rBase_, have);//拷贝数据
   setReadBuffer(rBuf_.get(), );//设置读缓存,基类实现该函数
   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 >= *wBufSize_) || (have_bytes == )) {
   if (have_bytes > ) {//缓存大于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之TTransport层的缓存传输类TBufferedTransport和缓冲基类TBufferBase的更多相关文章

  1. thrift之TTransport层的内存缓存传输类TMemoryBuffer

    内存缓存是简单的在内存进行读写操作的一种传输,任何时候想在上面写入数据都是放入缓存中,任何时候读操作数据也是来至于缓存.内存缓存的分配使用c语言的malloc类函数,分配的长度是需要长度的两倍,需要考 ...

  2. thrift之TTransport层的堵塞的套接字I/O传输类TSocket

    本节将介绍第一个实现具体传输功能的类TSocket,这个类是基于TCP socket实现TTransport的接口.下面具体介绍这个类的相关函数功能实现. 1.构造函数 分析一个类的功能首先看它的定义 ...

  3. thrift之TTransport层的分帧传输类TFramedTransport

    帧传输类就是按照一帧的固定大小来传输数据,所有的写操作首先都是在内存中完成的直到调用了flush操作,然后传输节点在flush操作之后将所有数据根据数据的有效载荷写入数据的长度的二进制块发送出去,允许 ...

  4. jdbc链接hive报错:java.lang.ClassNotFoundException: org.apache.thrift.transport.TTransport

    写了个jdbc连接hive2的demo,结果报错:java.lang.ClassNotFoundException: org.apache.thrift.transport.TTransport,实际 ...

  5. pyhive -- thrift.transport.TTransport.TTransportException: TSocket read 0 bytes

    Pyhive 远程连接hive出现问题: from pyhive import hive import pandas as pd #Create Hive connection conn = hive ...

  6. thrift.transport.TTransport.TTransportException: Could not start SASL: Error in sasl_client_start (-4) SASL(-4): no mechanism available: No worthy mechs found

    thrift.transport.TTransport.TTransportException: Could not start SASL: Error in sasl_client_start (- ...

  7. QCache 缓存(类似于map的模板类,逻辑意义上的缓存Cache,方便管理,默认类似于LRU的淘汰算法)

    最近在学习缓存方面的知识,了解了缓存(Cache)的基本概念,为什么要使用缓存,以及一些缓存算法(缓存替换),如LRU.LFU.ARC等等. 这些缓存算法的实现过程会使用一些基本的数据结构,如list ...

  8. [转]掌握 ASP.NET 之路:自定义实体类简介 --自定义实体类和DataSet的比较

    转自: http://www.microsoft.com/china/msdn/library/webservices/asp.net/CustEntCls.mspx?mfr=true 发布日期 : ...

  9. 9_13学习完整修改和查询&&实体类,数据访问类

    完整修改和查询:中间变量运用. 1.先查 2.执行操作 ---------------------------------------------------- namespace ADO.NET_小 ...

随机推荐

  1. vc 中调用COM组件的方法

    需求:1.创建myCom.dll,该COM只有一个组件,两个接口:   IGetRes--方法Hello(),   IGetResEx--方法HelloEx() 2.在工程中导入组件或类型库  #im ...

  2. BSF、BSR: 位扫描指令

    ;BSF(Bit Scan Forward): 位扫描找1, 低 -> 高 ;BSR(Bit Scan Reverse): 位扫描找1, 高 -> 低   找到是 1 的位后, 把位置数给 ...

  3. 1.Mybatis原理

    Mybatis是一个持久层框架,Apache底下的一个项目,它的前身是ibatis,它支持普通的SQL查询,存储过程和高级映射的优秀框架.Mybatis消除了几乎所有的JDBC代码和参数的手工设置以及 ...

  4. WebForm ASP开发方式、 IIS服务器以及WebForm开发基础

    网页端 B/S两种:(ASP.NET  --网站应用开发技术) WebForm:  微软最先出现的网站开发技术 MVC:  后来开发,为了让java.PHP转过来的开发人员更容易接受使用 客户端 C/ ...

  5. Installshield 打包安装包心得

     制作简单的安装软件 声明:下面的教程,是把读者当做完全没接触过IS的角度来制作的. 1. 启动InstallShield 12.建立一个InstallShield MSI Project,如图: 2 ...

  6. SqlServer2008R2附件数据库失败

    MSSQL附加数据库时提示以下错误: 无法打开物理文件“***.mdf”.操作系统错误 5:“5(拒绝访问.)”. (Microsoft SQL Server,错误: 5120) 该经验介绍如何处理该 ...

  7. hdu 2669 Romantic (乘法逆元)

    Romantic Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Su ...

  8. Light OJ 1025 - The Specials Menu(动态规划-区间dp)

    题目链接:http://www.lightoj.com/volume_showproblem.php?problem=1025 题目大意:一串字符, 通过删除其中一些字符, 能够使这串字符变成回文串. ...

  9. Bridging signals hdu 1950 (最长上升子序列)

    http://acm.split.hdu.edu.cn/showproblem.php?pid=1950 题意:求最长上升(不连续or连续)子序列 推荐博客链接: http://blog.csdn.n ...

  10. fail2ban 原理 安装 使用

    cd fail2ban python setup.py install /etc/fail2ban/ 为配置文件目录; /usr/lib/pythonx.x/site-packages/fail2ba ...