前面的三篇文章介绍了
Buffer
实现原理,简单的来说,
Buffer
就像一个
vector<uint8_t>
,自动管理内存,自动扩容,相比
vector<uint8_t>
它提供了更易用的写入,读取数据的接口。它可以作为一个基本
Buffer
,在它的基础上构建功能更多丰富的buffer类。
在webrtc中有一个CopyOnWriteBuffer
类,它就是基于Buffer
实现了一个写时复制功能的buffer。
功能
写时复制指在改变内容时,产生一个buffer的拷贝,在拷贝上更改内容,而不影响原buffer的内容。这是CopyOnWriteBuffer
的核心特性。它还具有如下几个特性:
- 它是个动态buffer,有size和容量的概念,容量大于size。容量变大后就不会减小(基于
Buffer
) - 该对象支持拷贝语义,移动语义(基于
Buffer
对象) - 拷贝语义,是让多个
CopyOnwrite
对象共享内存(Buffer
对象) - 当调用改动接口时,则会生成buffer的拷贝(产生新的
Buffer
对象)
作用
在流媒体,实时音视频这类应用中有个基本流程就是收媒体流,处理,再发送媒体流:
- 在收流时,对媒体数据都是读操作,那么使用
CopyOnWriteBuffer
就很适合,将媒体数据写入CopyOnWirteBuffer
对象,将以值语义传递,内部的Buffer
并不会拷贝,这样比使用裸指针更方便,因为CopyOnWriteBuffer
就提供了多种操作/读取数据的接口。 - 在处理时,比如媒体流转发应用,通常只需要在rtp包中更改某些字段,再转发出去。那么将rtp包放入
CopyOnWriteBuffer
中,在更改时,自动copy一份rtp包。这样比直接使用裸指针更方便。
实现
内部buffer是一个Buffer
类型shared_ptr
指针,表述它的语义就是可以共享_buffer
。
//是一个Buffer对象
std::shared_ptr<Buffer> _buffer;
复制语义
CopyOnWriteBuffer
支持复制语义,所以有复制构造函数,赋值操作符。
//复制构造函数
CopyOnWriteBuffer(const CopyOnWriteBuffer& buf);
//赋值操作符
CopyOnWriteBuffer& operator=(const CopyOnWriteBuffer& buf);
复制语义的作用就是让多个CopyOnWriteBuffer
对象共享_buffer
。
CopyOnWriteBuffer::CopyOnWriteBuffer(const CopyOnWriteBuffer& buf)
: _buffer(buf._buffer), _offset(buf._offset), _size(buf._size) {}
赋值操作符,也是如此:
CopyOnWriteBuffer& operator=(const CopyOnWriteBuffer& buf) {
assert(IsConsistent());
assert(buf.IsConsistent());
if (&buf != this) {
_buffer = buf._buffer;
_offset = buf._offset;
_size = buf._size;
}
return *this;
}
移动语义
CopyOnWriteBuffer
定义有移动构造函数和**移动赋值函数。 **通过std::move
将_buffer
的所有权交给另外一个CopyOnWriteBuffer
对象。
CopyOnWriteBuffer::CopyOnWriteBuffer(CopyOnWriteBuffer&& buf)
: _buffer(std::move(buf._buffer)), _offset(buf._offset), _size(buf._size) {
buf._offset = 0;
buf._size = 0;
assert(IsConsistent());
}
移动赋值操作符也是如此:
CopyOnWriteBuffer& operator=(CopyOnWriteBuffer&& buf) {
assert(IsConsistent());
assert(buf.IsConsistent());
_buffer = std::move(buf._buffer);
_offset = buf._offset;
_size = buf._size;
buf._offset = 0;
buf._size = 0;
return *this;
}
写时复制
CopyOnWriteBuffer
核心特性就是写时复制,多个CopyOnWriteBuffer
对象共享一个 Buffer,当某个对象改动Buffer时,就会对这个对象产生一个新的Buffer。涉及到修改Buffer的接口如下:
void AppendData(const uint8_t*data, size_t size)
uint8_t& operator[](size_t index)
void SetSize(size_t size)
void SetData(const uint8_t*data, size_t size)
前三个方法都会调用UnshareAndEnsureCapacity
方法,通过它来确定是否产生新的Buffer。
UnshareAndEnsureCapacity 方法
如函数名表示的意思,它有两个逻辑 :如果_buffer
被共享则产生一个新的_buffer
对象;如果没有被共享,则通过capacity来确认是否需要产生一个新的_buffer
对象。
void CopyOnWriteBuffer::UnshareAndEnsureCapacity(size_t new_capacity) {
if (_buffer.unique() && new_capacity <= capacity()) {
return;
}
_buffer.reset(new Buffer(_buffer->data() + _offset, _size, new_capacity));
_offset = 0;
assert(IsConsistent());
}
写时复制的一个例子
下面这个例子是使用AppendData
的例子
const uint8_t kTestData[] = {0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf};
//共享
CopyOnWriteBuffer buf23(kTestData,3,10);
CopyOnWriteBuffer buf24(buf23);
const uint8_t* d1 = buf23.cdata();
const uint8_t* d2 = buf24.cdata();
std::cout<<"before append data d1 == d2 "<<(d1 == d2)<<",buf23 size "<<buf23.size()<<",buf24 size "<<buf24.size()<<",buf23 c "<<buf23.capacity()<<",buf24 c "<<buf24.capacity()<<std::endl;
uint8_t moreData[] = {17,18,19};
//buf24 调用Append时,会产生一个新的buffer
buf24.AppendData(moreData,3);
std::cout<<"after append data,buf23 size "<<buf23.size()<<",buf24 size "<<buf24.size()<<",buf23 c "<<buf23.capacity()<<",buf24 c "<<buf24.capacity()<<std::endl;
const uint8_t* d3 = buf24.cdata();
std::cout<<"d1 == d3 "<<(d1 == d3)<<std::endl;
//打印buf23的内容:1,2,3
std::cout<<"print buf23 data:";
for (size_t i=0; i<buf23.size(); ++i) {
std::cout<<static_cast<int>(buf23[i])<<" ";
}
std::cout<<std::endl;
//打印buf24的内容:1,2,3,17,18,19
std::cout<<"print buf24 data:";
for (size_t i=0; i<buf24.size(); ++i) {
std::cout<<static_cast<int>(buf24[i])<<" ";
}
std::cout<<std::endl;
slice方法
用于对CopyOnWriteBuffer
对象进行分片,产生的一个从原对象offset
处,size为length
,新的CopyOnWriteBuffer
对象,它与原对象共享_buffer
。
CopyOnWriteBuffer Slice(size_t offset, size_t length) const {
CopyOnWriteBuffer slice(*this);
slice._offset += offset;
slice._size = length;
return slice;
}
示例代码
const uint8_t kTestData[] = {0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf};
CopyOnWriteBuffer ssbuf(kTestData,10,10);
//slice并不导致产生新buffer
CopyOnWriteBuffer sslice = ssbuf.Slice(0,3);
//sslice[0]改变了,将产生一个新的buffer
sslice[0] = 0xaa;
//sslice[0]的值为170
std::cout<<"sslice index 0 "<<static_cast<int>(*sslice.cdata())<<std::endl;
//ssbuf[0]的值为1
std::cout<<"ssbuf index 0 "<<static_cast<int>(*ssbuf.cdata())<<std::endl;
std::cout<<"sslic data == ssbuf data "<<(sslice.cdata() == ssbuf.cdata())<<std::endl;
总结
CopyOnWriteBuffer
的实现基于Buffer
,在功能上强于Buffer
。我们也可以根据业务的需求,基于Buffer
封装自己的Buffer类。