土法炼钢兴趣小组的算法知识备份

Evbuffer 内存管理

目录

Evbuffer Structure

在网络编程中,缓冲区的管理是重中之重。如何高效地拼接数据、处理粘包、避免频繁的内存分配与拷贝?Libevent 给出的答案是 evbuffer

1. 设计理念:链式存储

传统的缓冲区往往是一个连续的字节数组(如 char buf[4096])。这种设计在处理变长数据时很尴尬: * 数据太少:浪费内存。 * 数据太多:需要 realloc 并移动数据,开销巨大。 * 头部移除:需要 memmove 将剩余数据前移,O(N) 复杂度。

evbuffer 采用了 链表 (Linked List) 的设计。它由多个 evbuffer_chain 组成,每个 chain 是一块独立的内存块。

1.1. 结构体定义

struct evbuffer {
    struct evbuffer_chain *first; // 链表头
    struct evbuffer_chain *last;  // 链表尾
    struct evbuffer_chain **last_with_datap; // 指向最后一个有数据的 chain 的指针
    size_t total_len;             // 总字节数
    // ... 其他回调与锁
};

struct evbuffer_chain {
    struct evbuffer_chain *next;
    size_t buffer_len; // buffer 的总容量
    size_t misalign;   // 有效数据前的空闲空间 (头部偏移)
    size_t off;        // 有效数据的长度
    unsigned char *buffer; // 实际数据指针
};

1.2. 优势

2. 常用操作

2.1. 添加数据 (evbuffer_add)

evbuffer_add(buf, "Hello", 5);

Libevent 会检查最后一个 chain 是否有足够空间。如果有,直接拷贝;如果没有,分配一个新的 chain。

2.2. 移除数据 (evbuffer_drain)

evbuffer_drain(buf, 5);

从头部丢弃 5 字节。这通常只是指针的移动,非常快。

2.3. 线性化 (evbuffer_pullup)

有时候我们需要一段连续的内存(例如解析定长包头)。

unsigned char *data = evbuffer_pullup(buf, 8);

这会强制将前 8 个字节的数据拷贝到一个连续的内存块中。如果数据跨越了多个 chain,这里会发生内存拷贝(Copy)。慎用,尤其是在高性能场景下。

3. 零拷贝优化 (Zero-copy)

evbuffer 支持多种零拷贝技术,这是它高性能的关键。

3.1. 引用模式 (evbuffer_add_reference)

如果你有一块只读的静态数据(比如 HTTP 响应的 HTML 模板),你可以直接将指针传给 evbuffer,而不进行拷贝。

const char *static_data = "<html>...</html>";
evbuffer_add_reference(buf, static_data, len, NULL, NULL);

Libevent 会创建一个特殊的 chain,它不拥有内存,只是指向 static_data

3.2. Sendfile 集成 (evbuffer_add_file)

这是文件服务器的神器。

int fd = open("big_file.iso", O_RDONLY);
evbuffer_add_file(out_buf, fd, 0, file_size);

当这个 buffer 被写入 socket 时,Libevent 底层会尝试使用 sendfile (Linux) 或 TransmitFile (Windows),直接在内核态将文件内容发送到网卡,完全绕过用户态内存,实现极致性能。

4. 内存布局图解

[evbuffer]
   |
   v
[Chain A] -> [Chain B] -> [Chain C] -> NULL
| misalign |   off    |   free   |
|----------|----------|----------|
|  (waste) | "Hello " |  (space) |

misalign 很大时,Libevent 可能会在适当的时候通过 memmove 将数据前移,回收空间(通常发生在需要腾出空间写入时)。

5. 总结

evbuffer 是 Libevent 中最被低估的组件。它不仅仅是一个字节数组,更是一个智能的内存管理器。 * 它解决了碎片化和频繁扩容的问题。 * 它通过链表实现了高效的头部移除。 * 它通过 add_fileadd_reference 实现了零拷贝。

在后续的 bufferevent 章节中,你会看到 evbuffer 是如何作为输入/输出缓冲区,支撑起整个异步 I/O 流程的。


上一篇: 01-core/struct-event.md - 事件结构体详解 下一篇: 02-data/bufferevent.md - Bufferevent 原理与实战

返回 Libevent 专题索引


By .