RTC程序设计:实时音视频权威指南
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

1.2.2 字节管理类

本节设计一个跨平台Buffer类,用来管理二进制数据。

简单定义

最简单的Buffer类,通常结构如下:

img

其中,pBuf指向二进制数据的开始地址,nSize为二进制数据的字节数,如图1-6所示。在开源Web服务器Nginx中,字符串就是这样定义的[11]

然而上述定义存在很多问题,比如,如何控制内存分配与释放,如何让多个线程安全地共享,如何在函数与线程间高效地传递,减少复制次数。

Buffer作为传输通信中最常用的对象,我们需要为其实现更多的易用特性。

·极小栈消耗。

·内存自动回收。

·跨线程安全传递。

·写时复制(Copy-on-Write)。

·十六进制表示。

·Base64转换。

内存布局

本书提供的Buffer实现,采用如图1-7所示的内存布局。对比图1-6的简单定义,新的内存布局有如下特点。

(1)栈区只使用了一个指向堆区的指针,这使得占用栈空间的代价极小。

(2)栈指针指向堆区Buffer的起始地址,这使得可以随时访问Buffer的内容。

(3)在堆区Buffer前面,还有两个成员:nRefCount用来维护Buffer的引用计数;nSize用来表示Buffer的大小。

img

图1-6 简单Buffer类的内存布局

img

图1-7 改进后Buffer类的内存布局

为了方便读取Buffer之前的数据,我们定义了如下结构:

img

引用计数

引用计数可以有效地记录对象自身被线程持有的情况。

(1)当Buffer初始化时,其引用计数为1。

(2)当需要读取访问或复制构造Buffer时,无须复制Buffer的内容。我们仅需新增一个指向堆区的栈指针,并且让Buffer的引用计数加1。这里的栈指针,可能来自同一个线程的栈,也可能来自不同线程的栈。

(3)当不再需要访问Buffer时,将其引用计数减1。

(4)当引用计数减到0时,对象不再被任何线程持有,可安全地销毁[12]堆区Buffer。

(5)当需要修改Buffer时,根据引用计数的情况,决定是否需要写时复制。

由于涉及多个线程之间的共享操作,引用计数必须用原子变量来实现。

在Common文件夹下,有一个DAtomic.h文件,提供了对C++11<atomic>的类型封装。

写时复制

当引用计数为1时,我们可以正常地随意读写Buffer内的所有数据。

但当引用计数大于1时,如要修改Buffer的内容,我们就需要对其数据进行复制。这样各个线程仍可以像自己独占Buffer一样,访问各自的Buffer数据,使读写互不影响。

img

使用memcpy函数将自身的Buffer内容复制到新的Buffer中,由于该函数在不同平台下原型不同,我们使用DXP类对其进行了封装。

新分配的Buffer,其引用计数为1,原有的Buffer引用计数会减1。

更多Buffer的函数,可参看示例工程的DBuffer类。