转载请注明出处:http://blog.csdn.net/luotuo44/article/details/39290721
对于非阻塞IO的网络库来说,buffer几乎是必须的。Libevent在1.0版本之前就提供了buffer功能。现在来看一下Libevent的buffer。
buffer相关结构体:
Libevent为buffer定义了下面的结构体:
-
- struct evbuffer_chain;
- struct evbuffer {
- struct evbuffer_chain *first;
- struct evbuffer_chain *last;
-
-
-
-
- struct evbuffer_chain **last_with_datap;
-
- size_t total_len;
-
- ...
- };
-
-
- struct evbuffer_chain {
- struct evbuffer_chain *next;
- size_t buffer_len;
-
-
- ev_off_t misalign;
-
-
-
- size_t off;
-
- ...
-
- unsigned char *buffer;
- };
这两个结构体配合工作得到下图所示的存储结构:
![Libevent源码分析-----evbuffer结构与基本操作_第1张图片](http://img.e-com-net.com/image/info5/9d510e9d405f4fee805716489e387154.jpg)
因为last_with_datap成员比较特殊,上图只是展示了一种情况。后面还有一张图,展示另外一种情况。
Libevent将缓冲数据都存放到buffer中。通过一个个的evbuffer_chain连成的链表可以存放很多的缓冲数据。
这是一个很常见的链表形式。但Libevent有一个很独特的地方,就是那个evbuffer_chain结构体。
首先,该结构体有misalign成员。该成员表示错开不用的buffer空间。也就是说buffer中真正的数据是从buffer + misalign开始。
第二,evbuffer_chain结构体buffer是一个指针,按道理来说,应该单独调用malloc分配一个堆内存并让buffer指向之。但实际上buffer指向的内存和evbuffer_chain结构体本身的存储内存是一起分配的。下面代码展示了这一点:
-
- #define EVBUFFER_CHAIN_SIZE sizeof(struct evbuffer_chain)
-
- #if _EVENT_SIZEOF_VOID_P < 8
- #define MIN_BUFFER_SIZE 512
- #else
- #define MIN_BUFFER_SIZE 1024
- #endif
-
-
- #define EVBUFFER_CHAIN_EXTRA(t, c) (t *)((struct evbuffer_chain *)(c) + 1)
-
-
-
- static struct evbuffer_chain *
- evbuffer_chain_new(size_t size)
- {
- struct evbuffer_chain *chain;
- size_t to_alloc;
-
-
-
-
-
- size += EVBUFFER_CHAIN_SIZE;
-
-
- to_alloc = MIN_BUFFER_SIZE;
- while (to_alloc < size)
- to_alloc <<= 1;
-
-
- if ((chain = mm_malloc(to_alloc)) == NULL)
- return (NULL);
-
-
- memset(chain, 0, EVBUFFER_CHAIN_SIZE);
-
-
- chain->buffer_len = to_alloc - EVBUFFER_CHAIN_SIZE;
-
-
-
- chain->buffer = EVBUFFER_CHAIN_EXTRA(u_char, chain);
-
- return (chain);
- }
前面的图中,buffer内存区域(蓝色区域)连在next的后面也是基于这一点的。在代码的while循环中也可以看到申请的空间大小是512的倍数,也就是说evbuffer_chain申请的空间大小是512、1024、2048、4096……
上面贴出了函数evbuffer_chain_new,该函数是用来创建一个evbuffer_chain。现在贴出另外一个函数evbuffer_new,它是用来创建一个evbuffer的。
-
- struct evbuffer *
- evbuffer_new(void)
- {
- struct evbuffer *buffer;
-
- buffer = mm_calloc(1, sizeof(struct evbuffer));
- if (buffer == NULL)
- return (NULL);
-
- buffer->refcnt = 1;
- buffer->last_with_datap = &buffer->first;
-
- return (buffer);
- }
Buffer的数据操作:
在链表尾添加数据:
Libevent提供给用户的添加数据接口是evbuffer_add,现在就通过这个函数看一下是怎么将数据插入到buffer中的。该函数是在链表的尾部添加数据,如果想在链表的前面添加数据可以使用evbuffer_prepend。在链表尾部插入数据,分下面几种情况:
- 该链表为空,即这是第一次插入数据。这是最简单的,直接把新建的evbuffer_chain插入到链表中,通过调用evbuffer_chain_insert。
- 链表的最后一个节点(即evbuffer_chain)还有一些空余的空间,放得下本次要插入的数据。此时直接把数据追加到最后一个节点即可。
- 链表的最后一个节点并不能放得下本次要插入的数据,那么就需要把本次要插入的数据分开由两个evbuffer_chain存放。
具体的实现如下面所示:
-
- int
- evbuffer_add(struct evbuffer *buf, const void *data_in, size_t datlen)
- {
- struct evbuffer_chain *chain, *tmp;
- const unsigned char *data = data_in;
- size_t remain, to_alloc;
- int result = -1;
-
- EVBUFFER_LOCK(buf);
-
-
- if (buf->freeze_end) {
- goto done;
- }
-
-
- chain = buf->last;
-
-
- if (chain == NULL) {
- chain = evbuffer_chain_new(datlen);
- if (!chain)
- goto done;
- evbuffer_chain_insert(buf, chain);
- }
-
-
- if ((chain->flags & EVBUFFER_IMMUTABLE) == 0) {
-
- remain = (size_t)(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;
- goto out;
- } else if (!CHAIN_PINNED(chain) &&
- evbuffer_chain_should_realign(chain, datlen)) {
-
-
-
- evbuffer_chain_align(chain);
-
- memcpy(chain->buffer + chain->off, data, datlen);
- chain->off += datlen;
- buf->total_len += datlen;
- goto out;
- }
- } else {
- remain = 0;
- }
-
-
-
-
- to_alloc = chain->buffer_len;
-
-
- if (to_alloc <= EVBUFFER_CHAIN_MAX_AUTO_SIZE/2)
- to_alloc <<= 1;
-
-
-
- if (datlen > to_alloc)
- to_alloc = datlen;
-
-
- tmp = evbuffer_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;
-
- evbuffer_chain_insert(buf, tmp);
- buf->n_add_for_cb += datlen;
-
- out:
- evbuffer_invoke_callbacks(buf);
- result = 0;
- done:
- EVBUFFER_UNLOCK(buf);
- return result;
- }
可以看到,evbuffer_add函数是复制一份数据,保存在链表中。这样做的好处是,用户调用该函数后,就可以丢弃该数据。读者比较熟知的函数bufferevent_write就是直接调用这个函数。当用户调用bufferevent_write后,就可以马上把数据丢弃,无需等到Libevent把这份数据写到socket的缓存区中。
前面的代码是把数据存放到evbuffer_chain中,至于怎么把evbuffer_chain插入到链表中,则是由函数evbuffer_chain_insert完成。
-
- static void
- evbuffer_chain_insert(struct evbuffer *buf,
- struct evbuffer_chain *chain)
- {
-
-
-
- if (*buf->last_with_datap == NULL) {
- buf->first = buf->last = chain;
- } else {
- struct evbuffer_chain **ch = buf->last_with_datap;
-
-
-
-
-
- while ((*ch) && ((*ch)->off != 0 || CHAIN_PINNED(*ch)))
- ch = &(*ch)->next;
-
-
- if (*ch == NULL) {
-
-
- buf->last->next = chain;
-
- if (chain->off)
- buf->last_with_datap = &buf->last->next;
- } else {
-
-
- EVUTIL_ASSERT(evbuffer_chains_all_empty(*ch));
-
- evbuffer_free_all_chains(*ch);
-
-
- *ch = chain;
- }
- buf->last = chain;
- }
- buf->total_len += chain->off;
- }
-
- static void
- evbuffer_free_all_chains(struct evbuffer_chain *chain)
- {
- struct evbuffer_chain *next;
- for (; chain; chain = next) {
- next = chain->next;
- evbuffer_chain_free(chain);
- }
- }
-
-
- static inline void
- evbuffer_chain_free(struct evbuffer_chain *chain)
- {
- ...
- mm_free(chain);
- }
可以看到,evbuffer_chain_insert的插入并不是已经一个简单的链表插入,还要检测链表里面是否有没有数据(off为0)的节点。但这个buffer链表里面会有这样的节点吗?其实是有这样节点,这种节点一般是用于预留空间的。预留空间这个概念在STL中是很常见的,它的主要作用是使得当下次添加数据时,无需额外申请空间就能保存数据。
预留buffer空间:
其中一个扩大预留空间的函数是evbuffer_expand。在讲evbuffer_expand前,看一下如果存在没有数据(off为0)的节点,链表又会是怎么样的。这涉及到last_with_data指针的指向,如下图所示:
好了,现在来说一下evbuffer_expand。
-
- int
- evbuffer_expand(struct evbuffer *buf, size_t datlen)
- {
- struct evbuffer_chain *chain;
-
- EVBUFFER_LOCK(buf);
- chain = evbuffer_expand_singlechain(buf, datlen);
- EVBUFFER_UNLOCK(buf);
- return chain ? 0 : -1;
- }
该函数的作用是扩大链表的buffer空间,使得下次add一个长度为datlen的数据时,无需动态申请内存。
由于确保的是无需动态申请内存,所以假如这个链表本身还有大于datlen的空闲空间,那么这个evbuffer_expand函数将不做任何操作。
如果这个链表的所有buffer空间都被用完了,那么解决需要创建一个buffer为datlen的evbuffer_chain,然后把这个evbuffer_chain插入到链表最后面即可。此时这个evbuffer_chain的off就等于0了,也就出现了前面说的的那个问题。
如果链表的最后一个有数据chain还有一些空闲空间,但小于datlen。那么就有点麻烦。evbuffer_expand 是调用evbuffer_expand_singlechain实现扩大空间的。而evbuffer_expand_singlechain函数有一个特点,预留空间datlen必须是在一个evbuffer_chain中,不能跨chain。该函数的返回值就指明了哪个chain预留了datlen空间。不能跨chain也就导致了一些麻烦事。
由于不能跨chain,但最后一个chain确实又还有一些空闲空间。前面的evbuffer_add函数会把链表的所有节点的buffer都填得满满的。这说明所有节点的buffer还是用完的好,比较统一。要明确的是,此种情况下,肯定是要新建一个evbuffer_chain插入到后面。
Libevent还是想把所有节点的buffer都填满。如果最后一个chain的数据比较少,那么就直接不要那个chain。当然chain上的数据还是要的。Libevent新建一个比datlen更大的chain,把最后一个chain上的数据迁移到这个新建的chain上。这样就既能保证该chain节点也能填满,也保证了预留空间datlen必须在是一个chain的。如果最后一个chain的数据比较多,Libevent就认为迁移不划算,那么Libevent就让这个chain最后留有一些空间不使用。
下面是该函数的代码展示了上面所说的:
上面代码中evbuffer_expand_singlechain函数的第一个if语句,可以联合前面的两张图一起看,更容易看懂。
evbuffer_expand_singlechain函数是要求一个节点就能提供大小为datlen的可用空间。其实Libevent还提供了_evbuffer_expand_fast函数,该函数还有一个整型的参数n,用来表示使用不超过n个节点的前提下,提供datlen的可用空间。不过这个函数只留给Libevent内部使用,用户不能使用之。
在链表头添加数据:
前面的evbuffer_add是在链表尾部追加数据,Libevent提供了另外一个函数evbuffer_prepend可以在链表头部添加数据。在这个函数里面可以看到evbuffer_chain结构体成员misalign的一些使用,也能知道为什么会有这个成员。
evbuffer_prepend函数并不复杂,只需弄懂misalign的作用就很容易明白该函数的实现。考虑这种情况:要在链表头插入数据,那么应该new一个新的evbuffer_chain,然后把要插入的数据放到这个新建个的evbuffer_chain中。但evbuffer_chain_new申请到的buffer空间可能会大于要插入的数据长度。插入数据后,buffer就必然会剩下一些空闲空间。那么这个空闲空间放在buffer的前面好还是后面好呢?Libevent认为放在前面会好些,此时misalign就有用了。它表示错开不用的空间,也就是空闲空间。如果再次在链表头插入数据,就可以使用到这些空闲空间了。所以,misalign也可以认为是空闲空间,可以随时使用。
-
- int
- evbuffer_prepend(struct evbuffer *buf, const void *data, size_t datlen)
- {
- struct evbuffer_chain *chain, *tmp;
- int result = -1;
-
- EVBUFFER_LOCK(buf);
-
-
- if (buf->freeze_start) {
- goto done;
- }
-
- chain = buf->first;
-
-
- if (chain == NULL) {
- chain = evbuffer_chain_new(datlen);
- if (!chain)
- goto done;
- evbuffer_chain_insert(buf, chain);
- }
-
- if ((chain->flags & EVBUFFER_IMMUTABLE) == 0) {
-
-
- if (chain->off == 0)
- chain->misalign = chain->buffer_len;
-
-
-
-
-
- if ((size_t)chain->misalign >= datlen) {
- memcpy(chain->buffer + chain->misalign - datlen,
- data, datlen);
- chain->off += datlen;
- chain->misalign -= datlen;
- buf->total_len += datlen;
- buf->n_add_for_cb += datlen;
- goto out;
- } else if (chain->misalign) {
- memcpy(chain->buffer,
- (char*)data + datlen - chain->misalign,
- (size_t)chain->misalign);
- chain->off += (size_t)chain->misalign;
- buf->total_len += (size_t)chain->misalign;
- buf->n_add_for_cb += (size_t)chain->misalign;
- datlen -= (size_t)chain->misalign;
- chain->misalign = 0;
- }
- }
-
-
-
- if ((tmp = evbuffer_chain_new(datlen)) == NULL)
- goto done;
- buf->first = tmp;
- if (buf->last_with_datap == &buf->first)
- buf->last_with_datap = &tmp->next;
-
- tmp->next = chain;
-
- tmp->off = datlen;
- tmp->misalign = tmp->buffer_len - datlen;
-
- memcpy(tmp->buffer + tmp->misalign, data, datlen);
- buf->total_len += datlen;
- buf->n_add_for_cb += (size_t)chain->misalign;
-
- out:
- evbuffer_invoke_callbacks(buf);
- result = 0;
- done:
- EVBUFFER_UNLOCK(buf);
- return result;
- }
读取数据:
现在来看一下怎么从evbuffer中复制一些数据。Libevent提供了函数evbuffer_copyout用来复制evbuffer的数据。当然是从链表的前面开始复制。
-
- ev_ssize_t
- evbuffer_copyout(struct evbuffer *buf, void *data_out, size_t datlen)
- {
- struct evbuffer_chain *chain;
- char *data = data_out;
- size_t nread;
- ev_ssize_t result = 0;
-
- EVBUFFER_LOCK(buf);
-
- chain = buf->first;
-
- if (datlen >= buf->total_len)
- datlen = buf->total_len;
-
- if (datlen == 0)
- goto done;
-
-
- if (buf->freeze_start) {
- result = -1;
- goto done;
- }
-
- nread = datlen;
- while (datlen && datlen >= chain->off) {
- memcpy(data, chain->buffer + chain->misalign, chain->off);
- data += chain->off;
- datlen -= chain->off;
-
- chain = chain->next;
- }
-
- if (datlen) {
- memcpy(data, chain->buffer + chain->misalign, datlen);
- }
-
- result = nread;
- done:
- EVBUFFER_UNLOCK(buf);
- return result;
- }
这个函数逻辑比较简单,这里就不多讲了。
有时我们不仅仅想复制数据,还想删除数据,或者是复制后就删除数据。这些操作在socket编程中还是很常见的。
-
- int
- evbuffer_drain(struct evbuffer *buf, size_t len)
- {
- struct evbuffer_chain *chain, *next;
- size_t remaining, old_len;
- int result = 0;
-
- EVBUFFER_LOCK(buf);
- old_len = buf->total_len;
-
- if (old_len == 0)
- goto done;
-
-
- if (buf->freeze_start) {
- result = -1;
- goto done;
- }
-
-
- if (len >= old_len && !HAS_PINNED_R(buf)) {
- len = old_len;
- for (chain = buf->first; chain != NULL; chain = next) {
- next = chain->next;
- evbuffer_chain_free(chain);
- }
-
- ZERO_CHAIN(buf);
- } else {
- if (len >= old_len)
- len = old_len;
-
- 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;
-
-
- if (CHAIN_PINNED_R(chain)) {
- EVUTIL_ASSERT(remaining == 0);
- chain->misalign += chain->off;
- chain->off = 0;
- break;
- } else
- evbuffer_chain_free(chain);
- }
-
- buf->first = chain;
- if (chain) {
- chain->misalign += remaining;
- chain->off -= remaining;
- }
- }
-
- evbuffer_invoke_callbacks(buf);
- done:
- EVBUFFER_UNLOCK(buf);
- return result;
- }
-
-
- int
- evbuffer_remove(struct evbuffer *buf, void *data_out, size_t datlen)
- {
- ev_ssize_t n;
- EVBUFFER_LOCK(buf);
- n = evbuffer_copyout(buf, data_out, datlen);
- if (n > 0) {
- if (evbuffer_drain(buf, n)<0)
- n = -1;
- }
- EVBUFFER_UNLOCK(buf);
- return (int)n;
- }
可以看到evbuffer_remove是先复制数据,然后才删除evbuffer的数据。而evbuffer_drain则直接删除evbuffer的数据,而不会复制。