高效的两段式循环缓冲区──BipBuffer(3)

原文地址:http://www.codeproject.com/KB/IP/bipbuffer.aspx

 

继续上一篇的内容。。。

 

7 Characteristics of the Bip-Buffer BipBuffer的特点
The upshot of all of this is that on average, the buffer always has the maximal amount of free space available to be used, while not requiring any data copying or reallocation to free up space at the end of the buffer.
在绝大多数场合下,BipBuffer都可以充分利用全部缓冲区资源,而且即使到了缓冲区的末尾,也不需要任何数据拷贝或重新分配存储空间来实现循环利用缓冲区。
The biggest difference from an implementation standpoint between a regular circular buffer and the Bip Buffer is the fact that it only returns contiguous blocks. With a circular buffer, you need to worry about wrapping at the end of the buffer area - which is why for example if you look at Larry Antram's Fast Ring Buffer implementation, you'll see that you pass data into the buffer as a pointer and a length, the data from which is then copied byte by byte into the buffer to take into account the wrapping at the edges.

BipBuffer与常规的循环缓冲区相比较,最大的区别在于它可以返回连续的存储区。使用常规的循环缓冲区,需要考虑如何对缓冲区的末尾进行封装。这也是当看到Larry Antram's Fast Ring Buffer文章中对于循环缓冲区实现的时候,会发现需要传入数据缓冲区的指针以及数据的长度,然后数据会一个字节一个字节地拷贝到循环缓冲区中去(当然他的这种实现是需要考虑缓冲区的边界问题的。)

Another possibility which was brought up in the bulletin board (and the person who brought it up shall remain nameless, if just because they... erm... are nameless) was that of just splitting the calls across wraps. Well, this is one way of working around the wrapping problem, but it has the unfortunate side-effect that as your buffer fills, the amount of free space which you pass out to any calls always decreases to 1 byte at the minimum - even if you've got another 128kb of free space at the beginning of your buffer, at the end of it you're still going to have to deal with ever shrinking block sizes. The Bip-Buffer neatly sidesteps this issue by just leaving that space alone if the amount you request is larger than the remaining space at the end of the buffer. When writing networking code, this is very useful; you always want to try to receive as much data as possible, but you never can guarantee how much you're going to get. (For most optimal results, I'd recommend allocating a buffer which is some multiple of your MTU size).

Yes, you are going to lose some of what would have been free space at the end of the buffer. It's a small price to pay for playing nicely with the API.

Use of this buffer does require that one checks twice to see if the buffer has been emptied; as one has to deal with the possibility that there are two regions currently in use. However, the flexibility and performance gains outweigh this minor inconvenience.

