Author : Kevin Lynx
前言
可以說對于任何網(wǎng)絡(luò)庫(模塊)而言,一個(gè)緩沖模塊都是必不可少的。緩沖模塊主要用于緩沖從網(wǎng)絡(luò)接收到的數(shù)據(jù),以及
用戶提交的數(shù)據(jù)(用于發(fā)送)。很多時(shí)候,我們還需要將網(wǎng)絡(luò)模塊層(非TCP層)的這些緩沖數(shù)據(jù)拷貝到用戶層,而這些內(nèi)存拷貝
都會(huì)消耗時(shí)間。
在這里,我簡要分析下libevent的相關(guān)代碼(event.h和buffer.c)。
結(jié)構(gòu)
關(guān)于libevent的緩沖模塊,主要就是圍繞evbuffer結(jié)構(gòu)體展開。先看下evbuffer的定義:

/**//*event.h*/

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


libevent的緩沖是一個(gè)連續(xù)的內(nèi)存區(qū)域,其處理數(shù)據(jù)的方式(寫數(shù)據(jù)和讀數(shù)據(jù))更像一個(gè)隊(duì)列操作方式:從后寫入,從前
讀出。evbuffer分別設(shè)置相關(guān)指針(一個(gè)指標(biāo))用于指示讀出位置和寫入位置。其大致結(jié)構(gòu)如圖:
orig_buffer指向由realloc分配的連續(xù)內(nèi)存區(qū)域,buffer指向有效數(shù)據(jù)的內(nèi)存區(qū)域,totallen表示orig_buffer指向的內(nèi)存
區(qū)域的大小,misalign表示buffer相對于orig_buffer的偏移,off表示有效數(shù)據(jù)的長度。
實(shí)際運(yùn)作
這里我將結(jié)合具體的代碼分析libevent是如何操作上面那個(gè)隊(duì)列式的evbuffer的,先看一些輔助函數(shù):
evbuffer_drain:
該函數(shù)主要操作一些指標(biāo),當(dāng)每次從evbuffer里讀取數(shù)據(jù)時(shí),libevent便會(huì)將buffer指針后移,同時(shí)增大misalign,減小off,
而該函數(shù)正是做這件事的。說白了,該函數(shù)就是用于調(diào)整緩沖隊(duì)列的前向指標(biāo)。
evbuffer_expand:
該函數(shù)用于擴(kuò)充evbuffer的容量。每次向evbuffer寫數(shù)據(jù)時(shí),都是將數(shù)據(jù)寫到buffer+off后,buffer到buffer+off之間已被
使用,保存的是有效數(shù)據(jù),而orig_buffer和buffer之間則是因?yàn)樽x取數(shù)據(jù)移動(dòng)指標(biāo)而形成的無效區(qū)域。
evbuffer_expand的擴(kuò)充策略在于,首先判斷如果讓出orig_buffer和buffer之間的空閑區(qū)域是否可以容納添加的數(shù)據(jù),如果
可以,則移動(dòng)buffer和buffer+off之間的數(shù)據(jù)到orig_buffer和orig_buffer+off之間(有可能發(fā)生內(nèi)存重疊,所以這里移動(dòng)調(diào)用的
是memmove),然后把新的數(shù)據(jù)拷貝到orig_buffer+off之后;如果不可以容納,那么重新分配更大的空間(realloc),同樣會(huì)移動(dòng)
數(shù)據(jù)。
擴(kuò)充內(nèi)存的策略為:確保新的內(nèi)存區(qū)域最小尺寸為256,且以乘以2的方式逐步擴(kuò)大(256、512、1024、...)。
了解了以上兩個(gè)函數(shù),看其他函數(shù)就比較簡單了。可以看看具體的讀數(shù)據(jù)和寫數(shù)據(jù):
evbuffer_add:
該函數(shù)用于添加一段用戶數(shù)據(jù)到evbuffer中。很簡單,就是先判斷是否有足夠的空閑內(nèi)存,如果沒有則調(diào)用evbuffer_expand
擴(kuò)充之,然后直接memcpy,更新off指標(biāo)。
evbuffer_remove:
該函數(shù)用于將evbuffer中的數(shù)據(jù)復(fù)制給用戶空間(讀數(shù)據(jù))。簡單地將數(shù)據(jù)memcpy,然后調(diào)用evbuffer_drain移動(dòng)相關(guān)指標(biāo)。
其他
回過頭看看libevent的evbuffer其實(shí)是非常簡單的(跟我那個(gè)kl_net里的buffer一樣),不知道其他人有沒有更優(yōu)的緩沖管理
方案。evbuffer還提供了兩個(gè)函數(shù):evbuffer_write和evbuffer_read,用于直接在套接字(其他文件描述符)上寫/讀數(shù)據(jù)。
另外,關(guān)于libevent,因?yàn)楣俜教峁┑腣C工程文件有問題,很多人在windows下編譯不過。金慶曾提供過一種方法。其實(shí)主要
就是修改event-config.h文件,修改編譯相關(guān)配置。這里我也提供一個(gè)解決步驟,順便提供完整包下載:
1. vs2005打開libevent.dsw,轉(zhuǎn)換四個(gè)工程(event_test, libevent, signal_test, time_test)
2. 刪除libevent項(xiàng)目中所有的文件,重新添加文件,文件列表如下:
buffer.c
evbuffer.c
evdns.c
evdns.h
event.c
event.h
event_tagging.c
event-config.h
event-internal.h
evhttp.h
evrpc.h
evrpc.c
evrpc-internal.h
evsignal.h
evutil.c
evutil.h
http.c
http-internal.h
log.c
log.h
min_heap.h
strlcpy.c
strlcpy-internal.h
tree.h
win32.c
config.h
signal.c
3. 替換event-config.h,使用libevent-iocp中的
4. 項(xiàng)目設(shè)置里添加HAVE_CONFIG_H預(yù)處理宏
5. 修改win32.c中win32_init函數(shù),加入WSAStartup函數(shù),類似于:
WSADATA wd;
int err;
struct win32op *winop;
size_t size;
if( ( err = WSAStartup( MAKEWORD( 2, 2 ), &wd ) ) != 0 )
event_err( 1, "winsock startup failed : %d", err );
6. 修改win32.c中win32_dealloc函數(shù),在函數(shù)末尾加上WSACleanup的調(diào)用:
WSACleanup();
6. 至此libevent編譯成功;
7. 幾個(gè)例子程序,只需要加入HAVE_CONFIG_H預(yù)處理宏,以及連接ws2_32.lib即可;
(time_test需要修改time-test.c文件,即在包含event.h前包含windows.h)
libevent-1.4.5-stable-vs2005.zip下載