zoukankan      html  css  js  c++  java
  • linux网络编程--Circular Buffer(Ring Buffer) 环形缓冲区的设计与实现【转】

    转自:https://blog.csdn.net/yusiguyuan/article/details/18368095

    1. 应用场景

          网络编程中有这样一种场景:需要应用程序代码一边从TCP/IP协议栈接收数据(reading data from socket),一边解析接收的数据。具体场景例如:用户点击Youtube或优酷网站上的视频内容,这时用户PC上的播放软件就是一边接收数据一边对数据进行解码并播放的。这样的场景的存在如下约束:
    1. 必须边接收数据,边对数据进行解析,不能等待到数据全部接收完整后才解析(用户等待的时间与体验成反比)。
    2. 数据为流式数据(如TCP承载),需对接收到的数据进行定界分析,将数据转化为可被应用程序解析的结构化数据。
    3. 数据的解析需要兼顾性能和内存空间的利用效率(如果减少内存拷贝,分配适当大小的缓存空间)。image

         本文将设计一个适合上述场景的环形缓冲组件,提供方便的数据缓存与读取接口,让编码专注于数据解析的逻辑,而不是将过多的精力消耗在缓冲区本身的处理上。本文讨论POSIX的一种优化的环形缓冲实现方式,并提出了进一步优化:
    1. 高效的数据写入与读取接口,如应用程序可能对某段数据不感兴趣,则可将其直接忽略掉。
    2. 封装了常见的整形数据读取接口,解析程序可以直接读数1~4字节的整形数据。

    1. #ifndef _CIRCULAR_BUFFER_H
    2. #define _CIRCULAR_BUFFER_H
    3.  
    4. typedef struct CircularBuffer {
    5. void *ptr;
    6.  
    7. /* 必须为整数倍内存页面大小*/
    8. unsigned long count;
    9. unsigned long read_offset;
    10. unsigned long write_offset;
    11. } CircularBuffer;
    12.  
    13. /* 创建环形缓冲区 */
    14. CircularBuffer *cbCreate(unsigned long order);
    15. /* 销毁环形缓冲区 */
    16. void cbFree(CircularBuffer *cb);
    17. /* 重置缓冲区,使之可用于新的业务数据缓存 */
    18. void cbClear(CircularBuffer *cb);
    19.  
    20. int cbIsEmpty(CircularBuffer *cb);
    21. unsigned long cbUsedSpaceSize(CircularBuffer *cb);
    22. unsigned long cbFreeSpaceSize(CircularBuffer *cb);
    23.  
    24. /* 向环形缓冲写入len 字节数据 */
    25. unsigned long cbPushBuffer(CircularBuffer *cb, void *buffer, unsigned long len);
    26. /* 从环形缓冲读取len字节存放到buffer中,
    27. buffer可以为NULL,忽略len字节的数据*/
    28. void *cbReadBuffer(CircularBuffer *cb, void *buffer, unsigned long len);
    29.  
    30. /* 从环形缓冲区读取1个字节 */
    31. unsigned char cbReadUINT8(CircularBuffer *cb);
    32. /* 从环形缓冲区读取1个短整形数 */
    33. unsigned short cbReadUINT16(CircularBuffer *cb);
    34. short cbReadSINT16(CircularBuffer *cb);
    35. unsigned int cbReadUINT24(CircularBuffer *cb);
    36. int cbReadSINT24(CircularBuffer *cb);
    37. unsigned int cbReadUINT32(CircularBuffer *cb);
    38. int cbReadSINT32(CircularBuffer *cb);
    39.  
    40. #endif
    41.  

    cbCreate接口创建并初始化一个环形缓冲区,实现如下:

    1. CircularBuffer *cbCreate(unsigned long order)
    2. {
    3.  
    4. int fd = 0, status = 0;
    5. void *address = NULL;
    6. char path[] = "/dev/shm/circular_buffer_XXXXXX";
    7. CircularBuffer *cb = (CircularBuffer *)malloc(sizeof(CircularBuffer));
    8.  
    9. if (NULL == cb) {
    10. return NULL;
    11. }
    12.  
    13. order = (order <= 12 ? 12 : order);
    14. cb->count = 1UL << order;
    15. cb->read_offset = 0;
    16. cb->write_offset = 0;
    17.  
    18. /* 分配2倍指定的缓冲空间 */
    19. cb->ptr = mmap(NULL, cb->count << 1, PROT_NONE, MAP_ANONYMOUS |MAP_PRIVATE, -1, 0);
    20. if (MAP_FAILED == cb->ptr) {
    21. abort(); |
    22. }
    23.  
    24. /* 根据path模块创建一个唯一的临时文件 */
    25. fd = mkstemp(path);
    26. if (0 > fd) {
    27. abort();
    28. }
    29.  
    30. /* 删除文件访问的目录入口,进程仍可使用该文件 */
    31. status = unlink(path);
    32. if (0 != status) {
    33. abort();
    34. }
    35.  
    36. /* 将文件大小精确指定为count字节 */
    37. status = ftruncate(fd, cb->count);
    38. if (0 != status) {
    39. abort();
    40. }
    41.  
    42. /* 将[ cb->ptr, cb->ptr + cb->count)地址空间映射到临时文件*/
    43. address = mmap(cb->ptr, cb->count, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED, fd, 0);
    44. if (address != cb->ptr) {
    45. abort();
    46. }
    47.  
    48. /* 将[ cb->ptr + cb->count, cb->ptr + 2 * cb->count)地址空间映射到临时文件*/
    49. address = mmap(cb->ptr + cb->count, cb->count, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED, fd, 0);
    50. if (address != cb->ptr + cb->count) {
    51. abort();
    52. }
    53.  
    54. status = close(fd);
    55. if (0 != status) {
    56. abort();
    57. }
    58.  
    59. return cb;
    60. }
    61.  

    该实现采用了一种精妙的处理方式,用2倍的缓存空间简化数据的读写操作。
        第1个mmap采用私有匿名的方式分配了一块为指定缓冲区大小2倍的内存空间;第2个mmap将mkstemp创建的临时文件映射到[ptr, ptr + count)地址,第3个mmap将mkstemp创建的临时文件映射到[ptr + count, ptr + 2 * count)地址,这样对ptr[i]的读写操作将等同于对ptr[i + count]的读写操作,从而达到简化了环形缓冲区对于数据回绕的逻辑。

    image

         如下代码为读写环形缓冲区及计算缓冲区已使用空间大小的例程。cbUsedSpaceSize函数可用于cbIsEmpty及cbFreeSpaceSize函数的实现。cbReadBuffer函数则可用于实现cbReadUINT8、cbReadUINT16、cbReadSINT16、cbReadUINT24、cbReadSINT24、cbReadUINT32及cbReadSINT32。cbReadBuffer函数的buffer参数若传人为空,则忽略len指定长度字节的数据。

    1. unsigned long cbPushBuffer(CircularBuffer *cb, void *buffer, unsigned long len)
    2.  
    3. {
    4.  
    5. unsigned long write_offset = cb->write_offset;
    6.  
    7.  
    8.  
    9. cb->write_offset += len;
    10.  
    11. memmove(cb->ptr + write_offset, buffer, len);
    12.  
    13.  
    14.  
    15. return len;
    16.  
    17. }
    18.  
    19. void *cbReadBuffer(CircularBuffer *cb, void *buffer, unsigned long len)
    20.  
    21. {
    22.  
    23. void *address = NULL;
    24.  
    25.  
    26.  
    27. /* 忽略len字节数据 */
    28.  
    29. if (NULL != buffer) {
    30.  
    31. address = memmove(buffer, cb->ptr + cb->read_offset, len);
    32.  
    33. }
    34.  
    35. cb->read_offset += len;
    36.  
    37. if (cb->read_offset > cb->count) {
    38.  
    39. cb->read_offset -= cb->count;
    40.  
    41. cb->write_offset -= cb->count;
    42.  
    43. }
    44.  
    45.  
    46.  
    47. return address;
    48.  
    49. }
    50.  
    51.  
    52.  
    53. unsigned long cbUsedSpaceSize(CircularBuffer *cb)
    54.  
    55. {
    56.  
    57. return cb->write_offset - cb->read_offset;
    58.  
    59. }
    60.  

    3. 分析与讨论

    1. 环形缓冲区特别适合于FIFO类型数据的处理,利用它可以不拷贝内存完成缓冲上数据的解析,提高数据解析效率。
    2. 若数据读取函数采用单字节读、取模数计算偏移的方式,则可能带来性能上的损耗,该问题可以通过增加判断或以做位运算等机制来解决,但同时也增加了实现逻辑的复杂度。
    3. 其不足之处在于需要预先估计数据缓冲的大小,并分配比预估大小大一个数量级的缓存空间。一种可能的解决办法是增加检测机制,若发现缓冲太小,则动态调大缓冲的大小,但这同时又可能导致频繁的调整内存大小,带来性能的下降。

    (总结:根绝这样写的ringbuf 确实有点麻烦,暂时还没有体会其中的要义,自己也尝试着写一个简单的ringbuf,就是往这里放东西,然后取定长数据)

  • 相关阅读:
    STM32下载程序后不能运行
    (转载)时序约束的基本方法
    PLL失锁的问题
    算法与硬件构架的关系
    构建低成本、高度可配置的桥接解决方案:在嵌入式设计中采用基于D-PHY的MIPI标准外设
    SOLDERMASK_TOP不显示
    Allegro brd文件更新封装及焊盘方法
    Cadence Allegro光绘文件生成技巧
    shape合并
    allegro 如何 敷铜(铺铜),并去掉敷铜岛
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/9268619.html
Copyright © 2011-2022 走看看