libevent中的缓冲区(二)

1. 缓冲区的chunk的分配
在分配chunk时,其实chunk头包含一些附加信息,如chunk块存放数据的空间大小(buffer_len),起始地址(buffer),当前数据尾到起始片的偏移(off)等。chunk的大小算上头部信息,至少是1024个字节。

static struct evbuffer_chain *
evbuffer_chain_new(size_t size)  //这个size不包括chunk头大小
{
    struct evbuffer_chain *chain;
    size_t to_alloc;

    if (size > EVBUFFER_CHAIN_MAX - EVBUFFER_CHAIN_SIZE)   //如果分配空间的大小加上头部大小大于chunk的最大值就直接返回。
        return (NULL);

    size += EVBUFFER_CHAIN_SIZE; //加上chunk头大小

    /* get the next largest memory that can hold the buffer */
    //计算容纳size大小的最小值
    if (size < EVBUFFER_CHAIN_MAX / 2) {
        to_alloc = MIN_BUFFER_SIZE;
        while (to_alloc < size) {
            to_alloc <<= 1;
        }
    } else {
        to_alloc = size;
    }

    /* we get everything in one chunk */
    if ((chain = mm_malloc(to_alloc)) == NULL)
        return (NULL);

    //chunk头部作息初始化
    memset(chain, 0, EVBUFFER_CHAIN_SIZE);

    chain->buffer_len = to_alloc - EVBUFFER_CHAIN_SIZE;

    /* this way we can manipulate the buffer to different addresses,
     * which is required for mmap for example.
     */
    chain->buffer = EVBUFFER_CHAIN_EXTRA(u_char, chain);

    return (chain);
}

2. 列表内容
因为缓冲区是通过evbuffer_chain的next指针连接起来的,这个是链表操作

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

3. 缓冲区中添加chunk
将chunk添加到evbuffer的尾部时,会释放掉所有空的chunk,在添加chunk时,不会调用回调

static void
evbuffer_chain_insert(struct evbuffer *buf,
    struct evbuffer_chain *chain)
{
    ASSERT_EVBUFFER_LOCKED(buf);
    if (*buf->last_with_datap == NULL) {  //表示原来的缓冲区不存在为空,不存在chunk时,将first和last指针赋值为chain
        /* There are no chains data on the buffer at all. */
        EVUTIL_ASSERT(buf->last_with_datap == &buf->first);
        EVUTIL_ASSERT(buf->first == NULL);
        buf->first = buf->last = chain;
    } else {
        //在添加时,会释放尾部所有的空chunk
        struct evbuffer_chain **ch = buf->last_with_datap;
        /* Find the first victim chain.  It might be *last_with_datap */
        while ((*ch) && ((*ch)->off != 0 || CHAIN_PINNED(*ch)))
            ch = &(*ch)->next;
        if (*ch == NULL) {
            /* There is no victim; just append this new chain. */
            buf->last->next = chain;
            if (chain->off)  //如果添加的chunk包含有用数据,就修改last_with_datap指针
                buf->last_with_datap = &buf->last->next;
        } else {
            /* Replace all victim chains with this chain. */
            EVUTIL_ASSERT(evbuffer_chains_all_empty(*ch));
            evbuffer_free_all_chains(*ch);
            *ch = chain;
        }
        buf->last = chain;
    }
    buf->total_len += chain->off;
}

4. chunk块中flag标志的设置及取消
evbuffer_chain中的flag标志主要是锁定读还是写两种情况,在设置取消操作时比较简单

//设置
_evbuffer_chain_pin(struct evbuffer_chain *chain, unsigned flag)
{
    EVUTIL_ASSERT((chain->flags & flag) == 0);
    chain->flags |= flag;
}
//取消
void
_evbuffer_chain_unpin(struct evbuffer_chain *chain, unsigned flag)
{
    EVUTIL_ASSERT((chain->flags & flag) != 0);
    chain->flags &= ~flag;
    if (chain->flags & EVBUFFER_DANGLING)
        evbuffer_chain_free(chain);
}

5. 缓冲区回调的触发
在写操作使用非阻塞模式时,在调用网络接口可写状态监听时,总是满足的,所以在写操作完成后,会将对应的fd事件从事件循环中删除,而当向缓冲区中写数据时,会将对应的事件添加到event_base中,这时,这个回调就显得比较重要了

