循环buffer,即环形缓冲区,设有固定的大小,被定义成一个环形,新数据会覆盖旧的数据,减少内存拷贝,提高程序的性能。适用于通信上接收流式数据,然后进行分片、组包。
逻辑示意图:
图1
小C自己实现的环形Buffer,“一写一读”,支持多线程且无锁设计,支持随机长度读写。测试性能:500M 数据用时=1382110us(1.38秒) 速率=2.83Gbps。
程序设计:
虽然该程序被称为环形Buffer,实际上在内存中就是一个大的数组,通过对指向数组的位置进行管理,设计成一个环形的队列。
注:下文所述的指针不是常规C、C++上的(char* p, int* pi)这种指针,而是相对于一个数组起始位置的偏移。例如char buf[32], 偏移10个字节,用一个int pos变量去指向第10字节位置,这个pos在文中被称为指针。
//例如:
char buffer[32];
int pos=10;
buffer[10] == *(buffer+pos)//这两个是完全相等的//这个pos在文中被称为指针,即偏移,此处指针值为10
- 写入数据设计示意图
如图2所示,当我们要写入数据时,我们会关注写指针的位置,还有能写入的空间,图中的白色与黄色区域均是可以写入数据的,黄色区域已经读取了数据,即可被覆盖。
- 写指针在前(相对于读指针来看)
图2 写指针在前
2.写指针在后(相对于读指针位置来看)
如图3所示,此时写指针已经写的大小是10+3个区域大小,即已经走了一圈且再次走到3这个位置了。现在可以写入的数据大小即是黄色的区域。
图3 写指针在后
综上两种情况,要计算每次可写入数据的长度方式如下:
int wpos;//写指针
int rpos;//读指针
int buf_size; //整个buffer的大小
int available_len = ((rpos - wpos) +buf_size) % buf_size;
//计算可写入数据长度
//使用读指针位置减去写指针的位置加上总长,就保证该值是一个正数
//然后再对这个数进行取余,保证整个大小是在buf_size的范围内
//这样即算出可使用的空间大小
/******************************************************************/
//写函数实现
/******************************************************************/
int Ringbuffer::write(uint8_t* buffer, int len)
{
register int available_len = ((rpos-wpos)+buffer_size)&buffer_size_mask;
if(available_len <= len){
if(!is_init){
is_init = true;
goto run;
}
return -1;
}
run:
register int left_len = MIN(len, buffer_size-wpos);
::memcpy(m_buffer+wpos, buffer, left_len);
::memcpy(m_buffer,buffer+left_len, len-left_len);
smp_mb();
wpos += len;
wpos &= buffer_size_mask;
return len;
}
//wpos &= buffer_size_mask; ==> wpos %= buffer_size_mask;
//因为buffer长度传入时候处理为2的次幂,因此进行区域的使用可以使用&进行操作
//例如:pos % 8 ==> pos&(8-1) 即 pos&7;
//buffer_size_mask = buffer_size -1;
2.读入数据设计示意图
i.读指针在后(相对于写指针位置)
图4是读指针在后,此时可读的数据长度是2-6,即图中绿色区域。
图4 读指针在写指针之后
ii.读指针在后(相对于写指针位置)
图5是读指针在前,此时可读区域7-10、0-3,即图中绿色区域。
图5 读指针在写指针之前
(大家有没有发现图4、5 与 图2、3一样,对!就是一样的,只是现在我们要站在读指针的位置去看)
综上两种情况,要计算每次可写入数据的长度方式如下:
int wpos;//写指针
int rpos;//读指针
int buf_size; //整个buffer的大小
int available_len = ((wpos - rpos) +buf_size) % buf_size;
//计算可写入数据长度
//使用写指针位置减去读指针的位置加上总长,就保证该值是一个正数
//然后再对这个数进行取余,保证整个大小是在buf_size的范围内
//这样即算出可使用的空间大小
注:与上述计算可写长度类似,但区别是此处(写-读)
/*****************************************************************/
//读函数实现
/*****************************************************************/
int Ringbuffer::read(uint8_t* buffer, int len)
{
register int available_len = ((wpos-rpos)+buffer_size)&buffer_size_mask;
if(available_len <= len) return -1;
register int left_len=MIN(len, buffer_size-rpos);
::memcpy(buffer, m_buffer+rpos, left_len);
::memcpy(buffer+left_len, m_buffer, len-left_len);
smp_mb();
rpos += len;
rpos &= buffer_size_mask;
return len;
}
测试思路:
- 代码测试:
使用随机生成一个500M的源数据文件,开启两个线程,一个线程从数据文件中随机长度读取数据,然后往循环buffer里面写数据,一个线程从循环buffer里面随机长度读取出数据,然后写入文件,最后使用Beyond Compare软件对两个文件进行比较,查看数据是否相同。
测试结果:两个500M的文件数据一样,
- 测试速率:
代码目录简介:
testfile文件夹:生成随机的源数据文件src.dat,已经测试生成的out.dat数据文件
main.cpp文件:主要程序入口
主要功能实现:ringbuffer.cpp ringbuffer.h
使用:
请先进入testfile目录使用gcc generate_bin_file.cpp -o test编译,然后执行./test,生成src.dat源数据文件。然后回到上级目录,执行make,生成mainApp, 执行mainApp便可以测试程序。运行环境:Linux系统的任意发行版本都可以。