(十五)evbuffers緩衝區(上)

前言

evbuffer是libevent的緩衝區部分,它主要是負責緩衝區的構建等操作,而bufferevent主要負責管理輸入輸出緩衝區,相當於是對evbuffer做的一層抽象,緩衝區對於一個網絡庫幾乎是必需的。我們來介紹關於evbuffer部分,部分主要集中在event.hbuffer.c

evbuffer結構體

首先,先了解緩衝區的數據結構:

struct evbuffer {
        u_char *buffer;
        u_char *orig_buffer;
        size_t misalign;
        size_t totallen;
        size_t off;

        void (*cb)(struct evbuffer *, size_t, size_t, void *);
        void *cbarg;
};

buffer:當前有效緩衝區的起始地址
orig_buffer:申請內存時分配的起始地址
misalign:前兩者的差值,即buffer相對於orig_buffer的偏移地址
totallen:申請內存的整個大小
off:代表有效數據的長度
cb:當緩衝區有變化時的回調函數
cbarg:回調函數的參數
這裏就直接引用網上的一張圖來幫助理解。(抱歉,沒有找到原文鏈接,看到的也是轉發的)
這裏寫圖片描述
接下來,我們來看看關於緩衝區的一些操作

evbuffer_new

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

    buffer = calloc(1, sizeof(struct evbuffer));

    return (buffer);
}

這個函數很簡單,就是替evbuffer申請空間並返回就完了。evbuffer_free也類似,只是加了一步先釋放orig_buffer指向的內存,然後再釋放evbuffer,這裏爲了節約篇幅就不放出來了,如果感興趣可以看一看。

evbuffer_add_buffer

該函數的作用是將一個緩衝區移接到另一個緩衝區末尾。在正式分析這個函數之前,先介紹一個宏函數SWAP,代碼如下:

#define SWAP(x, y) do { \
        (x)->buffer = (y)->buffer; \
        (x)->orig_buffer = (y)->orig_buffer; \
        (x)->misalign = (y)->misalign; \
        (x)->totallen = (y)->totallen; \
        (x)->off = (y)->off; \
} while(0);

該宏函數的作用就是將y的成員(除了回調函數及參數)的值賦給x。至於爲什麼要加do{...}while(0),可以參考我之前寫的博文:http://blog.csdn.net/move_now/article/details/73480195
接下來,我們就正式進入evbuffer_add_buffer函數(將inbuf緩衝區接在outbuf緩衝區後面):

int
evbuffer_add_buffer(struct evbuffer *outbuf, struct evbuffer *inbuf)
{
    //返回值,0代表成功,-1代表失敗
    int res;

    /* Short cut for better performance */
    /* 當outbuf的有效緩衝區大小爲0時
     * 直接將outbuf替換成inbuf
     */
    if (outbuf->off == 0) {
        struct evbuffer tmp;
        //oldoff用於記錄當前inbuf的off
        size_t oldoff = inbuf->off;

        /* Swap them directly */
        /* 類似temp = a
         * a = b
         * b = temp這樣的操作
         */
        SWAP(&tmp, outbuf);
        SWAP(outbuf, inbuf);
        SWAP(inbuf, &tmp);

        /*
         * Optimization comes with a price; we need to notify the
         * buffer if necessary of the changes. oldoff is the amount
         * of data that we transfered from inbuf to outbuf
         */
        //如果off發生了改變(即緩衝區發生了改變)並且回調函數不爲空,回調處理函數
        if (inbuf->off != oldoff && inbuf->cb != NULL)
            (*inbuf->cb)(inbuf, oldoff, inbuf->off, inbuf->cbarg);
        //與上面類似,不過換成了outbuf
        if (oldoff && outbuf->cb != NULL)
            (*outbuf->cb)(outbuf, 0, oldoff, outbuf->cbarg);

        return (0);
    }
    /* 當outbuf的緩衝區不爲0時,則不能簡單的進行替換了
     * 調用evbuffer_add將inbuf接到outbuf末尾
     * evbuffer_add調用成功返回0,失敗返回-1
     */
    res = evbuffer_add(outbuf, inbuf->buffer, inbuf->off);
    //調用成功,清除inbuf的有效緩衝區
    if (res == 0) {
        /* We drain the input buffer on success */
        evbuffer_drain(inbuf, inbuf->off);
    }

    return (res);
}

整理一下該函數的邏輯:

  1. 首先判斷outbuf的有效緩衝區大小是否爲0
  2. 如果爲0,則可以直接將outbuf替換成inbuf
  3. 如果不爲0,則將inbuf緩衝區接在outbuf末尾
  4. 若接入成功,則將inbuf的有效緩衝區清除了
  5. 若接入不成功,則返回-1,代表出錯