void
evbuffer_invoke_callbacks(struct evbuffer *buffer)
{
    if (TAILQ_EMPTY(&buffer->callbacks)) {
        buffer->n_add_for_cb = buffer->n_del_for_cb = 0;
        return;
    }

    if (buffer->deferred_cbs) {
        if (buffer->deferred.queued)
            return;
        _evbuffer_incref_and_lock(buffer);
        if (buffer->parent)
            bufferevent_incref(buffer->parent);
        EVBUFFER_UNLOCK(buffer);
        event_deferred_cb_schedule(buffer->cb_queue, &buffer->deferred);  //将缓冲区回调添加加event_base中的回调队列中
    }

    evbuffer_run_callbacks(buffer, 0); //调用回调函数
}

6. 缓冲区大小的调节
在I/O上有数据可读时,将I/O上的数据读入到缓冲区,需要调节缓冲区的大小来填装从I/O上读到的数据。主要是函数evbuffer_reserve_space来完成。

int
evbuffer_reserve_space(struct evbuffer *buf, ev_ssize_t size,
    struct evbuffer_iovec *vec, int n_vecs)
{
    struct evbuffer_chain *chain, **chainp;
    int n = -1;

    EVBUFFER_LOCK(buf);
    if (buf->freeze_end)  //尾部禁止写,退出
        goto done;
    if (n_vecs < 1)
        goto done;
    if (n_vecs == 1) {
        if ((chain = evbuffer_expand_singlechain(buf, size)) == NULL)
            goto done;

        vec[0].iov_base = CHAIN_SPACE_PTR(chain);
        vec[0].iov_len = (size_t) CHAIN_SPACE_LEN(chain);
        EVUTIL_ASSERT(size<0 || (size_t)vec[0].iov_len >= (size_t)size);
        n = 1;
    } else {
        if (_evbuffer_expand_fast(buf, size, n_vecs)<0)
            goto done;
        n = _evbuffer_read_setup_vecs(buf, size, vec, n_vecs,
                &chainp, 0);
    }

done:
    EVBUFFER_UNLOCK(buf);
    return n;

}

7. 设置读入的缓冲区
在集中读时,设置iovec的缓冲区

int
_evbuffer_read_setup_vecs(struct evbuffer *buf, ev_ssize_t howmuch,
    struct evbuffer_iovec *vecs, int n_vecs_avail,
    struct evbuffer_chain ***chainp, int exact)
{
    struct evbuffer_chain *chain;
    struct evbuffer_chain **firstchainp;
    size_t so_far;
    int i;
    ASSERT_EVBUFFER_LOCKED(buf);

    if (howmuch < 0)
        return -1;

    so_far = 0;
    /* Let firstchain be the first chain with any space on it */
    firstchainp = buf->last_with_datap;
    if (CHAIN_SPACE_LEN(*firstchainp) == 0) {
        firstchainp = &(*firstchainp)->next;
    }

    chain = *firstchainp;
    for (i = 0; i < n_vecs_avail && so_far < (size_t)howmuch; ++i) {
        size_t avail = (size_t) CHAIN_SPACE_LEN(chain);
        if (avail > (howmuch - so_far) && exact)
            avail = howmuch - so_far;
        vecs[i].iov_base = CHAIN_SPACE_PTR(chain);
        vecs[i].iov_len = avail;
        so_far += avail;
        chain = chain->next;
    }

    *chainp = firstchainp;
    return i;
}

8. 读入缓冲区
在I/O上有数据可读时就将数据读入evbuffer中,函数evbuffer_read来完成,同时设置缓冲区的回调(即应用层的处理)