class BipBuffer
{
private:
    BYTE* pBuffer;
    int ixa, sza, ixb, szb, buflen, ixResrv, szResrv;

public:
    BipBuffer();
The constructor initializes the internal variables for tracking regions, and memory pointers to null; it does not allocate any memory for the buffer, in case one needs to use the class in an environment where exception handling cannot be used.

~BipBuffer();
The destructor simply frees any memory which has been allocated to the buffer.

bool AllocateBuffer(int buffersize = 4096);
AllocateBuffer allocates a buffer from virtual memory. The size of the buffer is rounded up to the nearest full page size. The function returns true if successful, or false if the buffer cannot be allocated.

void FreeBuffer();
FreeBuffer frees any memory allocated to the buffer by the call to AllocateBuffer, and releases any regions allocated within the Bip-Buffer.

bool IsInitialized() const;
IsInitialized returns true if the buffer has had memory allocated to it (by calling AllocateBuffer), or false if there is no memory allocated to the buffer.

int GetBufferSize() const;
GetBufferSize returns the total size (in bytes) of the buffer. This may be greater than the value passed into AllocateBuffer, if that value was not a multiple of the system's page size.

void Clear();
Clear ... well... clears the buffer. It does not free any memory allocated to the buffer; it merely resets the region pointers back to null, making the full buffer usable for new data again.

 


BYTE* Reserve(int size, OUT int& reserved);
Now to the nitty-gritty. Allocating data in the Bip-Buffer is a two-phase operation. First an area is reserved by calling the Reserve function; then, that area is Committed by calling the Commit function. This allows one to, say, reserve memory for an IO call, and when that IO call fails, pretend it never happened. Or alternatively, in a call to an overlapped WSARecv() function, it allows one to advertise how much memory is available to the network stack to use for incoming data, and then adjust the amount of space used based on how much data was actually read in (which may be less than the requested amount).

To use Reserve, pass in the size of block requested. The function will return the size of the largest free block available which is less than or equal to size in length in the reserved parameter you passed in. It will also return a BYTE* pointer to the area of the buffer which you have reserved.

In the case where the buffer has no space available, Reserve will return a NULL pointer, and reserved will be set to zero.

Note: you cannot nest calls to Reserve and Commit; after calling Reserve you must call Commit before calling Reserve again.

void Commit(int size);
Here's the other half of the allocation. Commit takes a size parameter, which is the number of bytes (starting at the BYTE* you were passed back from Reserve) which you have actually used and want to keep in the buffer. If you pass in zero for this size, the reservation will be completely released, as if you had never reserved any space at all. Alternatively, in a debug build, if one passes in a value greater than the original reservation, an assert will fire. (In a release build, the original reservation size will be used, and no one will be any the wiser). Committing data to the buffer makes it available for routines which take data back out of the buffer.

The diagram above shows how Reserve and Commit work. When you call Reserve, it will return a pointer to the beginning of the gray area above (fig. 1). Say you then only use as much of that buffer as the blue section (fig 2). It'd be a shame to leave this area allocated and going to waste, so you can call Commit with only as much data as you used, which gives you fig. 3 - namely, the committed space extends to fill just the part you needed, leaving the rest free.

int GetReservationSize() const;
If at any time you need to find out if you have a pending reservation, or need to find out that reservation's size, you can call GetReservationSize to find the amount reserved. No reservation? You'll get a zero back.

BYTE* GetContiguousBlock(OUT int& size);
Well, after all this work to put stuff into the buffer, we'd better have a way of getting it out again.

First of all, what if you need to work out how much data (total) is available to be read from the buffer?


int GetCommittedSize() const;
One method is to call GetCommittedSize, which will return the total length of data in the buffer - that's the total size of both regions combined. I would not recommend relying on this number, because it's very easy to forget that you have two regions in the Bip-Buffer if you do. And that would be a bad thing (as several weeks of painful debugging experience has proved to me). As an alternative, you can call:


BYTE* GetContiguousBlock(OUT int& size);
... which will return a BYTE* pointer to the first (as in FIFO, not left-most) contiguous region of committed data in the buffer. The size parameter is also updated with the length of the block. If no data is available, the function returns NULL (and the size parameter is set to zero).

In order to fully empty the buffer, you may wish to loop around, calling GetContiguousBlock until it returns NULL. If you're feeling miserly, you can call it only twice. However, I'd recommend the former; it means you can forget that there's two regions, and just remember that there's more than one.


void DecommitBlock(int size);
So what do you do after you've consumed data from the buffer? Well, in keeping with the spirit of the aforementioned Reserve and Commit calls, you then call DecommitBlock to release data from it. Data is released in FIFO order, from the first contiguous block only - so if you're going to call DecommitBlock, you should do it pretty shortly after calling GetContiguousBlock. If you pass in a size of greater than the length of the contiguous block, then the entire block is released - but none of the other block (if present) is released at all. This is a deliberate design choice to remind you that there is more than one block and you should act accordingly. (If you really need to be able to discard data from blocks you've not read yet, it's not too difficult to copy the DecommitBlock function and modify it so that it operates on both blocks; just unwrap the if statement, and adjust the size parameter after the first clause. Implementation of this is left as the dreaded youknowwhat).

And that's the Bip-Buffer implementation done. A short example of how to use it is provided below.

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!
循环缓冲区(Circular Buffer)是一种环形的数据结构,常用于实现数据的异步传输、数据缓存、数据流控制等场景。在 C 语言中,可以通过使用指针和数组来实现循环缓冲区。 以下是一份高效的 C 语言循环缓冲区的实现代码: ```c typedef struct { char *buffer; size_t size; size_t read_pos; size_t write_pos; } CircularBuffer; CircularBuffer *cb_new(size_t size) { CircularBuffer *cb = (CircularBuffer *) malloc(sizeof(CircularBuffer)); cb->buffer = (char *) malloc(size); cb->size = size; cb->read_pos = 0; cb->write_pos = 0; return cb; } void cb_free(CircularBuffer *cb) { free(cb->buffer); free(cb); } size_t cb_size(CircularBuffer *cb) { return cb->write_pos - cb->read_pos; } size_t cb_capacity(CircularBuffer *cb) { return cb->size - cb_size(cb); } size_t cb_write(CircularBuffer *cb, const char *data, size_t len) { size_t capacity = cb_capacity(cb); if (len > capacity) { len = capacity; } size_t right = cb->size - cb->write_pos; if (len <= right) { memcpy(cb->buffer + cb->write_pos, data, len); cb->write_pos += len; } else { memcpy(cb->buffer + cb->write_pos, data, right); memcpy(cb->buffer, data + right, len - right); cb->write_pos = len - right; } return len; } size_t cb_read(CircularBuffer *cb, char *data, size_t len) { size_t size = cb_size(cb); if (len > size) { len = size; } size_t right = cb->size - cb->read_pos; if (len <= right) { memcpy(data, cb->buffer + cb->read_pos, len); cb->read_pos += len; } else { memcpy(data, cb->buffer + cb->read_pos, right); memcpy(data + right, cb->buffer, len - right); cb->read_pos = len - right; } return len; } ``` 在上述代码中,`CircularBuffer` 结构体表示循环缓冲区,包含 `buffer`(存储数据的数组指针)、`size`(缓冲区大小)、`read_pos`(读指针位置)和 `write_pos`(写指针位置)四个成员变量。通过 `cb_new()` 函数创建一个新的循环缓冲区,通过 `cb_free()` 函数释放一个已有的循环缓冲区。`cb_size()` 函数返回当前缓冲区中的数据大小,`cb_capacity()` 函数返回缓冲区中还能存储的数据大小。`cb_write()` 函数向缓冲区中写入数据,`cb_read()` 函数从缓冲区中读取数据。 在 `cb_write()` 和 `cb_read()` 函数中,通过判断写指针和读指针的位置关系,实现数据的循环存储和读取。如果写指针位置超出缓冲区末尾,则从缓冲区头部继续写入;如果读指针位置超出缓冲区末尾,则从缓冲区头部继续读取。这样可以实现数据的循环存储和读取,提高缓冲区的利用效率。 需要注意的是,在使用循环缓冲区时需要注意指针位置的变化,避免出现指针越界等错误。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值