接下來,我們先分析其中調用的evbuffer_add,evbuffer_drain放在下一節講解。

evbuffer_add

int
evbuffer_add(struct evbuffer *buf, const void *data, size_t datlen)
{
    //接入後的緩衝區總大小
    size_t need = buf->misalign + buf->off + datlen;
    size_t oldoff = buf->off;
    /* 如果緩衝區的總大小比接入後的緩衝區總大小還小
     * 說明buf的緩衝區大小不足,需要調用evbuffer_expand進行擴展
     */
    if (buf->totallen < need) {
        if (evbuffer_expand(buf, datlen) == -1)
            return (-1);
    }
    /*
     * 將擴展部分的內存拷貝到有效緩衝區後面
     * 並更新off(有效緩衝區大小)的值
     */
    memcpy(buf->buffer + buf->off, data, datlen);
    buf->off += datlen;
    //除非datlen的大小爲0,否則肯定改變了buf的緩衝區大小,所以調用其回調函數
    if (datlen && buf->cb != NULL)
        (*buf->cb)(buf, oldoff, buf->off, buf->cbarg);

    return (0);
}

該函數有個不完美的地方,就是當datlen爲0的時候,直接返回就行了。邏輯還算清晰,整理如下:

  1. 判斷總的緩衝區大小能否滿足接入後的緩衝區大小
  2. 如果不滿足,則需要擴展緩衝區的大小,才能進行下一步操作
  3. 如果滿足,則無需擴展
  4. 擴展緩衝區
  5. 因緩衝區大小被改變,調用回調函數
  6. 返回

繼續引申下去,我們來看看擴展操作是如何執行的:

evbuffer_expand

int
evbuffer_expand(struct evbuffer *buf, size_t datlen)
{
        size_t need = buf->misalign + buf->off + datlen;

        /* If we can fit all the data, then we don't have to do anything */
        //緩衝區分配的總大小大於所需大小時,不需要分配,直接返回
        if (buf->totallen >= need)
                return (0);

        /*
         * If the misalignment fulfills our data needs, we just force an
         * alignment to happen.  Afterwards, we have enough space.
         */
        /* misalign的空間如果足夠大
         * 則直接使用misalign的空間
         * 別忘了使用緩衝區的時候,是從buffer開始的
         * 而misalign這段空間開始是空餘的
         * 若misalign的空間不夠則只有重新分配空間了
         */
        if (buf->misalign >= datlen) {
                evbuffer_align(buf);
        } else {
                void *newbuf;
                size_t length = buf->totallen;
                //一次最少分配256字節
                if (length < 256)
                        length = 256;
                //總大小小於需求,則直接申請總大小的2倍
                while (length < need)
                        length <<= 1;
                //在重新分配之前,先將buffer移到orig_buffer的位置(即申請的內存的起始位置)
                if (buf->orig_buffer != buf->buffer)
                        evbuffer_align(buf);
                //重新分配
                if ((newbuf = realloc(buf->buffer, length)) == NULL)
                        return (-1);
                //更新緩衝區成員的值
                buf->orig_buffer = buf->buffer = newbuf;
                buf->totallen = length;
        }

        return (0);
}

misalign大小的容量可以滿足datlen時,將使用misalign,否則的話,一次性最少分配256字節,如果總容量大小不夠,則將重新分配的大小設爲當前總容量的2倍,重新分配。

說一個題外話,在使用realloc的時候,最好先給一個空指針分配空間,分配成功了之後再指向這個分配成功的空間,這樣做是因爲realloc的調用還是比較容易失敗的,失敗了很有可能導致你原空間被破壞。

evbuffer_align函數就不細講了,它很簡單,內部就是調用memmove將buffer之後的off大小的內存移到orig_buffer地址上,然後將buffer置成orig_buffer,將misalign置成0。這裏我把代碼貼上,就省去了你再去翻源代碼的麻煩。

static void
evbuffer_align(struct evbuffer *buf)
{
        memmove(buf->orig_buffer, buf->buffer, buf->off);
        buf->buffer = buf->orig_buffer;
        buf->misalign = 0;
}

小節

在本小節中,我們主要介紹了有關緩衝區的概念,以及它的部分空間操作,比如轉接緩衝區以及緩衝區的擴展。接下來一個小節,我們將分析evbuffer_drain以及緩衝區的相關讀/寫操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章