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

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

相关文章

C~排序算法

在C/C中&#xff0c;有多种排序算法可供选择&#xff0c;每种算法都有其特定的应用场景和特点。下面介绍几种常用的排序算法&#xff0c;包括冒泡排序、选择排序、插入排序、快速排序、归并排序和堆排序&#xff0c;并给出相应的示例代码和解释。 冒泡排序&#xff08;Bubble …

LeetCode142. 环形链表 II(2024秋季每日一题 28)

给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数…

Android studio配置AVD虚拟机

目录 设置虚拟设备参数 安装HAXM 找到HAXM安装包 安装 启动虚拟设备 设置虚拟设备参数 Tools->Devices Manager->Add a new divece一个加号符号的图标->Create Virtual Device 选择尺寸参数&#xff0c;没有合适的话选择New Hardware Profile&#xff0c;调整好…

【深度学习】【TensorRT】【C++】模型转化、环境搭建以及模型部署的详细教程

【深度学习】【TensorRT】【C】模型转化、环境搭建以及模型部署的详细教程 提示:博主取舍了很多大佬的博文并亲测有效,分享笔记邀大家共同学习讨论 文章目录 【深度学习】【TensorRT】【C】模型转化、环境搭建以及模型部署的详细教程前言模型转换--pytorch转engineWindows平台搭…

中国空间计算产业链发展分析

2024中国空间计算产业链拆解 空间计算设备主要包括AR、VR、MR等终端设备。VR设备通常包括头戴式显示器&#xff08;VR头盔&#xff09;、手柄或追踪器等组件&#xff0c;用以完全封闭用户视野&#xff0c;营造虚拟环境体验。这些设备配备高分辨率显示屏、内置传感器和跟踪器。 …

【Spring】Spring Aop基础入门

一、AOP(Aspect-Oriented Programming: 面向切面编程) 将那些与业务无关&#xff0c;却为业务模块所共同调用的逻辑&#xff08;例如事务处理、日志管理、权限控制等&#xff09;封装抽取成一个可重用的模块&#xff0c;这个模块被命名为“切面”&#xff08;Aspect&#xff09…

怎么备考2024年11月软考高级系统架构师 ?

分享下我的系统架构设计师考证之路&#xff0c;希望能对即将参加考试的小伙伴们带来一些启示和帮助。 先贴出自己软考系统架构设计师成绩&#xff0c;备考一次就通过了考试。 一、架构考试教材 架构考试教材目前使用的是系统架构设计师教程&#xff08;第2版&#xff09;&…

将数字化转型理论应用于实践的路径:企业数字化转型的落地方案

将数字化转型理论应用于实践的路径 随着数字化技术的飞速发展&#xff0c;越来越多的企业开始意识到&#xff0c;成功的数字化转型不仅仅是引入新技术&#xff0c;更是如何将理论与实践相结合&#xff0c;实现企业业务流程的根本性变革。数字化转型不仅意味着技术上的革新&…

宝塔面板部署雷池社区版教程

宝塔面板部署雷池社区版教程 简单介绍一下宝塔面板&#xff0c;安全高效的服务器运维面板&#xff0c;使用宝塔面板的人非常多 在网站管理上&#xff0c;许多用户都是通过宝塔面板进行管理&#xff0c;宝塔面板的Nginx默认监听端口为80和443&#xff0c;这就导致共存部署时雷池…

《面向对象是怎样工作的》笔记

6、1、在面向对象的世界中&#xff0c;我们需要事先为所有的行动准备好方法并通过消息传递来调用方法&#xff0c;这样事物才会开始运作。 2、实际上&#xff0c;类、继承和多态应该被明确定义为能提高软件的可维护性和可重用行的结构。类将变量和子程序汇总在一起&#xff0c…

单片机的两种看门狗原理解析——IWDG和WWDG

