用户态缓存:链式缓冲区(Chain Buffer)

目录

链式缓冲区(Chain Buffer)简介

为什么选择链式缓冲区?

代码解析

1. 头文件与类型定义

2. 结构体定义

3. 宏定义与常量

4. 环形缓冲区的基本操作

5. 其他辅助函数

6. 数据读写操作的详细实现

7. 总结

8. 结合之前的内容

9. 具体应用解析

10. 综合应用

11. 总结


链式缓冲区(Chain Buffer)简介

链式缓冲区是一种通过链接多个缓冲区块来动态管理数据的结构。相比于固定大小的环形缓冲区,链式缓冲区具有更高的灵活性和可扩展性,特别适用于需要处理不同大小数据包的场景。它通过将数据分散存储在多个缓冲区块中,减少了内存浪费和数据移动的需求。

为什么选择链式缓冲区?

  • 灵活性和可扩展性:链式缓冲区能够动态地添加或移除缓冲区块,适应不同的数据量需求。
  • 减少数据移动:通过分散存储数据,避免了大规模的数据拷贝操作,提高了数据处理效率。
  • 高效内存利用:根据实际数据量动态分配缓冲区块,减少了内存浪费。

代码解析

让我们逐步解析你提供的链式缓冲区代码,理解其各个部分的功能和实现细节。

1. 头文件与类型定义

#ifndef _chain_buffer_h
#define _chain_buffer_h
#include <stdint.h>typedef struct buf_chain_s buf_chain_t;
typedef struct buffer_s buffer_t;// Function declarations
buffer_t * buffer_new(uint32_t sz);
uint32_t buffer_len(buffer_t *buf);
int buffer_add(buffer_t *buf, const void *data, uint32_t datlen);
int buffer_remove(buffer_t *buf, void *data, uint32_t datlen);
int buffer_drain(buffer_t *buf, uint32_t len);
void buffer_free(buffer_t *buf);
int buffer_search(buffer_t *buf, const char* sep, const int seplen);
uint8_t * buffer_write_atmost(buffer_t *p);#endif

1.1 头文件保护符

#ifndef _chain_buffer_h
#define _chain_buffer_h
...
#endif
  • 作用:防止头文件被多次包含,避免重复定义错误。

1.2 类型定义

typedef struct buf_chain_s buf_chain_t;
typedef struct buffer_s buffer_t;
  • 作用:为结构体 buf_chain_sbuffer_s 定义别名 buf_chain_tbuffer_t,简化后续代码的书写。

2. 结构体定义

struct buf_chain_s {struct buf_chain_s *next;uint32_t buffer_len;uint32_t misalign;uint32_t off;uint8_t *buffer;
};struct buffer_s {buf_chain_t *first;buf_chain_t *last;buf_chain_t **last_with_datap;uint32_t total_len;uint32_t last_read_pos; // for sep read
};

2.1 buf_chain_s 结构体

  • next (struct buf_chain_s *):指向下一个缓冲区块,实现链式结构。
  • buffer_len (uint32_t):缓冲区块的总大小,以字节为单位。
  • misalign (uint32_t):缓冲区块的未对齐偏移量,用于优化内存访问。
  • off (uint32_t):当前缓冲区块中有效数据的长度,以字节为单位。
  • buffer (uint8_t *):指向实际数据存储区的指针。

2.2 buffer_s 结构体

  • first (buf_chain_t *):链表的第一个缓冲区块。
  • last (buf_chain_t *):链表的最后一个缓冲区块。
  • last_with_datap (buf_chain_t **):指向链表中最后一个有数据的缓冲区块的指针的指针,用于快速定位添加新数据的位置。
  • total_len (uint32_t):缓冲区中当前存储的总数据量,以字节为单位。
  • last_read_pos (uint32_t):用于分隔符读取的上次读取位置,优化搜索性能。

3. 宏定义与常量

#define CHAIN_SPACE_LEN(ch) ((ch)->buffer_len - ((ch)->misalign + (ch)->off))
#define MIN_BUFFER_SIZE 1024
#define MAX_TO_COPY_IN_EXPAND 4096
#define BUFFER_CHAIN_MAX_AUTO_SIZE 4096
#define MAX_TO_REALIGN_IN_EXPAND 2048
#define BUFFER_CHAIN_MAX 16*1024*1024  // 16M
#define BUFFER_CHAIN_EXTRA(t, c) (t *)((buf_chain_t *)(c) + 1)
#define BUFFER_CHAIN_SIZE sizeof(buf_chain_t)
  • CHAIN_SPACE_LEN(ch):计算缓冲区块 ch 中剩余的可用空间。
  • MIN_BUFFER_SIZE:缓冲区块的最小大小,设置为1024字节。
  • MAX_TO_COPY_IN_EXPAND:扩展缓冲区时最多复制的字节数。
  • BUFFER_CHAIN_MAX_AUTO_SIZE:自动扩展缓冲区块的最大大小。
  • MAX_TO_REALIGN_IN_EXPAND:扩展时最大允许重新对齐的字节数。
  • BUFFER_CHAIN_MAX:缓冲区块的最大大小,设置为16MB。
  • BUFFER_CHAIN_EXTRA(t, c):宏,用于获取缓冲区块中的实际数据存储区的指针。
  • BUFFER_CHAIN_SIZE:缓冲区块结构体的大小。

4. 环形缓冲区的基本操作

4.1 创建缓冲区

buffer_t * buffer_new(uint32_t sz) {(void)sz;buffer_t * buf = (buffer_t *) malloc(sizeof(buffer_t));if (!buf) {return NULL;}memset(buf, 0, sizeof(*buf));buf->last_with_datap = &buf->first;return buf;
}
  • 功能:创建并初始化一个新的链式缓冲区。
  • 步骤
    1. 分配内存,初始化结构体 buffer_t
    2. 使用 memset 清零结构体成员。
    3. 初始化 last_with_datap 指针为指向 first,表示当前没有数据存储。
    4. 返回缓冲区指针。

