用户态缓存:链式缓冲区(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.xdnf.cn/news/146383.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

vue2+elementUI实现handleSelectionChange批量删除-前后端

功能需求&#xff1a;实现选中一个或多个执行批量删除操作 在elementUI官网选择一个表格样式模板&#xff0c;Element - The worlds most popular Vue UI framework 这里采用的是 将代码复制到前端&#xff0c;这里是index.vue <template><el-button type"dang…

人工智能有助于解决 IT/OT 集成安全挑战

思科的一项研究表明&#xff0c;信息技术 (IT) 和运营技术 (OT) 融合所带来的安全问题可以通过人工智能 (AI) 解决&#xff0c;尽管该技术也可能被恶意行为者利用。 该报告由思科和 Sapio Research 联合发布&#xff0c;对 17 个国家的 1,000 名行业专业人士进行了调查&#x…

如何在Java中实现用户列表的下载功能

在现代的Web应用中&#xff0c;用户管理是一个常见的需求。用户可能需要查看和下载他们的个人信息或者用户列表。本文将介绍如何使用Java和Spring框架实现用户列表的下载功能&#xff0c;具体采用Excel格式。 一、项目准备 首先&#xff0c;确保你的项目中已经引入了Spring B…

PAT甲级-1086 Tree Traversals Again

题目 题目大意 题目给出二叉树的节点个数&#xff0c;并给出用栈遍历树的过程。要求输出树的后序遍历&#xff0c;不能有多余空格。 思路 可以看出&#xff0c;栈遍历输出的是树的中序遍历&#xff0c;而依次push进栈的是先序遍历的顺序。题目要求后序&#xff0c;即已知先序…

MySQL缓冲池详解

Buffer Pool 本文参考开源项目&#xff1a;小林coding在线文档&#xff1b; 01-缓冲池概述 ​ 在MySQL查询数据的时候&#xff0c;是通过存储引擎去磁盘做IO来获取数据库中的数据&#xff0c;这样每次查询一条数据都要去做一次或者多次磁盘的IO&#xff0c;无疑是非常慢的。…

mxnet系统架构

mxnet系统架构 MXNet 是一个高性能、灵活的深度学习框架&#xff0c;最早由李沐&#xff08;Mu Li&#xff09;等人开发&#xff0c;并且得到了 Amazon 的支持。它支持多种语言&#xff08;包括 Python、Scala、C、R、Julia、Perl 等&#xff09;&#xff0c;并以其灵活的编程…

EfficientNet V1 V2

v1 论文地址&#xff1a;https://arxiv.org/abs/1905.11946 网络深度、宽度和图像分辨率&#xff0c;进行了栅格搜索&#xff08;Grid Search&#xff09;&#xff0c;找到了最优的几种搭配。 v2 论文地址&#xff1a;https://arxiv.org/abs/2104.00298 Fused-MBConv 到搜…

SpringBoot教程(三十) | SpringBoot集成Shiro权限框架(shiro-spring 方式)

SpringBoot教程&#xff08;三十&#xff09; | SpringBoot集成Shiro权限框架&#xff08;shiro-spring方式&#xff09; 一、 什么是Shiro二、Shiro 组件核心组件其他组件 三、流程说明shiro的运行流程 四、SpringBoot 集成 Shiro1. 添加 Shiro 相关 maven2. 添加 其他 maven3…

6种解决msvcp140_ATOMIC_WAIT.dll丢失的方法分享

日常生活工作中&#xff0c;电脑已经成为我们生活和工作中不可或缺的工具。然而&#xff0c;在使用过程中&#xff0c;我们也会遇到各种问题&#xff0c;其中之一就是电脑中的msvcp140_ATOMIC_WAIT.dll文件丢失。这个问题可能会导致电脑运行不稳定&#xff0c;甚至无法正常启动…

7-51 7-52 两个有序链表序列并集 和 交集

7-51代码&#xff1a;&#xff08;map) #include<iostream> #include<map> using namespace std; map<int,int>mp; int cnt,cnttp; void scan(){while(1){int x; scanf("%d",&x);if(x-1) break;mp[x];cnt;} } int main(){scan();scan();if(!…

基于波特图的控制系统设计算法

波特图&#xff08;Bode Plot&#xff09;是一种用于描述线性控制系统频率响应的图形表示方法&#xff0c;通常用于分析和设计控制系统。它以控制系统的传递函数&#xff08;或频域传递函数&#xff09;为基础&#xff0c;将系统的幅频特性&#xff08;振幅-频率响应&#xff0…

【MySQL】 索引

MySQL与磁盘存储 MySQL就是提供数据存储服务的&#xff0c;而最终存储的位置就是磁盘&#xff0c;但是磁盘存储速度慢&#xff0c;所以MySQL如何与磁盘交互&#xff0c;提高数据存储效率&#xff0c;即是MySQL和磁盘交互。 磁盘基础知识回顾 物理结构 磁道&#xff1a;磁盘是…

ARM 栈和函数调用

阅读本文前&#xff0c;可以先阅读下述文档&#xff0c;对函数栈、栈帧等的概念会有所了解&#xff0c;会对本文章的理解大有益处 X86_64 栈和函数调用 1、调试环境 Ubuntu&#xff1a; liangjieliangjie-virtual-machine:~/Desktop$ cat /proc/version Linux version 6.5.0…

c++9月20日

1.思维导图 2.顺序表 头文件 #ifndef RECTANGLE_H #define RECTANGLE_H#include <iostream>using namespace std;using datatype int ;//类型重定义class Seqlist { private://私有权限datatype *ptr; //指向堆区申请空间的起始地址int size;//堆区空间的长度int len …

leetcode第十二题:整数转罗马数字

七个不同的符号代表罗马数字&#xff0c;其值如下&#xff1a; 符号值I1V5X10L50C100D500M1000 罗马数字是通过添加从最高到最低的小数位值的转换而形成的。将小数位值转换为罗马数字有以下规则&#xff1a; 如果该值不是以 4 或 9 开头&#xff0c;请选择可以从输入中减去的…

利用LRZ压缩与Base64编码实现高效文件上传

引言 在当今互联网时代&#xff0c;文件上传已成为众多在线服务不可或缺的一部分&#xff0c;尤其是在社交媒体平台上的照片分享和云存储服务中的文档管理等场景&#xff0c;高效且安全的文件上传机制对于保障用户体验至关重要。 为此&#xff0c;本文将介绍一种结合了LRZ压缩…

使用vite+react+ts+Ant Design开发后台管理项目(三)

前言 本文将引导开发者从零基础开始&#xff0c;运用、react、react-router、react-redux、Ant Design、less、tailwindcss、axios等前沿技术栈&#xff0c;构建一个高效、响应式的后台管理系统。通过详细的步骤和实践指导&#xff0c;文章旨在为开发者揭示如何利用这些技术工具…

VSCode环境下连接 MySQL 8.0 数据库 (C++)

前言 时隔了不知道多久&#xff0c;笔者需要在Windows环境下通过VSCode重新搭建一个简单的数据库连接的Cpp工程。由于VSCode和MySQL的版本和之前连通时发生了一些变化&#xff0c;无需用MySQL Connector&#xff0c;环境配置的细节和之前也不尽相同&#xff0c;因此笔者找了一…

简单有效关于msvcp140.dll丢失的解决方法,msvcp140.dll修复的方法原理及步骤

这篇文章将和大家分享几种msvcp140.dll丢失的解决方法&#xff0c;分析解决方法为什么能够通过这种方法进行修复成功&#xff0c;有效的将丢失的msvcp140.dll文件进行修复完成。 msvcp140.dll丢失&#xff1f;简单有效的解决途径 一、重新安装相关软件 原理 许多应用程序在安…

掌握Android开发新趋势:Jetpack与现代架构技术全解析

随着Android开发技术的不断进步&#xff0c;Jetpack和现代架构技术已成为构建高效、可维护应用的关键。本文将为您介绍一套全面的学习资料&#xff0c;包括大纲、PDF文档、源代码以及学习视频&#xff0c;帮助您深入理解Jetpack核心库、架构组件以及现代开发工具。 内容&#…