一、IWDG独立开门狗的主要性能 计时机制&#xff1a; 递减计数器 独立开门狗的初始频率&#xff1a; LSI低速内部时钟&#xff1a;RC震荡器&#xff0c;40kHz 独立开门狗是以LSI为初始频率的&#xff0c;所以独立开门狗的初始时钟频率取决与单片机本身&#xff0c;因此在使…

AES CCM详解

AES CCM是一种对数据进行加密及完整性检查的算法&#xff0c;主要用到AES中的CBC(完整性检查)和CTR(对明文进行加密)&#xff0c;除此之外&#xff0c;还涉及到对数据的格式化(本文着重阐述)。 文章目录 加密过程STEPS 解密及校验过程STEPS 格式化B0的构成B0解析举例AAD的格式化…

David律所代理Beau Parsons的小狗插画图案版权维权,速排查下架

案件基本情况&#xff1a;起诉时间&#xff1a;2024-9-16案件号&#xff1a;2024-cv-08505原告&#xff1a;Beau Parsons原告律所&#xff1a;David起诉地&#xff1a;伊利诺伊州北部法院涉案商标/版权&#xff1a;原告品牌简介&#xff1a;Beau Parsons是一位来自澳大利亚的专…

C++模拟实现list:list、list类的初始化和尾插、list的迭代器的基本实现、list的完整实现、测试、整个list类等的介绍

文章目录 前言一、list二、list类的初始化和尾插三、list的迭代器的基本实现四、list的完整实现五、测试六、整个list类总结 前言 C模拟实现list&#xff1a;list、list类的初始化和尾插、list的迭代器的基本实现、list的完整实现、测试、整个list类等的介绍 一、list list本…

[Linux]从零开始的Minecraft服务器搭建教程

一、前言 学习Linux有一段时间了&#xff0c;当然&#xff0c;我们要把学习的知识运用到实际生活中去。最近朋友们都在玩我的世界&#xff0c;网易版的我的世界联机非常不稳定&#xff0c;用起来也算是非常难受了。所以还是准备转战JAVA版。为了联机&#xff0c;可以考虑一个人…

计算机网络 --- Socket 编程

序言 在上一篇文章中&#xff0c;我们介绍了 协议&#xff0c;协议就是一种约定&#xff0c;规范了双方通信需要遵循的规则、格式和流程&#xff0c;以确保信息能够被准确地传递、接收和理解。  在这篇文章中我们将介绍怎么进行跨网络数据传输&#xff0c;在这一过程中相信大家…

CSS调整背景

一、设置背景颜色 通过 background-color 属性指定&#xff0c;值可以是十六进制 #ffffff&#xff0c;也可以是rgb(0, 255, 255)&#xff0c;或是颜色名称 "red" div {background-color: red; /* 通过颜色名称设置 */background-color: #ff0000; /* 通过十六进制设…

图像分割(九)—— Mask Transfiner for High-Quality Instance Segmentation

Mask Transfiner for High-Quality Instance Segmentation Abstract1. Intrudouction3. Mask Transfiner3.1. Incoherent Regions3.2. Quadtree for Mask RefinementDetection of Incoherent Regions四叉树的定义与构建四叉树的细化四叉树的传播 3.3. Mask Transfiner Architec…

GreenPlum与PostgreSQL数据库

*** Greenplum*** 是一款开源数据仓库。基于开源的PostgreSQL改造&#xff0c;主要用来处理大规模数据分析任务&#xff0c;相比Hadoop&#xff0c;Greenplum更适合做大数据的存储、计算和分析引擎 它本质上是多个PostgreSQL面向磁盘的数据库实例一起工作形成的一个紧密结合的数…

【数据结构中的哈希】

泛黄的春联还残留在墙上.......................................................................................................... 文章目录 前言 一、【哈希结构的介绍】 1.1【哈希结构的概念】 1.2【哈希冲突】 1.3【哈希函数的设计】 1.4【应对哈希冲突的办法】 一、…