我是靠谱客的博主 奋斗茉莉,最近开发中收集的这篇文章主要介绍Libevent之evbuffer详解介绍evbuffer结构体创建和释放evbuffer向evbuffer尾部添加数据evbuffer扩展空间向evbuffer头部添加数据从evbuff复制数据从evbuff中移除数据,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

介绍

对于任何网络库(模块)而言,一个缓冲模块都是必不可少的。缓冲模块主要用于缓冲从网络接收到的数据,以及用户提交的数据(用于发送)。很多时候,我们还需要将网络模块层(非TCP层)的这些缓冲数据拷贝到用户层,而这些内存拷贝都会消耗时间。为此Libevent提供了evbuffer用于处理缓冲网络IO的缓冲部分,为后续bufferevent的工作做准备。

evbuffer结构体

缓冲区由evbuffer和evbuffer_chain组成,其中evbuffer_chain是真正存储数据的一块内存,通过链表将一个一个的evbuffer_chain连接在一起,组成内存池。而通过evbuffer结构体就可以管理这个内存池链表了。所以结果如下图:
这里写图片描述

//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;  
};  

Libevent将缓冲数据都存放到buffer中。通过一个个的evbuffer_chain连成的链表可以存放很多的缓冲数据。
Libevent有一个很独特的地方,就是那个evbuffer_chain结构体。
1、该结构体有misalign成员。该成员表示错开不用的buffer空间。也就是说buffer中真正的数据是从buffer + misalign开始。
2、evbuffer_chain结构体buffer是一个指针,按道理来说,应该单独调用malloc分配一个堆内存并让buffer指向之。但实际上buffer指向的内存和evbuffer_chain结构体本身的存储内存是一起分配的。下面代码展示了这一点

//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

创建和释放evbuffer

struct evbuffer *
evbuffer_new(void)
{
    struct evbuffer *buffer;

    buffer = mm_calloc(1, sizeof(struct evbuffer));
    if (buffer == NULL)
        return (NULL);

    TAILQ_INIT(&buffer->callbacks);
    buffer->refcnt = 1;
    buffer->last_with_datap = &buffer->first;

    return (buffer);
}

void
evbuffer_free(struct evbuffer *buffer)
{
    EVBUFFER_LOCK(buffer);
    _evbuffer_decref_and_unlock(buffer);
}

//减少引用计数以及解锁
void
_evbuffer_decref_and_unlock(struct evbuffer *buffer)
{
    struct evbuffer_chain *chain, *next;
    ASSERT_EVBUFFER_LOCKED(buffer);

    EVUTIL_ASSERT(buffer->refcnt > 0);//引用计数,处理多线程操作

    if (--buffer->refcnt > 0) {
        EVBUFFER_UNLOCK(buffer);
        return;
    }

//释放全部chain
    for (chain = buffer->first; chain != NULL; chain = next) {
        next = chain->next;
        evbuffer_chain_free(chain);
    }
    evbuffer_remove_all_callbacks(buffer);//调用回调函数
    if (buffer->deferred_cbs)
        event_deferred_cb_cancel(buffer->cb_queue, &buffer->deferred);

    EVBUFFER_UNLOCK(buffer);
    if (buffer->own_lock)
        EVTHREAD_FREE_LOCK(buffer->lock, EVTHREAD_LOCKTYPE_RECURSIVE);
    mm_free(buffer);//释放evbuff
}

evbuffer_new()分配和返回一个新的空evbuffer,而evbuffer_free()释放evbuffer和其中节点的内容。

向evbuffer尾部添加数据

Libevent提供给用户的添加数据接口是evbuffer_add,现在就通过这个函数看一下是怎么将数据插入到buffer中的。该函数是在链表的尾部添加数据,如果想在链表的前面添加数据可以使用evbuffer_prepend。在链表尾部插入数据,分下面几种情况:

  • 1、该链表为空,即这是第一次插入数据。这是最简单的,直接把新建的evbuffer_chain插入到链表中,通过调用evbuffer_chain_insert。
  • 2、链表的最后一个节点(即evbuffer_chain)还有一些空余的空间,放得下本次要插入的数据。此时直接把数据追加到最后一个节点即可。
  • 3、链表的最后一个节点并不能放得下本次要插入的数据,那么就需要把本次要插入的数据分开由两个evbuffer_chain存放。
//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完成。

//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中是很常见的,它的主要作用是使得当下次添加数据时,无需额外申请空间就能保存数据。


int
evbuffer_add_printf(struct evbuffer *buf, const char *fmt, ...)
{
    int res = -1;
    va_list ap;

    va_start(ap, fmt);
    res = evbuffer_add_vprintf(buf, fmt, ap);
    va_end(ap);

    return (res);
}

