-
-
Save stoensin/534b76fee589d80010012aab78a12b14 to your computer and use it in GitHub Desktop.
循环缓冲区对于数据写入和读出以不同速率发生的情况也是非常有用的结构:最新数据始终可用。如果读取数据的速度跟不上写入数据的速度,旧的数据将被新写入的数据覆盖。通过使用循环缓冲区,能够保证我们始终使用最新的数据。 环形缓冲区在处理异步IO时非常实用。它们可以在一端接收随机长度和区间的数据,在另一端以相同长度和区间提供密致的数据块。它们是Queue数据结构的变体,但是它针对于字节块而不是一系列指针。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdint.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
//must be a power of two so when B fit this: A % B == A &(B - 1) | |
#define RING_SIZE (32U) | |
#define RING_MASK (RING_SIZE - 1) | |
//将普通索引转换成ring_buf的索引 | |
#define RING_BUF_IDX(idx, tail) ((idx + tail) & RING_MASK) | |
typedef uint32_t rb_size_t; | |
typedef uint8_t rb_type_t; | |
// 定义结构体 | |
typedef struct { | |
rb_size_t head; | |
rb_size_t tail; | |
rb_type_t buffer[0]; | |
} ring_buffer; | |
//创建ring_buffer | |
ring_buffer *ring_buf_create(rb_size_t len) { | |
uint32_t size = sizeof(ring_buffer) + sizeof(rb_type_t) * len; | |
ring_buffer *rb = (ring_buffer *)malloc(size); | |
memset(rb, 0, size); | |
return rb; | |
} | |
/* | |
* 未翻转情况下:head + 1 == tail 时,ring_buf为满,牺牲了一个rb_type_t类型的空间 | |
* 但是让ring_buf的实现代码更加优雅,当然读者也可以使用多条件判断的方法, | |
* 但是那样会增大编码阅读和复杂度,损失了代码的优雅 | |
* 在目前的ring_buf实现中,大都是选择使用浪费一个空间,换取代码的整洁和优雅 | |
*/ | |
static inline uint8_t ring_buf_is_full(ring_buffer *rb) { | |
return (((rb->head + 1) & RING_MASK) == rb->tail); | |
} | |
/* | |
* 几乎所有的ring_buf实现都使用head == tail表示空 | |
*/ | |
static inline uint8_t ring_buf_is_empty(ring_buffer *rb) { | |
return (rb->tail == rb->head); | |
} | |
/* | |
* 获取ring_buf的有效长度,即计数cnt | |
* NOTE:必须是head - tail | |
*/ | |
static inline rb_size_t get_ring_buf_cnt(ring_buffer *rb) { | |
return ((rb->head - rb->tail + RING_SAZIE) & RING_MASK); | |
} | |
/* 写入ring_buf,写入一次,head 需要自增1 */ | |
int ring_buf_push(ring_buffer *ring, rb_type_t data) { | |
if (ring_buf_is_full(ring)) { | |
return -1; | |
} | |
ring->buffer[ring->head] = data; // Load data and then move | |
ring->head = (ring->head + 1) & RING_MASK; // head to next data offset. | |
return 0; | |
} | |
/* 读取ring_buf,读取一次,tail 需要自增1 */ | |
int ring_buf_pop(ring_buffer *ring, rb_type_t *data) { | |
if (ring_buf_is_empty(ring)) { | |
return -1; | |
} | |
*data = ring->buffer[ring->tail]; // Read data and then move | |
ring->tail = (ring->tail + 1) & RING_MASK; // tail to next offset. | |
return 0; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#define LEN (50U) | |
int main(void) { | |
ring_buf_t *ring = ring_buf_create(RING_SAZIE); | |
uint8_t r_data[LEN] = {0}; //出 pop read | |
uint8_t w_data[LEN] = {0}; //入 push write | |
//当ring为空时,pop无效 | |
printf("TEST:when ring is empty,pop invalid \n"); | |
for (int i = 0; i < LEN; i++) { | |
if (ring_buf_pop(ring, &r_data[i]) != 0) { | |
printf("ring_buf is empty. %d\n", i); | |
break; | |
} | |
} | |
//TEST:写入10个数据进入ring | |
printf("TEST:write 10 data in ring\n"); | |
for (int i = 0; i < 10; i++) { | |
w_data[i] = i; | |
if (ring_buf_push(ring, w_data[i]) != 0) { | |
printf("ring_buf is fullly. %d\n", i); | |
break; | |
} | |
} | |
int cnt = get_ring_buf_cnt(ring); //获取ring_buf的有效长度 | |
int idx; | |
//打印刚才写入的有效数据,ring->buffer: | |
printf("ring->buffer: "); | |
for (int i = 0; i < cnt; i++) { | |
idx = RING_BUF_IDX(i, ring->tail); //将普通索引转换成ring_buf索引 | |
printf("%d ", ring->buffer[idx]); | |
} | |
printf("\n"); | |
//弹出5个 | |
printf("pop 5 elements\n"); | |
for (int i = 0; i < 5; i++) { | |
if (ring_buf_pop(ring, &r_data[i]) != 0) { | |
printf("ring_buf is empty. %d\n", i); | |
break; | |
} | |
} | |
//弹出的数据 | |
printf("pop data,r_data: "); | |
for (int i = 0; i < 5; i++) { | |
printf("%d ", r_data[i]); | |
} | |
printf("\n"); | |
//弹出10个,但是在弹出5个之后,ring_buf会为空,结束弹出 | |
printf("pop 10 elements,after pop 5,ring_buf will empty,pop finish.\n"); | |
for (int i = 0; i < 10; i++) { | |
if (ring_buf_pop(ring, &r_data[i]) != 0) { | |
printf("ring_buf is empty. %d\n", i); | |
break; | |
} | |
} | |
//弹出的数据 | |
printf("\npop data,r_data: "); | |
for (int i = 0; i < 5; i++) { | |
printf("%d ", r_data[i]); | |
} | |
printf("\n"); | |
//写5个数据到ring_buf | |
printf("write 5 data in ring_buf\n"); | |
for (int i = 10; i < 15; i++) { | |
w_data[i] = i; | |
if (ring_buf_push(ring, w_data[i]) != 0) { | |
printf("ring_buf is fullly. %d\n", i); | |
break; | |
} | |
} | |
//弹出五个,为了测试head和tail不为0时的场景 | |
printf("pop 5,to make head and tail not equal 0\n"); | |
for (int i = 0; i < 5; i++) { | |
if (ring_buf_pop(ring, &r_data[i]) != 0) { | |
printf("ring_buf is empty. %d\n", i); | |
break; | |
} | |
} | |
//弹出的数据 | |
printf("\npop data,r_data: "); | |
for (int i = 0; i < 5; i++) { | |
printf("%d ", r_data[i]); | |
} | |
printf("\n"); | |
//写满直到ring full | |
printf("write until ring full\n"); | |
for (int i = 15; i < 50; i++) { | |
w_data[i] = i; | |
if (ring_buf_push(ring, w_data[i]) != 0) { | |
printf("ring_buf is fullly. %d\n", i); | |
break; | |
} | |
} | |
cnt = get_ring_buf_cnt(ring); //获取ring_buf有效长度 | |
//遍历ring buf数据 | |
printf("traverse ring buf data:\n"); | |
for (int i = 0; i < cnt; i++) { | |
idx = RING_BUF_IDX(i, ring->tail); //将普通索引转换成ring_buf索引 | |
printf("%d ", ring->buffer[idx]); | |
} | |
printf("\n"); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment