Libevent源码分析-----evbuffer结构与基本操作
2016-08-23 17:11
411 查看
出处:http://blog.csdn.net/luotuo44/article/details/39290721
对于非阻塞IO的网络库来说,buffer几乎是必须的。Libevent在1.0版本之前就提供了buffer功能。现在来看一下Libevent的buffer。
[cpp] view
plain copy
//evbuffer-internal.h文件
struct evbuffer_chain;
struct evbuffer {
struct evbuffer_chain *first;
struct evbuffer_chain *last;
//这是一个二级指针。使用*last_with_datap时,指向的是链表中最后一个有数据的evbuffer_chain。
//所以last_with_datap存储的是倒数第二个evbuffer_chain的next成员地址。
//一开始buffer->last_with_datap = &buffer->first;此时first为NULL。所以当链表没有节点时
//*last_with_datap为NULL。当只有一个节点时*last_with_datap就是first。
struct evbuffer_chain **last_with_datap;
size_t total_len;//链表中所有chain的总字节数
...
};
struct evbuffer_chain {
struct evbuffer_chain *next;
size_t buffer_len;//buffer的大小
//错开不使用的空间。该成员的值一般等于0
ev_off_t misalign;
//evbuffer_chain已存数据的字节数
//所以要从buffer + misalign + off的位置开始写入数据
size_t off;
...
unsigned char *buffer;
};
这两个结构体配合工作得到下图所示的存储结构:
因为last_with_datap成员比较特殊,上图只是展示了一种情况。后面还有一张图,展示另外一种情况。
Libevent将缓冲数据都存放到buffer中。通过一个个的evbuffer_chain连成的链表可以存放很多的缓冲数据。
这是一个很常见的链表形式。但Libevent有一个很独特的地方,就是那个evbuffer_chain结构体。
首先,该结构体有misalign成员。该成员表示错开不用的buffer空间。也就是说buffer中真正的数据是从buffer + misalign开始。
第二,evbuffer_chain结构体buffer是一个指针,按道理来说,应该单独调用malloc分配一个堆内存并让buffer指向之。但实际上buffer指向的内存和evbuffer_chain结构体本身的存储内存是一起分配的。下面代码展示了这一点:
[cpp] view
plain copy
//evbuffer-internal.h文件
#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
//宏的作用就是返回,chain + sizeof(evbuffer_chain) 的内存地址。
#define EVBUFFER_CHAIN_EXTRA(t, c) (t *)((struct evbuffer_chain *)(c) + 1)
//buffer.c文件
static struct evbuffer_chain *
evbuffer_chain_new(size_t size)//size是buffer所需的大小
{
struct evbuffer_chain *chain;
size_t to_alloc;
//所需的大小size 再 加上evbuffer_chain结构体本身所需
//的内存大小。这样做的原因是,evbuffer_chain本身是管理
//buffer的结构体。但buffer内存就分配在evbuffer_chain结构体存储
//内存的后面。所以要申请多一些内存。
size += EVBUFFER_CHAIN_SIZE;//evbuffer_chain结构体本身的大小
to_alloc = MIN_BUFFER_SIZE; //内存块的最小值
while (to_alloc < size)
to_alloc <<= 1;
//从分配的内存大小可以知道,evbuffer_chain结构体和buffer是一起分配的
//也就是说他们是存放在同一块内存中
if ((chain = mm_malloc(to_alloc)) == NULL)
return (NULL);
//只需初始化最前面的结构体部分即可
memset(chain, 0, EVBUFFER_CHAIN_SIZE);
//buffer_len存储的是buffer的大小
chain->buffer_len = to_alloc - EVBUFFER_CHAIN_SIZE;
//宏的作用就是返回,chain + sizeof(evbuffer_chain) 的内存地址。
//其效果就是buffer指向的内存刚好是在evbuffer_chain的后面。
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的。
[cpp] view
plain copy
//buffer.c
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);
}
该链表为空,即这是第一次插入数据。这是最简单的,直接把新建的evbuffer_chain插入到链表中,通过调用evbuffer_chain_insert。
链表的最后一个节点(即evbuffer_chain)还有一些空余的空间,放得下本次要插入的数据。此时直接把数据追加到最后一个节点即可。
链表的最后一个节点并不能放得下本次要插入的数据,那么就需要把本次要插入的数据分开由两个evbuffer_chain存放。
具体的实现如下面所示:
[cpp] view
plain copy
//buffer.c文件
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;
}
//找到最后一个evbuffer_chain。
chain = buf->last;
//第一次插入数据时,buf->last为NULL
if (chain == NULL) {
chain = evbuffer_chain_new(datlen);
if (!chain)
goto done;
evbuffer_chain_insert(buf, chain);
}
//EVBUFFER_IMMUTABLE 是 read-only chain
if ((chain->flags & EVBUFFER_IMMUTABLE) == 0) {//等于0说明是可以写的
//最后那个chain可以放的字节数
remain = (size_t)(chain->buffer_len - chain->misalign - chain->off);
if (remain >= datlen) {//最后那个chain可以放下本次要插入的数据
memcpy(chain->buffer + chain->misalign + chain->off,
data, datlen);
chain->off += datlen;//偏移量,方便下次插入数据
buf->total_len += datlen;//buffer的总字节数
goto out;
} else if (!CHAIN_PINNED(chain) &&//该evbuffer_chain可以修改
evbuffer_chain_should_realign(chain, datlen)) {
//通过调整后,也可以放得下本次要插入的数据
//通过使用chain->misalign这个错位空间而插入数据
evbuffer_chain_align(chain);
memcpy(chain->buffer + chain->off, data, datlen);
chain->off += datlen;
buf->total_len += datlen;
goto out;
}
} else {
remain = 0; //最后一个节点是只写evbuffer_chain
}
//当这个evbuffer_chain是一个read-only buffer或者最后那个chain
//放不下本次要插入的数据时才会执行下面代码
//此时需要新建一个evbuffer_chain
to_alloc = chain->buffer_len;
//当最后evbuffer_chain的缓冲区小于等于2048时,那么新建的evbuffer_chain的
//大小将是最后一个节点缓冲区的2倍。
if (to_alloc <= EVBUFFER_CHAIN_MAX_AUTO_SIZE/2)//4096/2
to_alloc <<= 1;
//最后的大小还是有要插入的数据决定。要注意的是虽然to_alloc最后的值可能为
//datlen。但在evbuffer_chain_new中,实际分配的内存大小必然是512的倍数。
if (datlen > to_alloc)
to_alloc = datlen;
//此时需要new一个chain才能保存本次要插入的数据
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;
//把要插入的数据复制到新建一个chain中。
memcpy(tmp->buffer, data, datlen);
tmp->off = datlen;
//将这个chain插入到evbuffer中
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完成。
[cpp] view
plain copy
//buffer.c文件
static void
evbuffer_chain_insert(struct evbuffer *buf,
struct evbuffer_chain *chain)
{
//新建evbuffer时是把整个evbuffer结构体都赋值0,
//并有buffer->last_with_datap = &buffer->first;
//所以*buf->last_with_datap就是first的值,所以一开始为NULL
if (*buf->last_with_datap == NULL) {
buf->first = buf->last = chain;
} else {
struct evbuffer_chain **ch = buf->last_with_datap;
/* Find the first victim chain. It might be *last_with_datap */
//(*ch)->off != 0表示该evbuffer_chain有数据了
//CHAIN_PINNED(*ch)则表示该evbuffer_chain不能被修改
//在链表中寻找到一个可以使用的evbuffer_chain.
//可以使用是指该chain没有数据并且可以修改。
while ((*ch) && ((*ch)->off != 0 || CHAIN_PINNED(*ch)))
ch = &(*ch)->next;//取的还是next地址。 这样看&((*ch)->next)更清晰
//在已有的链表中找不到一个满足条件的evbuffer_chain。一般都是这种情况
if (*ch == NULL) {
/* There is no victim; just append this new chain. */
//此时buf->last指向的chain不再是最后了。因为last->next被赋值了
buf->last->next = chain;
if (chain->off)//要插入的这个chain是有数据的
buf->last_with_datap = &buf->last->next;//last_with_datap指向的是倒数第二个有数据的chain的next
} else {//这种情况得到的链表可以参考下图
/* Replace all victim chains with this chain. */
//断言,从这个节点开始,后面的说有节点都是没有数据的
EVUTIL_ASSERT(evbuffer_chains_all_empty(*ch));
//释放从这个节点开始的余下链表节点
evbuffer_free_all_chains(*ch);
//把这个chain插入到最后
*ch = chain;
}
buf->last = chain;//重新设置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)
{
...//特殊buffer缓冲数据。一般的不用这些操作。直接释放内存即可
mm_free(chain);
}
可以看到,evbuffer_chain_insert的插入并不是已经一个简单的链表插入,还要检测链表里面是否有没有数据(off为0)的节点。但这个buffer链表里面会有这样的节点吗?其实是有这样节点,这种节点一般是用于预留空间的。预留空间这个概念在STL中是很常见的,它的主要作用是使得当下次添加数据时,无需额外申请空间就能保存数据。
好了,现在来说一下evbuffer_expand。
[cpp] view
plain copy
//buffer.c文件
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最后留有一些空间不使用。
下面是该函数的代码展示了上面所说的:
[cpp] view
plain copy
//buffer.c文件
#define MAX_TO_COPY_IN_EXPAND 4096
//计算evbuffer_chain的可用空间是多少
#define CHAIN_SPACE_LEN(ch) ((ch)->flags & EVBUFFER_IMMUTABLE ? \
0 : (ch)->buffer_len - ((ch)->misalign + (ch)->off))
static struct evbuffer_chain *
evbuffer_expand_singlechain(struct evbuffer *buf, size_t datlen)
{
struct evbuffer_chain *chain, **chainp;
struct evbuffer_chain *result = NULL;
ASSERT_EVBUFFER_LOCKED(buf);
chainp = buf->last_with_datap;
//*chainp指向最后一个有数据的evbuffer_chain或者为NULL
if (*chainp && CHAIN_SPACE_LEN(*chainp) == 0)//CHAIN_SPACE_LEN该chain可用空间的大小
chainp = &(*chainp)->next;
//经过上面的那个if后,当最后一个有数据的evbuffer_chain还有空闲空间时
//*chainp就指向之。否则*chainp指向最后一个有数据的evbuffer_chain的next。
chain = *chainp;
if (chain == NULL ||//这个chain是不可修改的,那么就只能插入一个新的chain了
(chain->flags & (EVBUFFER_IMMUTABLE|EVBUFFER_MEM_PINNED_ANY))) {
goto insert_new;
}
if (CHAIN_SPACE_LEN(chain) >= datlen) {//这个chain的可用空间大于扩展空间
result = chain;
//这种情况,Libevent并不会扩大buffer空间.因为Libevent认为现在的可用空间可以用作用户提出的预留空间
goto ok;
}
if (chain->off == 0) {//当前一个chain存满了时,就会出现这种情况
goto insert_new;//插入一个新的chain
}
//通过使用misalign错位空间,也能使得可用空间大于等于预留空间,那么也不用
//扩大buffer空间
if (evbuffer_chain_should_realign(chain, datlen)) {
evbuffer_chain_align(chain);
result = chain;
goto ok;
}
//空闲空间小于总空间的1/8 或者 已有的数据量大于MAX_TO_COPY_IN_EXPAND(4096)
if (CHAIN_SPACE_LEN(chain) < chain->buffer_len / 8 ||
chain->off > MAX_TO_COPY_IN_EXPAND) {//4096
//本chain有比较多的数据,将这些数据迁移到另外一个chain是不划算的
//此时,将不会改变这个chain。
//下一个chain是否可以有足够的空闲空间.有则直接用之
if (chain->next && CHAIN_SPACE_LEN(chain->next) >= datlen) {
result = chain->next;
goto ok;
} else {
goto insert_new;
}
} else {
//由于本chain的数据量比较小,所以把这个chain的数据迁移到另外一个
//chain上是值得的。
size_t length = chain->off + datlen;
struct evbuffer_chain *tmp = evbuffer_chain_new(length);
if (tmp == NULL)
goto err;
tmp->off = chain->off;
//进行数据迁移
memcpy(tmp->buffer, chain->buffer + chain->misalign,
chain->off);
EVUTIL_ASSERT(*chainp == chain);
result = *chainp = tmp;
if (buf->last == chain)
buf->last = tmp;
tmp->next = chain->next;
evbuffer_chain_free(chain);
goto ok;
}
insert_new:
result = evbuffer_chain_insert_new(buf, datlen);
if (!result)
goto err;
ok:
EVUTIL_ASSERT(result);
EVUTIL_ASSERT(CHAIN_SPACE_LEN(result) >= datlen);
err:
return result;
}
static inline struct evbuffer_chain *
evbuffer_chain_insert_new(struct evbuffer *buf, size_t datlen)
{
struct evbuffer_chain *chain;
if ((chain = evbuffer_chain_new(datlen)) == NULL)
return NULL;
evbuffer_chain_insert(buf, chain);
return chain;
}
上面代码中evbuffer_expand_singlechain函数的第一个if语句,可以联合前面的两张图一起看,更容易看懂。
evbuffer_expand_singlechain函数是要求一个节点就能提供大小为datlen的可用空间。其实Libevent还提供了_evbuffer_expand_fast函数,该函数还有一个整型的参数n,用来表示使用不超过n个节点的前提下,提供datlen的可用空间。不过这个函数只留给Libevent内部使用,用户不能使用之。
[cpp] view
plain copy
//buffer.c文件
int//用最多不超过n个节点就提供datlen大小的空闲空间。链表过长是不好的
_evbuffer_expand_fast(struct evbuffer *buf, size_t datlen, int n)
{
struct evbuffer_chain *chain = buf->last, *tmp, *next;
size_t avail;
int used;
EVUTIL_ASSERT(n >= 2); //n必须大于等于2
//最后一个节点是不可用的
if (chain == NULL || (chain->flags & EVBUFFER_IMMUTABLE)) {
//这种情况下,直接新建一个足够大的evbuffer_chain即可
chain = evbuffer_chain_new(datlen);
if (chain == NULL)
return (-1);
evbuffer_chain_insert(buf, chain);
return (0);
}
used = 0; /* number of chains we're using space in. */
avail = 0; /* how much space they have. */
for (chain = *buf->last_with_datap; chain; chain = chain->next) {
if (chain->off) {//最后一个有数据的节点的可用空间也是要被使用
size_t space = (size_t) CHAIN_SPACE_LEN(chain);
EVUTIL_ASSERT(chain == *buf->last_with_datap);
if (space) {
avail += space;
++used;
}
} else {//链表中off为0的空buffer统统使用
/* No data in chain; realign it. */
chain->misalign = 0;
avail += chain->buffer_len;
++used;
}
if (avail >= datlen) {//链表中的节点的可用空间已经足够了
return (0);
}
if (used == n)//到达了最大可以忍受的链表长度
break;
}
//前面的for循环,如果找够了空闲空间,那么是直接return。所以
//运行到这里时,就说明还没找到空闲空间。一般是因为链表后面的off等于0
//的节点已经被用完了都还不能满足datlen
if (used < n) {
EVUTIL_ASSERT(chain == NULL);
//申请一个足够大的evbuffer_chain,把空间补足
tmp = evbuffer_chain_new(datlen - avail);
if (tmp == NULL)
return (-1);
buf->last->next = tmp;
buf->last = tmp;
return (0);
} else { //used == n。把后面的n个节点都用了还是不够datlen空间
//链表后面的n个节点都用上了,这个n个节点中,至少有n-1个节点的off等于
//0。n个节点都不够,Libevent就认为这些节点都是饭桶,Libevent会统统删除
//然后新建一个足够大的evbuffer_chain。
//用来标志该链表的所有节点都是off为0的。在这种情况下,将删除所有的节点
int rmv_all = 0; /* True iff we removed last_with_data. */
chain = *buf->last_with_datap;
if (!chain->off) {
//这说明链表中的节点都是没有数据的evbuffer_chain
EVUTIL_ASSERT(chain == buf->first);
rmv_all = 1;//标志之
avail = 0;
} else {
//最后一个有数据的chain的可用空间的大小。这个空间是可以用上的
avail = (size_t) CHAIN_SPACE_LEN(chain);
chain = chain->next;
}
//chain指向第一个off等于0的evbuffer_chain 或者等于NULL
//将这些off等于0的evbuffer_chain统统free掉,不要了。
//然后new一个足够大的evbuffer_chain即可。这能降低链表的长度
for (; chain; chain = next) {
next = chain->next;
EVUTIL_ASSERT(chain->off == 0);
evbuffer_chain_free(chain);
}
//new一个足够大的evbuffer_chain
tmp = evbuffer_chain_new(datlen - avail);
if (tmp == NULL) {//new失败
if (rmv_all) {//这种情况下,该链表就根本没有节点了
ZERO_CHAIN(buf);//相当于初始化evbuffer的链表
} else {
buf->last = *buf->last_with_datap;
(*buf->last_with_datap)->next = NULL;
}
return (-1);
}
if (rmv_all) {//这种情况下,该链表就只有一个节点了
buf->first = buf->last = tmp;
buf->last_with_datap = &buf->first;
} else {
(*buf->last_with_datap)->next = tmp;
buf->last = tmp;
}
return (0);
}
}
evbuffer_prepend函数并不复杂,只需弄懂misalign的作用就很容易明白该函数的实现。考虑这种情况:要在链表头插入数据,那么应该new一个新的evbuffer_chain,然后把要插入的数据放到这个新建个的evbuffer_chain中。但evbuffer_chain_new申请到的buffer空间可能会大于要插入的数据长度。插入数据后,buffer就必然会剩下一些空闲空间。那么这个空闲空间放在buffer的前面好还是后面好呢?Libevent认为放在前面会好些,此时misalign就有用了。它表示错开不用的空间,也就是空闲空间。如果再次在链表头插入数据,就可以使用到这些空闲空间了。所以,misalign也可以认为是空闲空间,可以随时使用。
[cpp] view
plain copy
//buffer.c文件
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) {//该chain可以修改
/* If this chain is empty, we can treat it as
* 'empty at the beginning' rather than 'empty at the end' */
if (chain->off == 0)
chain->misalign = chain->buffer_len;
//考虑这种情况:一开始chain->off等于0,之后调用evbuffer_prepend插入
//一些数据(还没填满这个chain),之后再次调用evbuffer_prepend插入一些
//数据。这样就能分别进入下面的if else了
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,//用完这个chain,所以从头开始
(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;
}
}
//为datlen申请一个evbuffer_chain。把datlen长的数据放到这个新建的chain
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;
}
[cpp] view
plain copy
//buffer.c文件
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编程中还是很常见的。
[cpp] view
plain copy
//buffer.c文件
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;
}
//要删除的数据量大于等于已有的数据量。并且这个evbuffer是可以删除的
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);//相当于初试化evbuffer的链表
} 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;
//已经删除到最后一个有数据的evbuffer_chain了
if (chain == *buf->last_with_datap) {
buf->last_with_datap = &buf->first;
}
//删除到倒数第二个有数据的evbuffer_chain
if (&chain->next == buf->last_with_datap)
buf->last_with_datap = &buf->first;
//这个chain被固定了,不能删除
if (CHAIN_PINNED_R(chain)) {
EVUTIL_ASSERT(remaining == 0);
chain->misalign += chain->off;
chain->off = 0;
break;//后面的evbuffer_chain也是固定的
} 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的数据,而不会复制。
对于非阻塞IO的网络库来说,buffer几乎是必须的。Libevent在1.0版本之前就提供了buffer功能。现在来看一下Libevent的buffer。
buffer相关结构体:
Libevent为buffer定义了下面的结构体:[cpp] view
plain copy
//evbuffer-internal.h文件
struct evbuffer_chain;
struct evbuffer {
struct evbuffer_chain *first;
struct evbuffer_chain *last;
//这是一个二级指针。使用*last_with_datap时,指向的是链表中最后一个有数据的evbuffer_chain。
//所以last_with_datap存储的是倒数第二个evbuffer_chain的next成员地址。
//一开始buffer->last_with_datap = &buffer->first;此时first为NULL。所以当链表没有节点时
//*last_with_datap为NULL。当只有一个节点时*last_with_datap就是first。
struct evbuffer_chain **last_with_datap;
size_t total_len;//链表中所有chain的总字节数
...
};
struct evbuffer_chain {
struct evbuffer_chain *next;
size_t buffer_len;//buffer的大小
//错开不使用的空间。该成员的值一般等于0
ev_off_t misalign;
//evbuffer_chain已存数据的字节数
//所以要从buffer + misalign + off的位置开始写入数据
size_t off;
...
unsigned char *buffer;
};
这两个结构体配合工作得到下图所示的存储结构:
因为last_with_datap成员比较特殊,上图只是展示了一种情况。后面还有一张图,展示另外一种情况。
Libevent将缓冲数据都存放到buffer中。通过一个个的evbuffer_chain连成的链表可以存放很多的缓冲数据。
这是一个很常见的链表形式。但Libevent有一个很独特的地方,就是那个evbuffer_chain结构体。
首先,该结构体有misalign成员。该成员表示错开不用的buffer空间。也就是说buffer中真正的数据是从buffer + misalign开始。
第二,evbuffer_chain结构体buffer是一个指针,按道理来说,应该单独调用malloc分配一个堆内存并让buffer指向之。但实际上buffer指向的内存和evbuffer_chain结构体本身的存储内存是一起分配的。下面代码展示了这一点:
[cpp] view
plain copy
//evbuffer-internal.h文件
#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
//宏的作用就是返回,chain + sizeof(evbuffer_chain) 的内存地址。
#define EVBUFFER_CHAIN_EXTRA(t, c) (t *)((struct evbuffer_chain *)(c) + 1)
//buffer.c文件
static struct evbuffer_chain *
evbuffer_chain_new(size_t size)//size是buffer所需的大小
{
struct evbuffer_chain *chain;
size_t to_alloc;
//所需的大小size 再 加上evbuffer_chain结构体本身所需
//的内存大小。这样做的原因是,evbuffer_chain本身是管理
//buffer的结构体。但buffer内存就分配在evbuffer_chain结构体存储
//内存的后面。所以要申请多一些内存。
size += EVBUFFER_CHAIN_SIZE;//evbuffer_chain结构体本身的大小
to_alloc = MIN_BUFFER_SIZE; //内存块的最小值
while (to_alloc < size)
to_alloc <<= 1;
//从分配的内存大小可以知道,evbuffer_chain结构体和buffer是一起分配的
//也就是说他们是存放在同一块内存中
if ((chain = mm_malloc(to_alloc)) == NULL)
return (NULL);
//只需初始化最前面的结构体部分即可
memset(chain, 0, EVBUFFER_CHAIN_SIZE);
//buffer_len存储的是buffer的大小
chain->buffer_len = to_alloc - EVBUFFER_CHAIN_SIZE;
//宏的作用就是返回,chain + sizeof(evbuffer_chain) 的内存地址。
//其效果就是buffer指向的内存刚好是在evbuffer_chain的后面。
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的。
[cpp] view
plain copy
//buffer.c
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存放。
具体的实现如下面所示:
[cpp] view
plain copy
//buffer.c文件
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;
}
//找到最后一个evbuffer_chain。
chain = buf->last;
//第一次插入数据时,buf->last为NULL
if (chain == NULL) {
chain = evbuffer_chain_new(datlen);
if (!chain)
goto done;
evbuffer_chain_insert(buf, chain);
}
//EVBUFFER_IMMUTABLE 是 read-only chain
if ((chain->flags & EVBUFFER_IMMUTABLE) == 0) {//等于0说明是可以写的
//最后那个chain可以放的字节数
remain = (size_t)(chain->buffer_len - chain->misalign - chain->off);
if (remain >= datlen) {//最后那个chain可以放下本次要插入的数据
memcpy(chain->buffer + chain->misalign + chain->off,
data, datlen);
chain->off += datlen;//偏移量,方便下次插入数据
buf->total_len += datlen;//buffer的总字节数
goto out;
} else if (!CHAIN_PINNED(chain) &&//该evbuffer_chain可以修改
evbuffer_chain_should_realign(chain, datlen)) {
//通过调整后,也可以放得下本次要插入的数据
//通过使用chain->misalign这个错位空间而插入数据
evbuffer_chain_align(chain);
memcpy(chain->buffer + chain->off, data, datlen);
chain->off += datlen;
buf->total_len += datlen;
goto out;
}
} else {
remain = 0; //最后一个节点是只写evbuffer_chain
}
//当这个evbuffer_chain是一个read-only buffer或者最后那个chain
//放不下本次要插入的数据时才会执行下面代码
//此时需要新建一个evbuffer_chain
to_alloc = chain->buffer_len;
//当最后evbuffer_chain的缓冲区小于等于2048时,那么新建的evbuffer_chain的
//大小将是最后一个节点缓冲区的2倍。
if (to_alloc <= EVBUFFER_CHAIN_MAX_AUTO_SIZE/2)//4096/2
to_alloc <<= 1;
//最后的大小还是有要插入的数据决定。要注意的是虽然to_alloc最后的值可能为
//datlen。但在evbuffer_chain_new中,实际分配的内存大小必然是512的倍数。
if (datlen > to_alloc)
to_alloc = datlen;
//此时需要new一个chain才能保存本次要插入的数据
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;
//把要插入的数据复制到新建一个chain中。
memcpy(tmp->buffer, data, datlen);
tmp->off = datlen;
//将这个chain插入到evbuffer中
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完成。
[cpp] view
plain copy
//buffer.c文件
static void
evbuffer_chain_insert(struct evbuffer *buf,
struct evbuffer_chain *chain)
{
//新建evbuffer时是把整个evbuffer结构体都赋值0,
//并有buffer->last_with_datap = &buffer->first;
//所以*buf->last_with_datap就是first的值,所以一开始为NULL
if (*buf->last_with_datap == NULL) {
buf->first = buf->last = chain;
} else {
struct evbuffer_chain **ch = buf->last_with_datap;
/* Find the first victim chain. It might be *last_with_datap */
//(*ch)->off != 0表示该evbuffer_chain有数据了
//CHAIN_PINNED(*ch)则表示该evbuffer_chain不能被修改
//在链表中寻找到一个可以使用的evbuffer_chain.
//可以使用是指该chain没有数据并且可以修改。
while ((*ch) && ((*ch)->off != 0 || CHAIN_PINNED(*ch)))
ch = &(*ch)->next;//取的还是next地址。 这样看&((*ch)->next)更清晰
//在已有的链表中找不到一个满足条件的evbuffer_chain。一般都是这种情况
if (*ch == NULL) {
/* There is no victim; just append this new chain. */
//此时buf->last指向的chain不再是最后了。因为last->next被赋值了
buf->last->next = chain;
if (chain->off)//要插入的这个chain是有数据的
buf->last_with_datap = &buf->last->next;//last_with_datap指向的是倒数第二个有数据的chain的next
} else {//这种情况得到的链表可以参考下图
/* Replace all victim chains with this chain. */
//断言,从这个节点开始,后面的说有节点都是没有数据的
EVUTIL_ASSERT(evbuffer_chains_all_empty(*ch));
//释放从这个节点开始的余下链表节点
evbuffer_free_all_chains(*ch);
//把这个chain插入到最后
*ch = chain;
}
buf->last = chain;//重新设置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)
{
...//特殊buffer缓冲数据。一般的不用这些操作。直接释放内存即可
mm_free(chain);
}
可以看到,evbuffer_chain_insert的插入并不是已经一个简单的链表插入,还要检测链表里面是否有没有数据(off为0)的节点。但这个buffer链表里面会有这样的节点吗?其实是有这样节点,这种节点一般是用于预留空间的。预留空间这个概念在STL中是很常见的,它的主要作用是使得当下次添加数据时,无需额外申请空间就能保存数据。
预留buffer空间:
其中一个扩大预留空间的函数是evbuffer_expand。在讲evbuffer_expand前,看一下如果存在没有数据(off为0)的节点,链表又会是怎么样的。这涉及到last_with_data指针的指向,如下图所示:好了,现在来说一下evbuffer_expand。
[cpp] view
plain copy
//buffer.c文件
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最后留有一些空间不使用。
下面是该函数的代码展示了上面所说的:
[cpp] view
plain copy
//buffer.c文件
#define MAX_TO_COPY_IN_EXPAND 4096
//计算evbuffer_chain的可用空间是多少
#define CHAIN_SPACE_LEN(ch) ((ch)->flags & EVBUFFER_IMMUTABLE ? \
0 : (ch)->buffer_len - ((ch)->misalign + (ch)->off))
static struct evbuffer_chain *
evbuffer_expand_singlechain(struct evbuffer *buf, size_t datlen)
{
struct evbuffer_chain *chain, **chainp;
struct evbuffer_chain *result = NULL;
ASSERT_EVBUFFER_LOCKED(buf);
chainp = buf->last_with_datap;
//*chainp指向最后一个有数据的evbuffer_chain或者为NULL
if (*chainp && CHAIN_SPACE_LEN(*chainp) == 0)//CHAIN_SPACE_LEN该chain可用空间的大小
chainp = &(*chainp)->next;
//经过上面的那个if后,当最后一个有数据的evbuffer_chain还有空闲空间时
//*chainp就指向之。否则*chainp指向最后一个有数据的evbuffer_chain的next。
chain = *chainp;
if (chain == NULL ||//这个chain是不可修改的,那么就只能插入一个新的chain了
(chain->flags & (EVBUFFER_IMMUTABLE|EVBUFFER_MEM_PINNED_ANY))) {
goto insert_new;
}
if (CHAIN_SPACE_LEN(chain) >= datlen) {//这个chain的可用空间大于扩展空间
result = chain;
//这种情况,Libevent并不会扩大buffer空间.因为Libevent认为现在的可用空间可以用作用户提出的预留空间
goto ok;
}
if (chain->off == 0) {//当前一个chain存满了时,就会出现这种情况
goto insert_new;//插入一个新的chain
}
//通过使用misalign错位空间,也能使得可用空间大于等于预留空间,那么也不用
//扩大buffer空间
if (evbuffer_chain_should_realign(chain, datlen)) {
evbuffer_chain_align(chain);
result = chain;
goto ok;
}
//空闲空间小于总空间的1/8 或者 已有的数据量大于MAX_TO_COPY_IN_EXPAND(4096)
if (CHAIN_SPACE_LEN(chain) < chain->buffer_len / 8 ||
chain->off > MAX_TO_COPY_IN_EXPAND) {//4096
//本chain有比较多的数据,将这些数据迁移到另外一个chain是不划算的
//此时,将不会改变这个chain。
//下一个chain是否可以有足够的空闲空间.有则直接用之
if (chain->next && CHAIN_SPACE_LEN(chain->next) >= datlen) {
result = chain->next;
goto ok;
} else {
goto insert_new;
}
} else {
//由于本chain的数据量比较小,所以把这个chain的数据迁移到另外一个
//chain上是值得的。
size_t length = chain->off + datlen;
struct evbuffer_chain *tmp = evbuffer_chain_new(length);
if (tmp == NULL)
goto err;
tmp->off = chain->off;
//进行数据迁移
memcpy(tmp->buffer, chain->buffer + chain->misalign,
chain->off);
EVUTIL_ASSERT(*chainp == chain);
result = *chainp = tmp;
if (buf->last == chain)
buf->last = tmp;
tmp->next = chain->next;
evbuffer_chain_free(chain);
goto ok;
}
insert_new:
result = evbuffer_chain_insert_new(buf, datlen);
if (!result)
goto err;
ok:
EVUTIL_ASSERT(result);
EVUTIL_ASSERT(CHAIN_SPACE_LEN(result) >= datlen);
err:
return result;
}
static inline struct evbuffer_chain *
evbuffer_chain_insert_new(struct evbuffer *buf, size_t datlen)
{
struct evbuffer_chain *chain;
if ((chain = evbuffer_chain_new(datlen)) == NULL)
return NULL;
evbuffer_chain_insert(buf, chain);
return chain;
}
上面代码中evbuffer_expand_singlechain函数的第一个if语句,可以联合前面的两张图一起看,更容易看懂。
evbuffer_expand_singlechain函数是要求一个节点就能提供大小为datlen的可用空间。其实Libevent还提供了_evbuffer_expand_fast函数,该函数还有一个整型的参数n,用来表示使用不超过n个节点的前提下,提供datlen的可用空间。不过这个函数只留给Libevent内部使用,用户不能使用之。
[cpp] view
plain copy
//buffer.c文件
int//用最多不超过n个节点就提供datlen大小的空闲空间。链表过长是不好的
_evbuffer_expand_fast(struct evbuffer *buf, size_t datlen, int n)
{
struct evbuffer_chain *chain = buf->last, *tmp, *next;
size_t avail;
int used;
EVUTIL_ASSERT(n >= 2); //n必须大于等于2
//最后一个节点是不可用的
if (chain == NULL || (chain->flags & EVBUFFER_IMMUTABLE)) {
//这种情况下,直接新建一个足够大的evbuffer_chain即可
chain = evbuffer_chain_new(datlen);
if (chain == NULL)
return (-1);
evbuffer_chain_insert(buf, chain);
return (0);
}
used = 0; /* number of chains we're using space in. */
avail = 0; /* how much space they have. */
for (chain = *buf->last_with_datap; chain; chain = chain->next) {
if (chain->off) {//最后一个有数据的节点的可用空间也是要被使用
size_t space = (size_t) CHAIN_SPACE_LEN(chain);
EVUTIL_ASSERT(chain == *buf->last_with_datap);
if (space) {
avail += space;
++used;
}
} else {//链表中off为0的空buffer统统使用
/* No data in chain; realign it. */
chain->misalign = 0;
avail += chain->buffer_len;
++used;
}
if (avail >= datlen) {//链表中的节点的可用空间已经足够了
return (0);
}
if (used == n)//到达了最大可以忍受的链表长度
break;
}
//前面的for循环,如果找够了空闲空间,那么是直接return。所以
//运行到这里时,就说明还没找到空闲空间。一般是因为链表后面的off等于0
//的节点已经被用完了都还不能满足datlen
if (used < n) {
EVUTIL_ASSERT(chain == NULL);
//申请一个足够大的evbuffer_chain,把空间补足
tmp = evbuffer_chain_new(datlen - avail);
if (tmp == NULL)
return (-1);
buf->last->next = tmp;
buf->last = tmp;
return (0);
} else { //used == n。把后面的n个节点都用了还是不够datlen空间
//链表后面的n个节点都用上了,这个n个节点中,至少有n-1个节点的off等于
//0。n个节点都不够,Libevent就认为这些节点都是饭桶,Libevent会统统删除
//然后新建一个足够大的evbuffer_chain。
//用来标志该链表的所有节点都是off为0的。在这种情况下,将删除所有的节点
int rmv_all = 0; /* True iff we removed last_with_data. */
chain = *buf->last_with_datap;
if (!chain->off) {
//这说明链表中的节点都是没有数据的evbuffer_chain
EVUTIL_ASSERT(chain == buf->first);
rmv_all = 1;//标志之
avail = 0;
} else {
//最后一个有数据的chain的可用空间的大小。这个空间是可以用上的
avail = (size_t) CHAIN_SPACE_LEN(chain);
chain = chain->next;
}
//chain指向第一个off等于0的evbuffer_chain 或者等于NULL
//将这些off等于0的evbuffer_chain统统free掉,不要了。
//然后new一个足够大的evbuffer_chain即可。这能降低链表的长度
for (; chain; chain = next) {
next = chain->next;
EVUTIL_ASSERT(chain->off == 0);
evbuffer_chain_free(chain);
}
//new一个足够大的evbuffer_chain
tmp = evbuffer_chain_new(datlen - avail);
if (tmp == NULL) {//new失败
if (rmv_all) {//这种情况下,该链表就根本没有节点了
ZERO_CHAIN(buf);//相当于初始化evbuffer的链表
} else {
buf->last = *buf->last_with_datap;
(*buf->last_with_datap)->next = NULL;
}
return (-1);
}
if (rmv_all) {//这种情况下,该链表就只有一个节点了
buf->first = buf->last = tmp;
buf->last_with_datap = &buf->first;
} else {
(*buf->last_with_datap)->next = tmp;
buf->last = tmp;
}
return (0);
}
}
在链表头添加数据:
前面的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也可以认为是空闲空间,可以随时使用。
[cpp] view
plain copy
//buffer.c文件
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) {//该chain可以修改
/* If this chain is empty, we can treat it as
* 'empty at the beginning' rather than 'empty at the end' */
if (chain->off == 0)
chain->misalign = chain->buffer_len;
//考虑这种情况:一开始chain->off等于0,之后调用evbuffer_prepend插入
//一些数据(还没填满这个chain),之后再次调用evbuffer_prepend插入一些
//数据。这样就能分别进入下面的if else了
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,//用完这个chain,所以从头开始
(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;
}
}
//为datlen申请一个evbuffer_chain。把datlen长的数据放到这个新建的chain
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的数据。当然是从链表的前面开始复制。[cpp] view
plain copy
//buffer.c文件
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编程中还是很常见的。
[cpp] view
plain copy
//buffer.c文件
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;
}
//要删除的数据量大于等于已有的数据量。并且这个evbuffer是可以删除的
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);//相当于初试化evbuffer的链表
} 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;
//已经删除到最后一个有数据的evbuffer_chain了
if (chain == *buf->last_with_datap) {
buf->last_with_datap = &buf->first;
}
//删除到倒数第二个有数据的evbuffer_chain
if (&chain->next == buf->last_with_datap)
buf->last_with_datap = &buf->first;
//这个chain被固定了,不能删除
if (CHAIN_PINNED_R(chain)) {
EVUTIL_ASSERT(remaining == 0);
chain->misalign += chain->off;
chain->off = 0;
break;//后面的evbuffer_chain也是固定的
} 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的数据,而不会复制。
相关文章推荐
- Libevent源码分析-----evbuffer结构与基本操作
- Libevent源码分析-----evbuffer结构与基本操作
- Libevent源码分析-----evbuffer结构与基本操作
- Libevent源码分析-----evbuffer结构与基本操作
- Libevent源码分析-----evbuffer结构与基本操作
- evbuffer结构与基本操作
- Libevent源码分析(五)--- evbuffer的基本操作
- evbuffer结构与基本操作
- 安全隐患分析和基本系统结构信息的收集
- bo2-6.cpp 具有实用意义的线性链表(存储结构由c2-5.h定义)的24个基本操作
- bo2-7.cpp 多项式(存储结构由c2-6.h定义)的基本操作及算法
- 栈的基本结构和基本操作(c语言描述)
- AWR基本操作、分析
- Python操作Access数据库基本操作步骤分析
- 数据结构中线性表的一些基本操作
- 单链表的存储结构及其基本操作
- C++ 模板类 栈基本操作 数据结构
- 数据结构——链栈的基本操作
- tb-common-utils源码分析(3):文件和目录的基本操作
- bo2-9.cpp 不带头结点的单链表(存储结构由c2-2.h定义)的部分基本操作(2个)