注意:虽然函数接收一个大小参数 sz,但在当前实现中未使用(通过 (void)sz; 忽略)。这是因为链式缓冲区通过动态添加缓冲区块来适应不同的数据量需求,而不依赖于单一的固定大小。

4.2 获取缓冲区长度

uint32_t buffer_len(buffer_t *buf) {return buf->total_len;
}
  • 功能:返回缓冲区中当前存储的数据长度。
  • 实现:直接返回 total_len

4.3 释放缓冲区

void buffer_free(buffer_t *buf) {buf_chain_free_all(buf->first);
}
  • 功能:释放链式缓冲区中的所有缓冲区块,并释放缓冲区结构体本身。
  • 步骤
    1. 调用 buf_chain_free_all 释放所有缓冲区块。
    2. 注意:当前实现未释放 buffer_t 本身的内存,可能需要在调用者中进行释放。

4.4 添加数据到缓冲区

int buffer_add(buffer_t *buf, const void *data_in, uint32_t datlen) {buf_chain_t *chain, *tmp;const uint8_t *data = data_in;uint32_t remain, to_alloc;int result = -1;if (datlen > BUFFER_CHAIN_MAX - buf->total_len) {goto done;}if (*buf->last_with_datap == NULL) {chain = buf->last;} else {chain = *buf->last_with_datap;}if (chain == NULL) {chain = buf_chain_insert_new(buf, datlen);if (!chain)goto done;}remain = chain->buffer_len - chain->misalign - chain->off;if (remain >= datlen) {memcpy(chain->buffer + chain->misalign + chain->off, data, datlen);chain->off += datlen;buf->total_len += datlen;// buf->n_add_for_cb += datlen;goto out;} else if (buf_chain_should_realign(chain, datlen)) {buf_chain_align(chain);memcpy(chain->buffer + chain->off, data, datlen);chain->off += datlen;buf->total_len += datlen;// buf->n_add_for_cb += datlen;goto out;}to_alloc = chain->buffer_len;if (to_alloc <= BUFFER_CHAIN_MAX_AUTO_SIZE/2)to_alloc <<= 1;if (datlen > to_alloc)to_alloc = datlen;tmp = buf_chain_new(to_alloc);if (tmp == NULL)goto done;if (remain) {memcpy(chain->buffer + chain->misalign + chain->off, data, remain);chain->off += remain;buf->total_len += remain;// buf->n_add_for_cb += remain;}data += remain;datlen -= remain;memcpy(tmp->buffer, data, datlen);tmp->off = datlen;buf_chain_insert(buf, tmp);// buf->n_add_for_cb += datlen;
out:result = 0;
done:return result;
}
  • 功能:将数据添加到链式缓冲区中。
  • 步骤
    1. 检查缓冲区是否已满
      • 如果要添加的数据 datlen 超过缓冲区的最大允许长度(BUFFER_CHAIN_MAX),则返回错误 -1
    2. 定位当前可用的缓冲区块
      • 如果 last_with_datap 指向 NULL,则使用 last 缓冲区块。
      • 否则,使用 last_with_datap 指向的缓冲区块。
    3. 如果当前缓冲区块为空,则创建一个新的缓冲区块并插入链表。
    4. 计算当前缓冲区块的剩余空间 remain
    5. 数据拷贝
      • 情况1:如果剩余空间足够,直接将数据拷贝到缓冲区块中,并更新 offtotal_len
      • 情况2:如果需要重新对齐,并且剩余空间足够,调用 buf_chain_align 重新对齐缓冲区块,然后拷贝数据。
      • 情况3:如果剩余空间不足,创建一个新的缓冲区块,并将部分数据拷贝到当前缓冲区块,剩余数据拷贝到新缓冲区块中。
    6. 更新结果:成功添加数据后,返回 0

4.5 从缓冲区移除数据

int buffer_remove(buffer_t *buf, void *data_out, uint32_t datlen) {uint32_t n = buf_copyout(buf, data_out, datlen);if (n > 0) {if (buffer_drain(buf, n) < 0)n = -1;}return (int)n;
}
  • 功能:从链式缓冲区中读取并移除数据。
  • 步骤
    1. 调用 buf_copyout 从缓冲区中读取数据到 data_out,读取长度为 datlen
    2. 如果成功读取 (n > 0),则调用 buffer_drain 移除已读取的数据。
    3. 返回实际读取的数据长度 n,如果移除失败,返回 -1

4.6 清空缓冲区的一部分数据

int buffer_drain(buffer_t *buf, uint32_t len) {buf_chain_t *chain, *next;uint32_t remaining, old_len;old_len = buf->total_len;if (old_len == 0)return 0;if (len >= old_len) {len = old_len;for (chain = buf->first; chain != NULL; chain = next) {next = chain->next;free(chain);}ZERO_CHAIN(buf);} else {buf->total_len -= len;remaining = len;for (chain = buf->first; remaining >= chain->off; chain = next) {next = chain->next;remaining -= chain->off;if (chain == *buf->last_with_datap) {buf->last_with_datap = &buf->first;}if (&chain->next == buf->last_with_datap)buf->last_with_datap = &buf->first;free(chain);}buf->first = chain;chain->misalign += remaining;chain->off -= remaining;}// buf->n_del_for_cb += len;return len;
}
  • 功能:从链式缓冲区中清除 len 字节的数据,而不读取到用户空间。
  • 步骤
    1. 检查缓冲区是否为空
      • 如果缓冲区为空,返回 0
    2. 清除操作
      • 情况1:如果要清除的数据 len 大于或等于缓冲区中的总数据量 old_len,则清空整个缓冲区,释放所有缓冲区块,并调用 ZERO_CHAIN 重置缓冲区结构体。
      • 情况2:如果要清除的数据 len 小于总数据量,则逐个缓冲区块地清除数据,直到清除完 len 字节。
        • 更新 total_len
        • 移除完全清除的缓冲区块。
        • 对部分清除的缓冲区块,更新 misalignoff
    3. 返回:返回实际清除的数据长度 len

4.7 搜索特定分隔符

int buffer_search(buffer_t *buf, const char* sep, const int seplen) {buf_chain_t *chain;int i;chain = buf->first;if (chain == NULL)return 0;int bytes = chain->off;while (bytes <= buf->last_read_pos) {chain = chain->next;if (chain == NULL)return 0;bytes += chain->off;}bytes -= buf->last_read_pos;int from = chain->off - bytes;for (i = buf->last_read_pos; i <= buf->total_len - seplen; i++) {if (check_sep(chain, from, sep, seplen)) {buf->last_read_pos = 0;return i+seplen;}++from;--bytes;if (bytes == 0) {chain = chain->next;from = 0;if (chain == NULL)break;bytes = chain->off;}}buf->last_read_pos = i;return 0;
}
  • 功能:在链式缓冲区中搜索特定的分隔符 sep,用于界定数据包的边界(例如,查找换行符 \n)。
  • 步骤
    1. 初始化
      • 从第一个缓冲区块 first 开始。
      • 如果缓冲区为空,返回 0
      • 计算当前缓冲区块中有数据的字节数 bytes
    2. 定位开始搜索的位置
      • 跳过已读的位置 last_read_pos,找到当前搜索的起始缓冲区块和偏移量 from
    3. 遍历缓冲区数据
      • last_read_pos 开始,逐个字节检查是否匹配分隔符 sep
      • 使用 check_sep 函数检查分隔符是否完整匹配。
      • 如果找到匹配,更新 last_read_pos 并返回分隔符结束的位置 i + seplen
    4. 更新搜索位置
      • 如果未找到匹配,更新 last_read_pos 为当前检查的位置 i
    5. 返回
      • 返回找到的分隔符结束的位置,或 0 表示未找到。

4.8 获取写入缓冲区的可写指针

uint8_t * buffer_write_atmost(buffer_t *p) {buf_chain_t *chain, *next, *tmp, *last_with_data;uint8_t *buffer;uint32_t remaining;int removed_last_with_data = 0;int removed_last_with_datap = 0;chain = p->first;uint32_t size = p->total_len;if (chain->off >= size) {return chain->buffer + chain->misalign;}remaining = size - chain->off;for (tmp=chain->next; tmp; tmp=tmp->next) {if (tmp->off >= (size_t)remaining)break;remaining -= tmp->off;}if (chain->buffer_len - chain->misalign >= (size_t)size) {/* already have enough space in the first chain */size_t old_off = chain->off;buffer = chain->buffer + chain->misalign + chain->off;tmp = chain;tmp->off = size;size -= old_off;chain = chain->next;} else {if ((tmp = buf_chain_new(size)) == NULL) {return NULL;}buffer = tmp->buffer;tmp->off = size;p->first = tmp;}last_with_data = *p->last_with_datap;for (; chain != NULL && (size_t)size >= chain->off; chain = next) {next = chain->next;if (chain->buffer) {memcpy(buffer, chain->buffer + chain->misalign, chain->off);size -= chain->off;buffer += chain->off;}if (chain == last_with_data)removed_last_with_data = 1;if (&chain->next == p->last_with_datap)removed_last_with_datap = 1;free(chain);}if (chain != NULL) {memcpy(buffer, chain->buffer + chain->misalign, size);chain->misalign += size;chain->off -= size;} else {p->last = tmp;}tmp->next = chain;if (removed_last_with_data) {p->last_with_datap = &p->first;} else if (removed_last_with_datap) {if (p->first->next && p->first->next->off)p->last_with_datap = &p->first->next;elsep->last_with_datap = &p->first;}return tmp->buffer + tmp->misalign;
}
  • 功能:获取当前缓冲区中可写入数据的位置指针,最多可写入的字节数。
  • 步骤
    1. 初始化
      • 获取第一个缓冲区块 first
      • 计算当前总数据量 size
    2. 检查是否有足够的空间
      • 如果第一个缓冲区块的 off 大于或等于总数据量 size,则返回当前写入位置的指针。
    3. 定位剩余空间
      • 计算 remaining 字节,寻找可以连续写入的缓冲区块。
    4. 检查并扩展缓冲区
      • 如果当前缓冲区块有足够的空间,直接返回可写入的位置。
      • 否则,创建一个新的缓冲区块并插入链表。
    5. 复制数据并更新指针
      • 将数据从旧缓冲区块复制到新缓冲区块中,确保数据的连续性。
      • 更新 last_with_datap 指针,确保下一次添加数据时能正确定位。
    6. 返回可写入的位置指针

注意:链式缓冲区通过动态添加缓冲区块,实现了高效的数据写入管理,避免了单一缓冲区块空间不足导致的阻塞。

5. 其他辅助函数

5.1 创建新的缓冲区块

static buf_chain_t * buf_chain_new(uint32_t size) {buf_chain_t *chain;uint32_t to_alloc;if (size > BUFFER_CHAIN_MAX - BUFFER_CHAIN_SIZE)return (NULL);size += BUFFER_CHAIN_SIZE;if (size < BUFFER_CHAIN_MAX / 2) {to_alloc = MIN_BUFFER_SIZE;while (to_alloc < size) {to_alloc <<= 1;}} else {to_alloc = size;}if ((chain = malloc(to_alloc)) == NULL)return (NULL);memset(chain, 0, BUFFER_CHAIN_SIZE);chain->buffer_len = to_alloc - BUFFER_CHAIN_SIZE;chain->buffer = BUFFER_CHAIN_EXTRA(uint8_t, chain);return (chain);
}
  • 功能:创建并初始化一个新的缓冲区块。
  • 步骤
    1. 检查缓冲区块大小
      • 如果请求的大小 size 超过最大允许大小 BUFFER_CHAIN_MAX - BUFFER_CHAIN_SIZE,则返回 NULL
    2. 计算实际分配大小
      • 包括缓冲区块结构体的大小。
      • 如果请求大小小于一半的自动扩展最大大小,则将其向上舍入为2的幂次方。
    3. 分配内存并初始化
      • 使用 malloc 分配内存。
      • 使用 memset 清零缓冲区块结构体部分。
      • 设置 buffer_len 为实际数据存储区的大小。
      • 设置 buffer 指针指向实际数据存储区。
    4. 返回缓冲区块指针

5.2 释放所有缓冲区块

static void buf_chain_insert(buffer_t *buf, buf_chain_t *chain) {if (*buf->last_with_datap == NULL) {buf->first = buf->last = chain;} else {buf_chain_t **chp;chp = free_empty_chains(buf);*chp = chain;if (chain->off)buf->last_with_datap = chp;buf->last = chain;}buf->total_len += chain->off;
}
  • 功能:将新的缓冲区块 chain 插入到链式缓冲区 buf 中。
  • 步骤
    1. 检查是否存在可用的缓冲区块指针
      • 如果 last_with_datap 指向 NULL,则更新 firstlast 为新的缓冲区块。
      • 否则,调用 free_empty_chains 寻找并释放空闲缓冲区块指针。
    2. 插入缓冲区块
      • 将新的缓冲区块 chain 插入到找到的位置。
      • 如果缓冲区块中有数据 (off > 0),则更新 last_with_datap 指针为当前缓冲区块的位置。
      • 更新 last 为新的缓冲区块。
    3. 更新总数据长度:将 chain->off 加到 total_len 上。

5.4 插入新的缓冲区块

static inline buf_chain_t * buf_chain_insert_new(buffer_t *buf, uint32_t datlen) {buf_chain_t *chain;if ((chain = buf_chain_new(datlen)) == NULL)return NULL;buf_chain_insert(buf, chain);return chain;
}
  • 功能:创建并插入一个新的缓冲区块。
  • 步骤
    1. 调用 buf_chain_new 创建一个新的缓冲区块。
    2. 调用 buf_chain_insert 将新缓冲区块插入链表。
    3. 返回新缓冲区块的指针。

5.5 判断是否需要重新对齐缓冲区块

static int buf_chain_should_realign(buf_chain_t *chain, uint32_t datlen) {return chain->buffer_len - chain->off >= datlen &&(chain->off < chain->buffer_len / 2) &&(chain->off <= MAX_TO_REALIGN_IN_EXPAND);
}
  • 功能:判断当前缓冲区块是否需要重新对齐,以便腾出足够的空间添加新数据。
  • 条件
    1. 缓冲区块剩余空间 buffer_len - off 大于等于新数据长度 datlen
    2. 当前数据量 off 小于缓冲区块大小的一半,表示有足够的空间可以重新对齐。
    3. 当前数据量 off 小于等于最大允许重新对齐的字节数 MAX_TO_REALIGN_IN_EXPAND

5.6 重新对齐缓冲区块

static void buf_chain_align(buf_chain_t *chain) {memmove(chain->buffer, chain->buffer + chain->misalign, chain->off);chain->misalign = 0;
}
  • 功能:将缓冲区块中的数据重新对齐到缓冲区起始位置,释放 misalign 部分的空间。
  • 步骤
    1. 使用 memmove 将数据从 buffer + misalign 复制到 buffer,实现数据的左移。
    2. misalign 更新为 0,表示数据已对齐到缓冲区起始位置。

6. 数据读写操作的详细实现

6.1 从缓冲区复制数据到用户空间

static uint32_t buf_copyout(buffer_t *buf, void *data_out, uint32_t datlen) {buf_chain_t *chain;char *data = data_out;uint32_t nread;chain = buf->first;if (datlen > buf->total_len)datlen = buf->total_len;if (datlen == 0)return 0;nread = datlen;while (datlen && datlen >= chain->off) {uint32_t copylen = chain->off;memcpy(data,chain->buffer + chain->misalign,copylen);data += copylen;datlen -= copylen;chain = chain->next;}if (datlen) {memcpy(data, chain->buffer + chain->misalign, datlen);}return nread;
}
  • 功能:将缓冲区中的数据复制到用户提供的输出缓冲区 data_out,最多复制 datlen 字节。
  • 步骤
    1. 初始化
      • 获取第一个缓冲区块 first
      • 如果请求的读取长度 datlen 大于总数据量 total_len,则调整 datlentotal_len
      • 如果 datlen0,返回 0
      • 设置 nread 为实际要读取的字节数。
    2. 遍历缓冲区块
      • 对于每个缓冲区块,复制其数据到输出缓冲区。
      • 如果当前缓冲区块的 off 大于或等于剩余的 datlen,则复制部分数据。
      • 更新 data 指针和剩余的 datlen
    3. 返回:返回实际复制的字节数 nread

6.2 清空缓冲区的一部分数据

int buffer_drain(buffer_t *buf, uint32_t len) {buf_chain_t *chain, *next;uint32_t remaining, old_len;old_len = buf->total_len;if (old_len == 0)return 0;if (len >= old_len) {len = old_len;for (chain = buf->first; chain != NULL; chain = next) {next = chain->next;free(chain);}ZERO_CHAIN(buf);} else {buf->total_len -= len;remaining = len;for (chain = buf->first; remaining >= chain->off; chain = next) {next = chain->next;remaining -= chain->off;if (chain == *buf->last_with_datap) {buf->last_with_datap = &buf->first;}if (&chain->next == buf->last_with_datap)buf->last_with_datap = &buf->first;free(chain);}buf->first = chain;chain->misalign += remaining;chain->off -= remaining;}// buf->n_del_for_cb += len;return len;
}
  • 功能:从链式缓冲区中清除 len 字节的数据,而不读取到用户空间。
  • 步骤
    1. 检查缓冲区是否为空
      • 如果缓冲区为空,返回 0
    2. 清除操作
      • 情况1:如果要清除的数据 len 大于或等于缓冲区中的总数据量 old_len,则清空整个缓冲区,释放所有缓冲区块,并调用 ZERO_CHAIN 重置缓冲区结构体。
      • 情况2:如果要清除的数据 len 小于总数据量,则逐个缓冲区块地清除数据,直到清除完 len 字节。
        • 更新 total_len
        • 移除完全清除的缓冲区块。
        • 对部分清除的缓冲区块,更新 misalignoff
    3. 返回:返回实际清除的数据长度 len

6.3 从缓冲区移除并读取数据

int buffer_remove(buffer_t *buf, void *data_out, uint32_t datlen) {uint32_t n = buf_copyout(buf, data_out, datlen);if (n > 0) {if (buffer_drain(buf, n) < 0)n = -1;}return (int)n;
}
  • 功能:从链式缓冲区中读取并移除数据。
  • 步骤
    1. 调用 buf_copyout 从缓冲区中读取数据到 data_out,读取长度为 datlen
    2. 如果成功读取 (n > 0),则调用 buffer_drain 移除已读取的数据。
    3. 返回实际读取的数据长度 n,如果移除失败,返回 -1

6.4 检查分隔符是否匹配

static bool check_sep(buf_chain_t * chain, int from, const char *sep, int seplen) {for (;;) {int sz = chain->off - from;if (sz >= seplen) {return memcmp(chain->buffer + chain->misalign + from, sep, seplen) == 0;}if (sz > 0) {if (memcmp(chain->buffer + chain->misalign + from, sep, sz)) {return false;}}chain = chain->next;sep += sz;seplen -= sz;from = 0;}
}
  • 功能:在链式缓冲区中检查分隔符 sep 是否完整匹配。
  • 步骤
    1. 循环检查
      • 计算当前缓冲区块中从 from 位置开始的数据长度 sz
      • 如果 sz 大于或等于分隔符长度 seplen,则进行内存比较,检查是否匹配。
      • 如果 sz 小于分隔符长度,但有部分数据匹配,则继续检查下一个缓冲区块,确保分隔符跨越缓冲区块时也能正确匹配。
    2. 返回
      • 如果匹配成功,返回 true
      • 如果匹配失败,返回 false

7. 总结

链式缓冲区通过链接多个缓冲区块,实现了动态的、灵活的数据管理。与环形缓冲区相比,链式缓冲区具有以下优势:

  • 更高的灵活性:能够动态添加或移除缓冲区块,适应不同的数据量需求。
  • 更好的内存利用:根据实际数据量动态分配缓冲区块,减少内存浪费。
  • 减少数据移动:通过分散存储数据,避免了大量的数据拷贝和移动操作。

然而,链式缓冲区也存在一些挑战:

  • 复杂性增加:需要维护多个缓冲区块的链接,增加了代码的复杂性。
  • 系统调用开销:在缓冲区块不足时,需要频繁地进行内存分配和释放,可能增加系统调用的开销。
  • 碎片化问题:长期运行可能导致内存碎片化,影响性能。

通过结合Reactor 模式,链式缓冲区能够高效地管理和传输网络数据,特别是在高并发和多连接的场景中。它确保了数据的完整性和可靠性,即使在生产者和消费者速度不匹配的情况下,也能有效地管理数据流动,避免数据丢失和阻塞。

8. 结合之前的内容

用户态缓存:环形缓冲区(Ring Buffer)-CSDN博客文章浏览阅读168次,点赞13次,收藏13次。环形缓冲区是一种高效的数据结构,广泛应用于生产者-消费者模型中。在网络通信中,尤其是用户态缓存区中,环形缓冲区通过循环使用固定大小的内存区域,减少数据移动和内存管理开销,提升数据传输效率。#endif作用:为定义一个别名buffer_t,简化后续代码的书写。高效的数据管理:通过固定大小的缓冲区和双指针机制,环形缓冲区实现了高效的数据读写操作。减少数据移动:利用环形地址计算和分段拷贝,避免了大量的数据拷贝和移动操作,提升了性能。灵活的空间管理:通过动态调整和优化(如。https://blog.csdn.net/weixin_43925427/article/details/142358862?fromshare=blogdetail&sharetype=blogdetail&sharerId=142358862&sharerefer=PC&sharesource=weixin_43925427&sharefrom=from_link在之前的讲解中,我们深入解析了 环形缓冲区 的实现及其在网络通信中的应用。链式缓冲区作为另一种常用的数据结构,提供了不同的优势和适用场景。让我们将链式缓冲区与环形缓冲区进行对比,进一步理解它们在用户态缓存区设计中的应用。

8.1 环形缓冲区 vs 链式缓冲区

特性环形缓冲区(Ring Buffer)链式缓冲区(Chain Buffer)
内存管理固定大小,通常为2的幂次方动态添加缓冲区块,灵活调整大小
数据移动通过环形地址计算,避免大规模数据拷贝分散存储数据,减少数据移动
内存利用率可能存在内存浪费,尤其在数据量波动大时高效内存利用,根据需求动态分配缓冲区块
复杂性相对简单,实现容易较复杂,需要维护链表结构和缓冲区块链接
适用场景适用于数据量固定且高效的数据流管理适用于数据量不固定,需处理不同大小数据包

8.2 在 Reactor 模式中的应用

Reactor 模式 中,无论是环形缓冲区还是链式缓冲区,都扮演着重要的数据管理角色。它们确保了从网络读取的数据能够高效、可靠地传输到用户空间,并且在需要发送数据时能够及时、完整地写入网络。

  • 环形缓冲区 适用于数据量相对固定、读写速度相匹配的场景,通过减少数据拷贝提升性能。
  • 链式缓冲区 适用于数据量不固定、需要动态扩展的场景,通过灵活的缓冲区块管理提升内存利用率和适应性。

9. 具体应用解析

让我们将链式缓冲区的实现与之前的 服务器代码 结合起来,理解其在实际工作中的具体应用。

9.1 服务器主程序中的链式缓冲区

在 服务器代码 中,链式缓冲区被用于管理每个客户端连接的接收缓冲区 in 和发送缓冲区 out。下面是关键部分的解析:

9.1.1 接受连接回调函数

void accept_cb(int fd, int events, void *privdata) {event_t *e = (event_t*) privdata;struct sockaddr_in addr;memset(&addr, 0, sizeof(struct sockaddr_in));socklen_t len = sizeof(addr);int clientfd = accept(fd, (struct sockaddr*)&addr, &len);if (clientfd <= 0) {printf("accept failed\n");return;}char str[INET_ADDRSTRLEN] = {0};printf("recv from %s at port %d\n", inet_ntop(AF_INET, &addr.sin_addr, str, sizeof(str)),ntohs(addr.sin_port));event_t *ne = new_event(event_base(e), clientfd, read_cb, 0, 0);add_event(event_base(e), EPOLLIN, ne);set_nonblock(clientfd);
}
  • 功能
    1. 接受新连接:调用 accept 函数接受新的客户端连接,获取 clientfd
    2. 打印客户端信息:使用 inet_ntop 将客户端 IP 地址转换为字符串,并打印其端口号。
    3. 创建新事件对象:调用 new_event 为新连接创建一个事件对象,关联 read_cb 作为读事件的回调函数。
    4. 注册事件:将新事件对象添加到 reactor 中,监听 EPOLLIN 事件(有数据可读)。
    5. 设置非阻塞模式:将客户端套接字设置为非阻塞模式,确保事件循环不会被单个连接阻塞。

9.1.2 读取数据回调函数

void read_cb(int fd, int events, void *privdata) {event_t *e = (event_t *)privdata;int n = event_buffer_read(e); // 将网络中读缓冲区的数据拷贝到用户态缓冲区if (n > 0) {// buffer_search 检测是否是一个完整的数据包int len = buffer_search(evbuf_in(e), "\n", 1);if (len > 0 && len < 1024) {char buf[1024] = {0};buffer_remove(evbuf_in(e), buf, len);event_buffer_write(e, buf, len);}}
}
  • 功能
    1. 读取数据:调用 event_buffer_read 从网络读取数据,并将其添加到接收缓冲区 in
    2. 搜索分隔符:使用 buffer_search 在接收缓冲区中查找换行符 \n,判断是否收到完整的数据包。
    3. 处理完整数据包
      • 如果找到完整的数据包且长度合理(len < 1024),则:
        • 从接收缓冲区中移除该数据包,存储到本地缓冲区 buf
        • 将数据包写入发送缓冲区 out,准备发送回客户端。

9.1.3 读取数据到用户态缓冲区

int event_buffer_read(event_t *e) {int fd = e->fd;int num = 0;while (1) {char buf[1024] = {0};int n = read(fd, buf, 1024);if (n == 0) {printf("close connection fd = %d\n", fd);if (e->error_fn)e->error_fn(fd, "close socket");del_event(e->r, e);close(fd);return 0;} else if (n < 0) {if (errno == EINTR)continue;if (errno == EWOULDBLOCK)break;printf("read error fd = %d err = %s\n", fd, strerror(errno));if (e->error_fn)e->error_fn(fd, strerror(errno));del_event(e->r, e);close(fd);return 0;} else {printf("recv data from client:%s", buf);buffer_add(evbuf_in(e), buf, n);}num += n;}return num;
}
  • 功能
    1. 持续读取数据:使用 read 系统调用从套接字读取数据,直到没有更多数据可读。
    2. 处理读取结果
      • n == 0:表示客户端关闭连接,打印信息,触发错误回调,删除事件并关闭套接字。
      • n < 0
        • EINTR:被信号中断,继续读取。
        • EWOULDBLOCK:非阻塞模式下没有更多数据可读,退出循环。
        • 其他错误,打印错误信息,触发错误回调,删除事件并关闭套接字。
      • n > 0:成功读取数据,将数据添加到接收缓冲区 in
    3. 返回:返回读取的数据总量 num

9.1.4 写入数据到套接字

int event_buffer_write(event_t *e, void * buf, int sz) {buffer_t *out = evbuf_out(e);if (buffer_len(out) == 0) {int n = _write_socket(e, buf, sz);if (n == 0 || n < sz) {// 发送失败,除了将没有发送出去的数据写入缓冲区,还要注册写事件buffer_add(out, (char *)buf+n, sz-n);enable_event(e->r, e, 1, 1);return 0;} else if (n < 0) return 0;return 1;}buffer_add(out, (char *)buf, sz);return 1;
}
  • 功能
    1. 获取发送缓冲区 out
    2. 尝试直接写入套接字
      • 如果发送缓冲区为空,尝试调用 _write_socket 将数据直接写入套接字。
      • 发送成功且全部发送:返回 1
      • 发送部分失败n < sz):将未发送的数据添加到发送缓冲区 out,并注册写事件 EPOLLOUT,等待后续发送。
    3. 发送缓冲区不为空:将数据添加到发送缓冲区 out,等待后续发送。
    4. 返回:根据发送结果返回相应的值。

9.1.5 实际写入套接字

static int _write_socket(event_t *e, void * buf, int sz) {int fd = e->fd;while (1) {int n = write(fd, buf, sz);if (n < 0) {if (errno == EINTR)continue;if (errno == EWOULDBLOCK)break;if (e->error_fn)e->error_fn(fd, strerror(errno));del_event(e->r, e);close(e->fd);}return n;}return 0;
}
  • 功能:尝试将数据写入套接字。
  • 步骤
    1. 尝试写入:调用 write 系统调用将数据写入套接字。
    2. 处理写入结果
      • n < 0
        • EINTR:被信号中断,继续写入。
        • EWOULDBLOCK:非阻塞模式下无法立即写入,退出循环,返回 0
        • 其他错误,打印错误信息,触发错误回调,删除事件并关闭套接字。
      • n >= 0:返回实际写入的字节数 n
    3. 返回:返回写入的字节数 n,如果无法写入则返回 0

10. 综合应用

10.1 在用户态缓存区中的应用

在链式缓冲区中,buffer_t 结构体管理着多个缓冲区块,每个缓冲区块存储一定量的数据。当有新的数据到达时,通过 buffer_add 将数据添加到适当的缓冲区块中;当需要读取数据时,通过 buffer_remove 从链表中按顺序读取数据。这种设计能够灵活地应对不同大小的数据包和动态的数据量需求。

10.2 处理生产者与消费者速度不匹配

在网络通信中,生产者(如内核协议栈)生成数据的速度可能快于消费者(如应用程序)的处理速度,或反之。链式缓冲区通过以下方式有效地处理这种不匹配:

  • 生产者速度快于消费者
    • 链式缓冲区通过动态添加缓冲区块,暂存大量数据,避免数据丢失。
    • 确保缓冲区块的灵活扩展,适应高峰数据量。
  • 消费者速度快于生产者
    • 链式缓冲区可以高效地移除已处理的数据,腾出空间给新的数据。
    • 通过释放已清除的缓冲区块,避免内存浪费。

10.3 搜索分隔符和数据包处理

链式缓冲区中的 buffer_search 函数通过查找特定的分隔符(如换行符 \n),实现数据包的界定和拆分。这对于基于协议的通信(如 HTTP、SMTP 等)尤为重要,确保应用程序能够正确解析和处理每个完整的数据包。

11. 总结

通过详细解析这段链式缓冲区的代码,我们深入理解了链式缓冲区的结构和工作原理:

  • 高效的数据管理:通过链接多个缓冲区块,链式缓冲区实现了高效的数据读写操作,适应不同的数据量需求。
  • 减少数据移动:通过分散存储数据,链式缓冲区避免了大规模的数据拷贝和移动操作,提升了性能。
  • 灵活的空间管理:通过动态添加和释放缓冲区块,链式缓冲区能够灵活地适应不同的数据量需求,保持高效运行。
  • 可靠的数据传输:在生产者和消费者速度不匹配的情况下,链式缓冲区通过暂存和管理数据,确保数据的完整性和可靠性。

结合之前对 环形缓冲区 的解析,我们可以看到链式缓冲区在处理动态和不规则数据流方面具有更大的优势。然而,链式缓冲区也带来了更高的实现复杂性和潜在的系统调用开销,需要在具体应用中权衡选择。

理解和掌握链式缓冲区的实现和应用,对于优化网络应用程序的性能,提升系统的响应速度和稳定性具有重要意义。结合 Reactor 模式,链式缓冲区能够高效地管理和传输网络数据,特别是在高并发和多连接的场景中,确保数据传输的流畅性和可靠性。

 参考:

0voice · GitHub

GitHub - TryTryTL/buffer_design

用户态缓存:高效数据交互与性能优化-CSDN博客

用户态缓存:环形缓冲区(Ring Buffer)-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/53566.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

鸿蒙OpenHarmony【小型系统基础内核(进程管理任务)】子系统开发

任务 基本概念 从系统的角度看&#xff0c;任务Task是竞争系统资源的最小运行单元。任务可以使用或等待CPU、使用内存空间等系统资源&#xff0c;并独立于其它任务运行。 OpenHarmony 内核中使用一个任务表示一个线程。 OpenHarmony 内核中同优先级进程内的任务统一调度、运…

STM32 map 文件浅析

目录 一、概述二、Section Cross References三、Removing Unused input sections from the image四、Memory Map of the image1、Local Symbols2、全局符号&#xff08;Global Symbols&#xff09; 五、Image Symbol Table六、Image component sizes 一、概述 .map 文件是编译…

【质优价廉】GAP9 AI算力处理器赋能智能可听耳机,超低功耗畅享未来音频体验!

当今世界&#xff0c;智能可听设备已经成为了流行趋势。随后耳机市场的不断成长起来&#xff0c;消费者又对AI-ANC&#xff0c;AI-ENC&#xff08;环境噪音消除&#xff09;降噪的需求逐年增加&#xff0c;但是&#xff0c;用户对于产品体验的需求也从简单的需求&#xff0c;升…

半导体器件制造5G智能工厂数字孪生物联平台,推进制造业数字化转型

半导体器件制造行业作为高科技领域的核心驱动力&#xff0c;正积极探索和实践以5G智能工厂数字孪生平台为核心的新型制造模式。这一创新不仅极大地提升了生产效率与质量&#xff0c;更为制造业的未来发展绘制了一幅智能化、网络化的宏伟蓝图。 在半导体器件制造5G智能工厂中&a…

Java笔试面试题AI答之设计模式(1)

文章目录 1. 简述什么是设计模式 &#xff1f;2. 叙述常见Java设计模式分类 &#xff1f;3. Java 设计模式的六大原则 &#xff1f;4. 简述对 MVC 的理解&#xff0c; MVC 有什么优缺点&#xff1f;MVC 的三个核心部分&#xff1a;MVC 的优点&#xff1a;MVC 的缺点&#xff1a…

巨潮股票爬虫逆向

目标网站 aHR0cDovL3dlYmFwaS5jbmluZm8uY29tLmNuLyMvSVBPTGlzdD9tYXJrZXQ9c3o 一、抓包分析 请求头参数加密 二、逆向分析 下xhr断点 参数生成位置 发现是AES加密&#xff0c;不过是混淆的&#xff0c;但并不影响咱们扣代码 文章仅提供技术交流学习&#xff0c;不可对目标服…

LabVIEW提高开发效率技巧----合理使用数据流与内存管理

理使用数据流和内存管理是LabVIEW开发中提高性能和稳定性的关键&#xff0c;特别是在处理大数据或高频率信号时&#xff0c;优化可以避免内存消耗过大、程序卡顿甚至崩溃。 1. 使用 Shift Register 进行内存管理 Shift Register&#xff08;移位寄存器&#xff09; 是 LabVIE…

前缀和问题

洛谷题面 这个其实可以当模板了。 代码&#xff1a; #include<bits/stdc.h> using namespace std; const int N1e510; int sum[N]; int main(){ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);int n,m,x;cin>>n;for(int i1;i<n;i){cin>>x;sum[i]sum[i…

《微信小程序实战(4) · 地图导航功能》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

240924-通过服务器代理ip地址及port端口wget等下载文件

A. 如何下载 在服务器上设置了代理 IP 和端口后&#xff0c;可以使用以下命令行格式通过 wget 下载文件&#xff1a; wget -e use_proxyyes -e http_proxyhttp://代理IP:端口号 目标文件URL或者&#xff0c;如果你使用 HTTPS 协议&#xff0c;可以使用以下命令&#xff1a; …

数据结构应试-1

1. 好像是错的 2. n个元素&#xff0c;插入的可能有n1个位置&#xff0c;所以n&#xff08;n1&#xff09;/2*(n1)2/n 3. 4. 5. 6. 假设我们有一个循环队列&#xff0c;数组的长度为 n 10&#xff0c;并且当前队头指针 f 的位置是 2&#xff0c;队尾指针 r 的位置是 8。我们需…

【开源免费】基于SpringBoot+Vue.JS墙绘产品展示交易平台(JAVA毕业设计)

本文项目编号 T 049 &#xff0c;文末自助获取源码 \color{red}{T049&#xff0c;文末自助获取源码} T049&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 查…

携手SelectDB,观测云实现性能与成本的双重飞跃

在刚刚落下帷幕的2024云栖大会上&#xff0c;观测云又一次迎来了全面革新。携手SelectDB&#xff0c;实现了技术的飞跃&#xff0c;这不仅彰显了观测云在监控观测领域的技术实力&#xff0c;也预示着我们可以为全球用户提供更加高效、稳定的数据监测与分析服务。这一技术升级&a…

Golang | Leetcode Golang题解之第435题无重叠区间

题目&#xff1a; 题解&#xff1a; func eraseOverlapIntervals(intervals [][]int) int {n : len(intervals)if n 0 {return 0}sort.Slice(intervals, func(i, j int) bool { return intervals[i][1] < intervals[j][1] })ans, right : 1, intervals[0][1]for _, p : ra…

【计算机视觉】YoloV8-训练与测试教程

✨ Blog’s 主页: 白乐天_ξ( ✿&#xff1e;◡❛) &#x1f308; 个人Motto&#xff1a;他强任他强&#xff0c;清风拂山冈&#xff01; &#x1f4ab; 欢迎来到我的学习笔记&#xff01; 制作数据集 Labelme 数据集 数据集选用自己标注的&#xff0c;可参考以下&#xff1a…

面经 | css

CSS CSSpositiondisplayflex: 1元素居中flexalign-itemjustify-contentabsolutemargin:auto align-item vs vertical-align CSS position static: 默认。就是自然顺序&#xff0c;从上到下&#xff0c;从左到右&#xff0c;爱着你排列&#xff1b;absolute&#xff1a;绝对。…

如何备份SqlServer数据库

第一步&#xff1a;登录你要备份的服务器数据库ssms 第二步&#xff1a;选择你要备份的数据库 此处已PZ-SJCS 数据库为例 右键该数据库-->任务-->备份 第三步&#xff1a;选择你备份的类型备份组件等&#xff0c;目标磁盘 &#xff0c;点击添加选择将你备份的文件备份那…

数据结构~二叉搜索树

文章目录 一、二叉树搜索的概念二、二叉树搜索的结构二叉树搜索的性能分析二叉树搜索的插入二叉树搜索的查找二叉树搜索的删除 三、二叉搜索树key和key/value使用场景四、二叉树搜索的练习将二叉搜索树就地转化为已排序的双向循环链表从前序与中序遍历序列构造二叉树二叉树的前…

茶思屋直播|TinyEngine+AI:聚焦主航道,在实践中探索低代码技术黑土地

低代码引擎使能开发者定制低代码平台。它是低代码平台的底座&#xff0c;提供可视化搭建页面等基础能力&#xff0c;既可以通过线上搭配组合&#xff0c;也可以通过cli创建个人工程进行二次开发&#xff0c;实时定制出自己的低代码平台。适用于多场景的低代码平台开发&#xff…

GIS开发常用的开源地图数据框架有哪些?

学完Web前端开发&#xff0c;还需要掌握哪些内容&#xff1f;本篇文章再给大家主要讲讲针对WebGIS开发的地图和可视化数据库。 Echarts ECharts是一个使用 JavaScript 实现的开源可视化库。它可以流畅的运行在 PC 和移动设备上&#xff0c;兼容当前绝大部分浏览器&#xff08;…