前言
evbuffer是libevent的緩衝區部分,它主要是負責緩衝區的構建等操作,而bufferevent主要負責管理輸入輸出緩衝區,相當於是對evbuffer做的一層抽象,緩衝區對於一個網絡庫幾乎是必需的。我們來介紹關於evbuffer
部分,部分主要集中在event.h
和buffer.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);
}
整理一下該函數的邏輯:
- 首先判斷outbuf的有效緩衝區大小是否爲0
- 如果爲0,則可以直接將outbuf替換成inbuf
- 如果不爲0,則將inbuf緩衝區接在outbuf末尾
- 若接入成功,則將inbuf的有效緩衝區清除了
- 若接入不成功,則返回-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的時候,直接返回就行了。邏輯還算清晰,整理如下:
- 判斷總的緩衝區大小能否滿足接入後的緩衝區大小
- 如果不滿足,則需要擴展緩衝區的大小,才能進行下一步操作
- 如果滿足,則無需擴展
- 擴展緩衝區
- 因緩衝區大小被改變,調用回調函數
- 返回
繼續引申下去,我們來看看擴展操作是如何執行的:
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
以及緩衝區的相關讀/寫操作。