int
evbuffer_read(struct evbuffer *buf, evutil_socket_t fd, int howmuch)
{
    struct evbuffer_chain **chainp;
    int n;
    int result;

#ifdef USE_IOVEC_IMPL
    int nvecs, i, remaining;
#else
    struct evbuffer_chain *chain;
    unsigned char *p;
#endif

    EVBUFFER_LOCK(buf);

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

    n = get_n_bytes_readable_on_socket(fd);
    if (n <= 0 || n > EVBUFFER_MAX_READ)
        n = EVBUFFER_MAX_READ;
    if (howmuch < 0 || howmuch > n)
        howmuch = n;

#ifdef USE_IOVEC_IMPL
    /* Since we can use iovecs, we're willing to use the last
     * NUM_READ_IOVEC chains. */
    if (_evbuffer_expand_fast(buf, howmuch, NUM_READ_IOVEC) == -1) {
        result = -1;
        goto done;
    } else {
        IOV_TYPE vecs[NUM_READ_IOVEC];
#ifdef _EVBUFFER_IOVEC_IS_NATIVE
        nvecs = _evbuffer_read_setup_vecs(buf, howmuch, vecs,
            NUM_READ_IOVEC, &chainp, 1);
#else
        /* We aren't using the native struct iovec.  Therefore,
           we are on win32. */
        struct evbuffer_iovec ev_vecs[NUM_READ_IOVEC];
        nvecs = _evbuffer_read_setup_vecs(buf, howmuch, ev_vecs, 2,
            &chainp, 1);

        for (i=0; i < nvecs; ++i)
            WSABUF_FROM_EVBUFFER_IOV(&vecs[i], &ev_vecs[i]);
#endif

#ifdef WIN32
        {
            DWORD bytesRead;
            DWORD flags=0;
            if (WSARecv(fd, vecs, nvecs, &bytesRead, &flags, NULL, NULL)) {
                /* The read failed. It might be a close,
                 * or it might be an error. */
                if (WSAGetLastError() == WSAECONNABORTED)
                    n = 0;
                else
                    n = -1;
            } else
                n = bytesRead;
        }
#else
        n = readv(fd, vecs, nvecs);
#endif
    }

#else /*!USE_IOVEC_IMPL*/
    /* If we don't have FIONREAD, we might waste some space here */
    /* XXX we _will_ waste some space here if there is any space left
     * over on buf->last. */
    if ((chain = evbuffer_expand_singlechain(buf, howmuch)) == NULL) {
        result = -1;
        goto done;
    }

    /* We can append new data at this point */
    p = chain->buffer + chain->misalign + chain->off;

#ifndef WIN32
    n = read(fd, p, howmuch);
#else
    n = recv(fd, p, howmuch, 0);
#endif
#endif /* USE_IOVEC_IMPL */

    if (n == -1) {
        result = -1;
        goto done;
    }
    if (n == 0) {
        result = 0;
        goto done;
    }

#ifdef USE_IOVEC_IMPL
    remaining = n;
    for (i=0; i < nvecs; ++i) {
        /* can't overflow, since only mutable chains have
         * huge misaligns. */
        size_t space = (size_t) CHAIN_SPACE_LEN(*chainp);
        /* XXXX This is a kludge that can waste space in perverse
         * situations. */
        if (space > EVBUFFER_CHAIN_MAX)
            space = EVBUFFER_CHAIN_MAX;
        if ((ev_ssize_t)space < remaining) {
            (*chainp)->off += space;
            remaining -= (int)space;
        } else {
            (*chainp)->off += remaining;
            buf->last_with_datap = chainp;
            break;
        }
        chainp = &(*chainp)->next;
    }
#else
    chain->off += n;
    advance_last_with_data(buf);
#endif
    buf->total_len += n;
    buf->n_add_for_cb += n;

    /* Tell someone about changes in this buffer */
    evbuffer_invoke_callbacks(buf);
    result = n;
done:
    EVBUFFER_UNLOCK(buf);
    return result;
}

9. 缓冲区中的写操作
将缓冲区中的数据写入I/O,在写时分为集中写与一般的写。在一般写的情况下,如果要写的字节数分散在多个chunk中,这就要将多个chunk中的数据合并到一个chunk中,而将其它的释放,这种处理是通过函数evbuffer_pullup来完成。将缓冲区的数据写到socket是通过evbuffer_write_atmost来完成。在写完后,会将缓冲区中的一部分chunk释放。

int
evbuffer_write_atmost(struct evbuffer *buffer, evutil_socket_t fd,
    ev_ssize_t howmuch)
{
    int n = -1;

    EVBUFFER_LOCK(buffer);

    if (buffer->freeze_start) {
        goto done;
    }

    if (howmuch < 0 || (size_t)howmuch > buffer->total_len)
        howmuch = buffer->total_len;

    if (howmuch > 0) {
#ifdef USE_SENDFILE
        struct evbuffer_chain *chain = buffer->first;
        if (chain != NULL && (chain->flags & EVBUFFER_SENDFILE))
            n = evbuffer_write_sendfile(buffer, fd, howmuch);
        else {
#endif
#ifdef USE_IOVEC_IMPL
        n = evbuffer_write_iovec(buffer, fd, howmuch);   //集中写
#elif defined(WIN32)                                //一般写
        /* XXX(nickm) Don't disable this code until we know if
         * the WSARecv code above works. */
        void *p = evbuffer_pullup(buffer, howmuch);
        EVUTIL_ASSERT(p || !howmuch);
        n = send(fd, p, howmuch, 0);
#else
        void *p = evbuffer_pullup(buffer, howmuch);
        EVUTIL_ASSERT(p || !howmuch);
        n = write(fd, p, howmuch);                  //一般写
#endif
#ifdef USE_SENDFILE
        }
#endif
    }

    if (n > 0)
        evbuffer_drain(buffer, n);   //释放

done:
    EVBUFFER_UNLOCK(buffer);
    return (n);
}

你可能感兴趣的:(libevent)