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

1. 应用场景

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

本文将设计一个适合上述场景的环形缓冲组件,提供方便的数据缓存与读取接口,让编码专注于数据解析的逻辑,而不是将过多的精力消耗在缓冲区本身的处理上。本文讨论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]的读写操作,从而达到简化了环形缓冲区对于数据回绕的逻辑。

如下代码为读写环形缓冲区及计算缓冲区已使用空间大小的例程。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,就是往这里放东西,然后取定长数据)

linux网络编程--Circular Buffer(Ring Buffer) 环形缓冲区的设计与实现【转】的更多相关文章

  1. Linux 网络编程(IO模型)

    针对linux 操作系统的5类IO模型,阻塞式.非阻塞式.多路复用.信号驱动和异步IO进行整理,参考<linux网络编程>及相关网络资料. 阻塞模式 在socket编程(如下图)中调用如下 ...

  2. linux网络编程_1

    本文属于转载,稍有改动,以利于学习. (一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍客户端和服务端         网络程序和普通的程序有一个最大的区别是网络程序是由两个 ...

  3. Linux网络编程入门 (转载)

    (一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍客户端和服务端         网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客户 ...

  4. [转] - Linux网络编程 -- 网络知识介绍

    (一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍客户端和服务端         网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客户 ...

  5. 【转】Linux网络编程入门

    (一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍客户端和服务端         网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客户 ...

  6. 《转》Linux网络编程入门

    原地址:http://www.cnblogs.com/duzouzhe/archive/2009/06/19/1506699.html (一)Linux网络编程--网络知识介绍 Linux网络编程-- ...

  7. Linux 高性能服务器编程——Linux网络编程基础API

    问题聚焦:     这节介绍的不仅是网络编程的几个API     更重要的是,探讨了Linux网络编程基础API与内核中TCP/IP协议族之间的关系.     这节主要介绍三个方面的内容:套接字(so ...

  8. linux网络编程基础--(转自网络)

    转自 http://www.cnblogs.com/MyLove-Summer/p/5215287.html Linux下的网络编程指的是socket套接字编程,入门比较简单. 1. socket套接 ...

  9. Linux网络编程socket选项之SO_LINGER,SO_REUSEADDR

    from http://blog.csdn.net/feiyinzilgd/article/details/5894300 Linux网络编程中,socket的选项很多.其中几个比较重要的选项有:SO ...

随机推荐

  1. Beta吐槽

    目录 感想 管理团队 推进项目 pm吐槽 开发 经过我慎重的考虑,我jio得,Beta版本的吐槽还是不能少. 感想 管理团队 这学期十分庆幸成为我们这个这么优秀的团队的pm.身在pm的位置上经历这么一 ...

  2. ELK 性能(1) — Logstash 性能及其替代方案

    ELK 性能(1) - Logstash 性能及其替代方案 介绍 当谈及集中日志到 Elasticsearch 时,首先想到的日志传输(log shipper)就是 Logstash.开发者听说过它, ...

  3. Android TextView中图文混排设置行间距导致高度不一致问题解决

    最近项目中需要实现一个评论带表情的功能,刚开始一切顺利,非常easy,突然有一天发现文字跟表情混排的时候,TextView中图文高度不一致,excuse...什么鬼,之前明明测试过图文混排,不存在这个 ...

  4. SpringBoot 6.SpringBoot使用 Log4j2 实现日志输出

    一.添加 Log4j2 的依赖 <!-- 引入 log4j2 必须排除 logging --> <dependency> <groupId>org.springfr ...

  5. BeanFactory 简介以及它 和FactoryBean的区别(阿里面试)

    BeanFacotry是spring中比较原始的Factory.如XMLBeanFactory就是一种典型的BeanFactory.原始的BeanFactory无法支持spring的许多插件,如AOP ...

  6. delphi制作登陆窗体

    delphi登陆窗体的制作,就我知道的,可以有两种方法,一种是在工程文件中实现登陆窗体的动态调用,另一种就是在主窗体的OnCreate事件中动态创建登陆窗体,两种方法都需要将主窗体设置为Auto-cr ...

  7. 安装 oracle

    先下载3个东西:链接忘记了,大家自己找一下 1  ORA+11+G+R2+server+64bit+for+windows.iso  (oracle 安装文件) 2  PLSql 3  oracle6 ...

  8. 本地开启apache虚拟服务器

    一般来说,服务器是可以托管多个网站的,只要服务器开启虚拟主机的功能,原理是根据来源的host进行判断,不同的域名实现不同的文件访问,这样就可以实现一个服务器托管不同网站了,只要服务器的性能和带宽足够强 ...

  9. 【刷题】LOJ 2587 「APIO2018」铁人两项

    题目描述 比特镇的路网由 \(m\) 条双向道路连接的 \(n\) 个交叉路口组成. 最近,比特镇获得了一场铁人两项锦标赛的主办权.这场比赛共有两段赛程:选手先完成一段长跑赛程,然后骑自行车完成第二段 ...

  10. luogu3778/bzoj4898 商旅 (floyd+分数规划+spfa)

    首先floyd求出来每两点间的最短距离,然后再求出来从某点买再到某点卖的最大收益 问题就变成了找到一个和的比值最大的环 所以做分数规划,二分出来那个答案r,把边权变成w[i]-r*l[i],再做spf ...