thrift之TTransport层的内存缓存传输类TMemoryBuffer
内存缓存是简单的在内存进行读写操作的一种传输,任何时候想在上面写入数据都是放入缓存中,任何时候读操作数据也是来至于缓存。内存缓存的分配使用c语言的malloc类函数,分配的长度是需要长度的两倍,需要考虑这些内存缓存的使用范围。
同样这个类继承至缓存基类TBufferBase,默认的缓存大小是1024(static const uint32_t defaultSize = 1024;),所有的构造函数都调用函数initCommon,这个函数实现如下:
voidinitCommon(uint8_t* buf, uint32_t size, bool owner, uint32_t wPos) { if (buf== NULL && size != 0) {//如果传递过来的buf为null并且size不为0 assert(owner);//断言这个内存缓存是不是自己所有,不是就报错不继续执行后面的代码了 buf =(uint8_t*)std::malloc(size);//是自己拥有的内存缓存就自己分配size大小的内存作为缓存 if(buf == NULL) { throw std::bad_alloc();//分配失败抛出异常 } } buffer_= buf;//初始化缓存成员变量 bufferSize_ = size;//大小 rBase_= buffer_;基地址 rBound_= buffer_ + wPos;//界限 wBase_= buffer_ + wPos;//写缓存基地址 wBound_= buffer_ + bufferSize_;//写界限 owner_= owner;//是否自己拥有这段内存缓存 }
上面代码需要说明是:在初始化这个类的时候对于内存缓存是不是属于类自己拥有有不同的处理方式,后面介绍各个方针;还有一点就是读的边界其实就是写的起始地址,因为写入开始的地方就是没有有效数据的地方了,就是一个生存者和消费者的问题,这个后面介绍的一个函数computeRead会更新相关内容。下面我们就看看内存缓存采取的几种可配置的方针(对待内存缓存的方式),是定义一个枚举标识每一个方针的,定义如下:
enumMemoryPolicy { OBSERVE= 1 , COPY =2 ,TAKE_OWNERSHIP = 3 };
下面分配分析这三种方针,第一种称为观察方针,意思是内存缓存传输类只是简单的存储一个指针存放到内存,它是调用者来保证内存缓存在剩余的时间指向一个有效的内存,并且在适当的时候清理它(注意:这种内存缓存是不允许写入数据的);第二种是内存缓存类内部的一种拷贝,调用者无任何责任;最后一种就是内存缓存类自己拥有的内存缓存,这个必须自己释放,并且在使用以前需要使用malloc分配。明白了三种内存方针了,就知道什么时候需要的是哪一种。内存使用方针除了被构造函数使用以外,我们在重置内存缓存区时也会使用到,而重置缓存函数是resetBuffer,有两个函数重载。在实现重载函数的时候使用了一点点优化手段,就是交换内存的时候使用std::swap,这个函数的好处就是如果是相同的内存分配器分配的就直接交换指针头就可以了。
同其它缓存类一样,内存缓存类通常采用快读和快写去操作数据,这些操作基类的实现已经满足要求了,只是在缓存中数据不足时就会调用慢读和慢写函数从更底层的传输去拿数据,所以慢写和慢读函数实现是不相同的。我们现在就看看内存缓存类的慢读和慢写函数。慢读函数readSlow函数现实如下:
uint32_t TMemoryBuffer::readSlow(uint8_t* buf,uint32_t len) {
uint8_t*start;//缓存读取开始地址
uint32_tgive;//缓存可以读取的长度
computeRead(len, &start, &give);//计算缓存中可以读取的开始地址和长度
// 把缓存中的数据拷贝到提供的buf中
memcpy(buf, start, give);
returngive;//返回实际读取到的数据,可能小于len
}
在读取数据以前先要计算可以读取的开始地址和长度(因为写函数会更新缓存中的数据),这个功能是函数computeRead实现,代码如下:
voidTMemoryBuffer::computeRead(uint32_t len, uint8_t** out_start, uint32_t*out_give) { rBound_ = wBase_;//更新读取的边界,写的起始地址就是读取的最大限制地址 //取小者,因为数据可能不够,可利用的读长度就是写的起始地址减去读的起始地址 uint32_t give = std::min(len,available_read()); *out_start = rBase_;//返回的读起始地址 *out_give = give;//返回读取的可用长度 rBase_ += give;//更新读取的起始地址 }
下面继续分析慢写函数的实现,代码如下:
voidTMemoryBuffer::writeSlow(const uint8_t* buf, uint32_t len) { ensureCanWrite(len);//确保能够写入 // 拷贝数据到写缓存的基地址 memcpy(wBase_, buf, len); wBase_ += len;//更新写缓存的基地址 }
在写入之前需要保证是可以写入的,实现这个保证的函数是ensureCanWrite,代码如下:
void TMemoryBuffer::ensureCanWrite(uint32_tlen) { uint32_t avail = available_write();//检查可以利用的缓存空间 if (len <= avail) {//如果空间足够就直接返回了 return; } if (!owner_) {//检查内存是否自己拥有 throwTTransportException("Insufficient space in external MemoryBuffer");//抛出不是自己拥有不能写入 } // 根据需要增加缓存空间. uint32_t new_size = bufferSize_; while (len > avail) {//循环增加,每次都是上次的二倍大小,知道满足要求为止 new_size = new_size > 0 ? new_size * 2 :1; avail = available_write() + (new_size -bufferSize_);//计算新的可利用空间:新-老+以前可利用 } void* new_buffer = std::realloc(buffer_,new_size);//重新按照新大小分配缓存空间 if (new_buffer == NULL) { throw std::bad_alloc(); } bufferSize_ = new_size;//重置缓存大小 ptrdiff_t offset = (uint8_t*)new_buffer -buffer_;//计算偏移量,下面依次更新缓存值 buffer_ += offset; rBase_ += offset; rBound_+= offset; wBase_ += offset; wBound_ = buffer_ + bufferSize_; }
内存缓存类还增加了一些额外的功能:例如按照字符串来读写,方便了对字符串的操作。