int
evbuffer_add_vprintf(struct evbuffer *buf, const char *fmt, va_list ap)
{
    char *buffer;
    size_t space;
    int sz, result = -1;
    va_list aq;
    struct evbuffer_chain *chain;


    EVBUFFER_LOCK(buf);

    if (buf->freeze_end) {
        goto done;
    }

    /* make sure that at least some space is available */
    if ((chain = evbuffer_expand_singlechain(buf, 64)) == NULL)
        goto done;

    for (;;) {
#if 0
        size_t used = chain->misalign + chain->off;
        buffer = (char *)chain->buffer + chain->misalign + chain->off;
        EVUTIL_ASSERT(chain->buffer_len >= used);
        space = chain->buffer_len - used;
#endif
        buffer = (char*) CHAIN_SPACE_PTR(chain);
        space = (size_t) CHAIN_SPACE_LEN(chain);

#ifndef va_copy
#define va_copy(dst, src)   memcpy(&(dst), &(src), sizeof(va_list))
#endif
        va_copy(aq, ap);

        sz = evutil_vsnprintf(buffer, space, fmt, aq);

        va_end(aq);

        if (sz < 0)
            goto done;
        if (INT_MAX >= EVBUFFER_CHAIN_MAX &&
            (size_t)sz >= EVBUFFER_CHAIN_MAX)
            goto done;
        if ((size_t)sz < space) {
            chain->off += sz;
            buf->total_len += sz;
            buf->n_add_for_cb += sz;

            advance_last_with_data(buf);
            evbuffer_invoke_callbacks(buf);
            result = sz;
            goto done;
        }
        if ((chain = evbuffer_expand_singlechain(buf, sz + 1)) == NULL)
            goto done;
    }
    /* NOTREACHED */

done:
    EVBUFFER_UNLOCK(buf);
    return result;
}

evbuffer_add_printfevbuffer_add_vprintf添加格式化的数据到evbuffer的尾部。

evbuffer_add(buf, "Hello world 2.0.1", 17);
evbuffer_add_printf(buf, "Hello %s %d.%d.%d", "world", 2, 0, 1);

evbuffer扩展空间

为了减少动态分配的次数,Libevent会在链表尾部预留一个空闲的空间,为下次分配做准备。内部调用,我们根本不需要关心这里如何实现。

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的数据时,无需动态申请内存。
1、如果这个链表的所有buffer空间都被用完了,那么解决需要创建一个buffer为datlen的evbuffer_chain,然后把这个evbuffer_chain插入到链表最后面即可。此时这个evbuffer_chainoff就等于0,代表chain节点里面数据为空。

2、最后一个有数据chain还有一些空闲空间,但小于datlen。evbuffer_expand是调用evbuffer_expand_singlechain实现扩大空间的。而evbuffer_expand_singlechain函数有一个特点,预留空间datlen必须是在一个evbuffer_chain中,不能跨chain。
所以如果最后一个chain的数据比较少,那么就直接不要那个chain。当然chain上的数据还是要的。Libevent新建一个比datlen更大的chain,把最后一个chain上的数据迁移到这个新建的chain上。这样就既能保证该chain节点也能填满,也保证了预留空间datlen必须在是一个chain的。如果最后一个chain的数据比较多,Libevent就认为迁移不划算,那么Libevent就让这个chain最后留有一些空间不使用。

向evbuffer头部添加数据

前面的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也可以认为是空闲空间,可以随时使用。

//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;  
}  

从evbuff复制数据

现在来看一下怎么从evbuffer中复制一些数据。Libevent提供了函数evbuffer_copyout用来复制evbuffer的数据。当然是从链表的前面开始复制。

//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;  
}  

将buf前面复制datlen字节到data处。如果可用字节少于datlen,函数会复制所有字节。失败返回-1,否则返回复制的字节数。如果从缓冲区复制数据太慢可以使用evbuffer_peek()

从evbuff中移除数据

//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_copyout将buff前面datlen字节拷贝到data_out处,然后再通过evbuffer_drain从buff中删除数据。
evbuffer_drain则直接删除evbuffer的数据,而不会复制。

最后

以上就是奋斗茉莉为你收集整理的Libevent之evbuffer详解介绍evbuffer结构体创建和释放evbuffer向evbuffer尾部添加数据evbuffer扩展空间向evbuffer头部添加数据从evbuff复制数据从evbuff中移除数据的全部内容,希望文章能够帮你解决Libevent之evbuffer详解介绍evbuffer结构体创建和释放evbuffer向evbuffer尾部添加数据evbuffer扩展空间向evbuffer头部添加数据从evbuff复制数据从evbuff中移除数据所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(67)

评论列表共有 0 条评论

立即
投稿
返回
顶部