C语言实现ring buffer -- AWTK环形缓冲器及其应用


前言

         最近在学习李老师的[AWTK](https://hub.fastgit.org/zlgopen/awtk)源码,看到环形缓冲器这个类感觉比较有意思,这篇博文就来分享一下AWTK中环形缓冲器的实现以及它在AWTK中的应用。

一、环形缓冲器是什么

        环形缓冲器(ringr buffer),也称作圆形队列(circular queue),循环缓冲区(cyclic buffer),圆形缓冲区(circula buffer),是一种用于表示一个固定尺寸、头尾相连的缓冲区的数据结构,适合缓存数据流。-- 来自百度百科

二、AWTK环形缓冲器的设计

        AWTK使用C语言实现,采用面向对象的设计方法设计每一个子模块。它使用一个结构体封装对象的属性,通过对外提供接口的方式操作对象,环形缓冲器也是遵循这种方法设计的。

1.属性定义

         环形缓冲器一般包括四个指针:内存起始位置、内存结束位置、当前读取位置(读指针)和当前写位置(写指针);内存起始位置和结束位置由对象内部管理即可,不必告诉用户,所以没必要把这些属性对用户开放;而读指针和写指针是用户关心的属性,需要提供给用户。AWTK中,把读指针r和写指针w用索引来替代,方便了数据的读写操作。具体的属性定义如下:
/**
 * @class ring_buffer_t
 * 循环缓存区。
 */
typedef struct _ring_buffer_t {
  /**
   * @property {uint32_t} r
   * @annotation ["readable"]
   * 读取位置。
   */
  uint32_t r;
  /**
   * @property {uint32_t} w
   * @annotation ["readable"]
   * 写入位置。
   */
  uint32_t w;
  /**
   * @property {bool_t} full
   * @annotation ["readable"]
   * 是否满。
   */
  bool_t full;
  /**
   * @property {uint32_t} capacity
   * @annotation ["readable"]
   * 当前容量。
   */
  uint32_t capacity;
  /**
   * @property {uint32_t} max_capacity
   * @annotation ["readable"]
   * 最大容量。
   */
  uint32_t max_capacity;
  /**
   * @property {uint8_t*} data
   * @annotation ["readable"]
   * 数据。
   */
  uint8_t* data;

} ring_buffer_t;

2.接口定义

         环形缓冲器的操作接口比较丰富,包括创建函数和销毁函数、判空和判满、容量、有效数据长度及剩余空间、读写操作接口、光标设置接口等接口函数。完整接口源码及实现代码可以通过这个网址访问(https://github.com/zlgopen/awtk/blob/master/src/tkc/ring_buffer.h、及ring_buffer.c)。
//创建ring_buffer对象。
ring_buffer_t* ring_buffer_create(uint32_t init_capacity, uint32_t max_capacity);
//检查ring_buffer是否满。
bool_t ring_buffer_is_full(ring_buffer_t* ring_buffer);
//检查ring_buffer是否空。
bool_t ring_buffer_is_empty(ring_buffer_t* ring_buffer);
//获取数据长度。
uint32_t ring_buffer_size(ring_buffer_t* ring_buffer);
//获取空闲空间的长度。
uint32_t ring_buffer_free_size(ring_buffer_t* ring_buffer);
//获取容量。
uint32_t ring_buffer_capacity(ring_buffer_t* ring_buffer);
//读取数据。
uint32_t ring_buffer_read(ring_buffer_t* ring_buffer, void* buff, uint32_t size);
//读取数据(不修改读取位置)。
uint32_t ring_buffer_peek(ring_buffer_t* ring_buffer, void* buff, uint32_t size);
//写入数据。
uint32_t ring_buffer_write(ring_buffer_t* ring_buffer, const void* buff, uint32_t size);
//读取指定长度数据,要么成功要么失败。
ret_t ring_buffer_read_len(ring_buffer_t* ring_buffer, void* buff, uint32_t size);
//跳过指定长度数据,要么成功要么失败。
ret_t ring_buffer_skip(ring_buffer_t* ring_buffer, uint32_t size);
//写入指定长度数据,要么成功要么失败。
ret_t ring_buffer_write_len(ring_buffer_t* ring_buffer, const void* buff, uint32_t size);
//重置ring_buffer为空。
ret_t ring_buffer_reset(ring_buffer_t* ring_buffer);
//设置读取光标的位置。
ret_t ring_buffer_set_read_cursor(ring_buffer_t* ring_buffer, uint32_t r);
//设置读取光标的位置(delta)。
ret_t ring_buffer_set_read_cursor_delta(ring_buffer_t* ring_buffer, uint32_t r_delta);
//设置写入光标的位置。
ret_t ring_buffer_set_write_cursor(ring_buffer_t* ring_buffer, uint32_t w);
//设置写入光标的位置(delta)。
ret_t ring_buffer_set_write_cursor_delta(ring_buffer_t* ring_buffer, uint32_t w_delta);
//扩展buffer。
ret_t ring_buffer_ensure_write_space(ring_buffer_t* ring_buffer, uint32_t size);
//销毁ring_buffer。
ret_t ring_buffer_destroy(ring_buffer_t* ring_buffer);

3.接口使用说明

1、首先,通过ring_buffer_create创建环形缓冲器对象,通过指定容量来初始化缓冲器;
2、在写入数据前,先调用ring_buffer_ensure_write_space,确保有足够的空间存储数据;该接口 先检查是否有剩余空间,如果没有,则考虑扩展容量,如果容量已经达到最大值,无法扩展容量,那么就不能往缓冲器写入数据,所以,需要判断返回值是否是RET_OK,如果不是,说明没有可用空间,不能写入数据到缓冲器。
3、通过ring_buffer_write_len写入指定长度数据;
4、在读数据之前,先调用ring_buffer_is_empty判断是否有数据;
5、通过ring_buffer_peek读取指定长度数据,这个接口并不把读指针做移动,只获取数据;这样的好处是,方便对读取的数据做预处理,而且读取后,这些数据在缓冲器内仍是有效数据;
6、通过ring_buffer_read接口读指定长度数据,读取后,读指针会移动到剩余有效数据的开始位置;
7、通过ring_buffer_reset接口清空缓冲器,重置缓冲器对象到初始状态;
8、当不再需要缓冲器,使用ring_buffer_destroy接口销毁缓冲器。

三、AWTK环形缓冲器的应用

         在AWTK中,ostream_retry这个类用到了环形缓冲器ring_buffer_t,(源码位置:https://hub.fastgit.org/zlgopen/awtk/blob/master/src/streams/misc/ostream_retry.c)。环形缓冲器常用来缓存数据流,ostream_retry这个可重试输出流类使用环形缓冲器来缓存数据恰到好处,在ostream_retry中,如果写数据失败或者没写完数据,会把没写的数据缓存到ring_buffer_t对象rb当中,下次再写数据的时候,会尝试把缓冲器里的数据读出来,再尝试写操作。
1、首先,在类定义中定义一个ring_buffer_t成员rb,源码如下:
struct _tk_ostream_retry_t {
  tk_ostream_t ostream;

  /**
   * @property {uint32_t} pending_bytes
   * @annotation ["readable"]
   * 待重写的数据大小。
   */
  uint32_t pending_bytes;
  /**
   * @property {uint32_t} pending_packets
   * @annotation ["readable"]
   * 待重写的数据包的个数。
   */
  uint32_t pending_packets;
  /**
   * @property {uint32_t} discard_bytes
   * @annotation ["readable"]
   * 总共丢弃数据的字节数。
   */
  uint32_t discard_bytes;
  /**
   * @property {uint32_t} discard_packets
   * @annotation ["readable"]
   * 总共丢弃数据包的个数。
   */
  uint32_t discard_packets;
  /**
   * @property {uint32_t} max_retry_times
   * @annotation ["readable"]
   * 尝试次数超过指定的值时丢弃该数据包。
   */
  uint32_t max_retry_times;
  /**
   * @property {uint32_t} timeout
   * @annotation ["readable"]
   * 写超时时间(ms)。
   */
  uint32_t timeout;
  /**
   * @property {data_discard_policy_t} discard_policy
   * @annotation ["readable"]
   * 缓存不够时,丢弃数据包的策略。
   */
  data_discard_policy_t discard_policy;

  /*private*/
  wbuffer_t wb;
  ring_buffer_t* rb;
  uint32_t retried_times;
  tk_ostream_t* real_ostream;
};
2、通过ring_buffer_create接口创建环形缓冲器对象
tk_ostream_t* tk_ostream_retry_create(tk_ostream_t* real_ostream) {
  object_t* obj = NULL;
  tk_ostream_retry_t* ostream_retry = NULL;
  return_value_if_fail(real_ostream != NULL, NULL);

  obj = object_create(&s_tk_ostream_retry_vtable);
  ostream_retry = TK_OSTREAM_RETRY(obj);
  return_value_if_fail(ostream_retry != NULL, NULL);

  OBJECT_REF(real_ostream);
  ostream_retry->timeout = 3000;
  ostream_retry->max_retry_times = 10;
  ostream_retry->real_ostream = real_ostream;
  ostream_retry->rb = ring_buffer_create(1024, 4096);
  wbuffer_init_extendable(&(ostream_retry->wb));
  TK_OSTREAM(obj)->write = tk_ostream_retry_write;
  TK_OSTREAM(obj)->flush = tk_ostream_retry_flush;

  return TK_OSTREAM(obj);
}
3、通过ring_buffer_write_len接口把数据写入环形缓冲器;在写数据的时候,如果环形缓冲器内有数据,需要把新数据也存入缓冲器;如果写失败或者部分写入,需要把未写的数据存进缓冲器。
static ret_t tk_ostream_retry_write_to_buffer(tk_ostream_retry_t* ostream_retry,
                                              const uint8_t* buff, uint32_t max_size) {
  ring_buffer_t* rb = ostream_retry->rb;
  uint32_t size = max_size + sizeof(max_size);
  data_discard_policy_t policy = ostream_retry->discard_policy;

  if (ring_buffer_ensure_write_space(rb, size) != RET_OK) {
    if (policy == DATA_DISCARD_NEW) {
      ostream_retry->discard_packets++;
      ostream_retry->discard_bytes += max_size;
      return RET_OK;
    } else {
      tk_ostream_retry_clear_buffer(TK_OSTREAM(ostream_retry));
    }
  }

  return_value_if_fail(ring_buffer_write_len(rb, &max_size, sizeof(max_size)) == RET_OK, RET_FAIL);
  return_value_if_fail(ring_buffer_write_len(rb, buff, max_size) == RET_OK, RET_FAIL);
  ostream_retry->pending_bytes += max_size;
  ostream_retry->pending_packets++;

  return RET_OK;
}
4、通过ring_buffer_peek接口读取缓冲器的数据;在flush的时候,如果缓冲器内有缓存数据,需要读出缓冲器数据,然后执行写操作;
static ret_t tk_ostream_retry_flush(tk_ostream_t* stream) {
  uint32_t size = 0;
  tk_ostream_retry_t* ostream_retry = TK_OSTREAM_RETRY(stream);
  uint32_t timeout = ostream_retry->timeout;
  ring_buffer_t* rb = ostream_retry->rb;

  if (!ring_buffer_is_empty(rb)) {
    wbuffer_t* wb = &(ostream_retry->wb);
    tk_ostream_t* ostream = ostream_retry->real_ostream;

    do {
      uint32_t packet_size = 0;
      ostream_retry->retried_times++;
      if (ring_buffer_peek(rb, &size, sizeof(size)) != sizeof(size)) {
        break;
      }
      wb->cursor = 0;
      packet_size = size + sizeof(size);

      return_value_if_fail(wbuffer_extend_capacity(wb, packet_size) == RET_OK, RET_OOM);
      if (ring_buffer_peek(rb, wb->data, packet_size) != packet_size) {
        break;
      }

      if (tk_ostream_write_len(ostream, wb->data + sizeof(size), size, timeout) == size) {
        ostream_retry->retried_times = 0;
        ostream_retry->pending_packets--;
        ostream_retry->pending_bytes -= size;
        ring_buffer_skip(rb, packet_size);
      } else {
        if (ostream_retry->retried_times == ostream_retry->max_retry_times) {
          tk_ostream_retry_clear_buffer(stream);
        }
        break;
      }
    } while (TRUE);
  }

  return RET_OK;
}
5、通过ring_buffer_reset接口重置缓冲器的数据。
ret_t tk_ostream_retry_clear_buffer(tk_ostream_t* ostream) {
  tk_ostream_retry_t* ostream_retry = TK_OSTREAM_RETRY(ostream);
  ring_buffer_t* rb = ostream_retry->rb;
  return_value_if_fail(ostream_retry != NULL, RET_BAD_PARAMS);

  ostream_retry->retried_times = 0;
  ostream_retry->discard_bytes += ostream_retry->pending_bytes;
  ostream_retry->discard_packets += ostream_retry->pending_packets;

  ring_buffer_reset(rb);
  ostream_retry->pending_bytes = 0;
  ostream_retry->pending_packets = 0;

  return RET_OK;
}

6、通过ring_buffer_destroy接口销毁缓冲器。

static ret_t tk_ostream_retry_on_destroy(object_t* obj) {
  tk_ostream_retry_t* ostream_retry = TK_OSTREAM_RETRY(obj);

  wbuffer_deinit(&(ostream_retry->wb));
  ring_buffer_destroy(ostream_retry->rb);
  OBJECT_UNREF(ostream_retry->real_ostream);

  return RET_OK;
}

总结

1、环形缓冲器相对于常用的数组,优点在于,环形缓冲器读取出一个数据后,剩余数据不需要移动,只需要把读位置的索引向后移即可,效率非常高,而数组如果取出前面的数据,后面的数据需要往前移动,效率比较低;
2、环形缓冲器拥有数组随机访问的性能,通过读位置索引、写位置索引,方便高效的执行读写操作;
3、环形缓冲器需要指定容量,如果初始容量比较小,当写入数据时,可用空间不足,需要重新扩展空间并拷贝旧数据,效率不高。
4、由于环形缓冲器在逻辑上是环形的,没有起始、没有结束,只有读位置、写位置、大小和容量,特别适合存储固定最大数目个数的数据,比较典型的一个例子:界面上需要显示固定条数的数据,当累计条数超过最大数目时,覆盖旧数据,这是一个非常典型的、逻辑上是环形结构的例子。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值