這兩天大致看了看
libevent的代碼,簡(jiǎn)單做一個(gè)分析.
libevent最大的特點(diǎn)就是封裝了對(duì)以下三種事件的響應(yīng):IO事件,定時(shí)器事件,信號(hào)事件.這里就分析libevent如果做到這一點(diǎn)的,在libevent中還包括一些其他的功能(如緩沖區(qū)),但是我這里就重點(diǎn)講解這一部分了.
事件原型,簡(jiǎn)單看一看用于封裝事件的結(jié)構(gòu)體定義:
struct event {
TAILQ_ENTRY (event) ev_next;
TAILQ_ENTRY (event) ev_active_next;
TAILQ_ENTRY (event) ev_signal_next;
unsigned int min_heap_idx; /* for managing timeouts */
struct event_base *ev_base;
int ev_fd;
short ev_events;
short ev_ncalls;
short *ev_pncalls; /* Allows deletes in callback */
struct timeval ev_timeout;
int ev_pri; /* smaller numbers are higher priority */
void (*ev_callback)(int, short, void *arg);
void *ev_arg;
int ev_res; /* result passed to event callback */
int ev_flags;
};
其中的ev_callback就是回調(diào)函數(shù),也就是說(shuō)當(dāng)所關(guān)注的事件發(fā)生時(shí)所要觸發(fā)的函數(shù)是注冊(cè)到這個(gè)函數(shù)指針中的.
1)IO事件:再簡(jiǎn)單不過(guò)了,對(duì)select/epoll/poll等之類(lèi)的調(diào)用進(jìn)行封裝即可,所提供的接口無(wú)非這幾種:
struct eventop {
const char *name;
void *(*init)(struct event_base *);
int (*add)(void *, struct event *);
int (*del)(void *, struct event *);
int (*dispatch)(struct event_base *, void *, struct timeval *);
void (*dealloc)(struct event_base *, void *);
/* set if we need to reinitialize the event base */
int need_reinit;
};
在我看過(guò)的很多開(kāi)源服務(wù)器源碼(如lighttpd)中都有類(lèi)似的封裝,不是什么新鮮的東西.
2)定時(shí)器事件:libevent采用堆數(shù)據(jù)結(jié)構(gòu)存放所要定時(shí)的事件的時(shí)間,大家知道堆可以用來(lái)實(shí)現(xiàn)優(yōu)先隊(duì)列,在這里,所有的定時(shí)器就放在這樣的一個(gè)數(shù)據(jù)結(jié)構(gòu)中了.
3)信號(hào)事件:所有的信號(hào)都注冊(cè)回調(diào)函數(shù)為evsignal_handler(在signal.c中),這個(gè)函數(shù)的功能就是在某信號(hào)被觸發(fā)的時(shí)候?qū)⒃撔盘?hào)被觸發(fā)的計(jì)數(shù)器加1,同時(shí)置一個(gè)標(biāo)志位表示有信號(hào)被觸發(fā).
現(xiàn)在,把所有這些結(jié)合起來(lái),看看libevent框架的主循環(huán)是如何工作的,用簡(jiǎn)單的偽碼表示:
主循環(huán)
更新當(dāng)前時(shí)間
將當(dāng)前時(shí)間與存放時(shí)間的堆中的時(shí)間依次進(jìn)行比較,由于是采用堆實(shí)現(xiàn)的,這里查找相當(dāng)?shù)目?于是所有可以被觸發(fā)的定時(shí)器事件都從堆中被取出,同時(shí)取下的事件被放到一個(gè)活動(dòng)事件的隊(duì)列中
調(diào)用封裝IO操作的dispatch函數(shù),在其中也將被觸發(fā)的IO事件加入到那個(gè)存放活動(dòng)事件的隊(duì)列中
在dispatch的函數(shù)中如果信號(hào)被觸發(fā)的標(biāo)志位被置位,說(shuō)明有信號(hào)被觸發(fā),調(diào)用evsignal_process函數(shù),這個(gè)函數(shù)的功能也是把所有被觸發(fā)的事件放到活動(dòng)事件的隊(duì)列中
好了,現(xiàn)在所有可以被觸發(fā)的事件都在活動(dòng)事件隊(duì)列中了,依次遍歷取出來(lái)調(diào)用它們注冊(cè)的回調(diào)函數(shù)就成了.
上面就是libevent處理這三種事件的大體框架.
說(shuō)一說(shuō)我認(rèn)為這個(gè)框架存在的缺點(diǎn):
1) callback函數(shù)只能有一個(gè),假設(shè)這樣一個(gè)場(chǎng)景,我需要對(duì)某個(gè)連接socket同時(shí)監(jiān)控它的可讀/可寫(xiě)/超時(shí)事件,那么我需要針對(duì)同一個(gè)socket fd生成三個(gè)event對(duì)象.
2) 在主循環(huán)中,每次都要去查詢(xún)存放時(shí)間的堆看看有沒(méi)有定時(shí)器事件可以被觸發(fā),問(wèn)題在于,很多時(shí)候,一個(gè)主循環(huán)很快就到了下一次,而時(shí)間過(guò)去的并不多,這次去檢查時(shí)間是冗余的操作,當(dāng)然了,由于libevent的定時(shí)器是精確到毫秒級(jí)別的,所以有這么做的必要,但是在一個(gè)真正的服務(wù)器中,我懷疑有多少需要精確到微秒級(jí)別的事件,所以呢,我覺(jué)得這個(gè)可以做一個(gè)改進(jìn),每次更新時(shí)間之后跟上一次更新的時(shí)間做一個(gè)比較,如果超過(guò)了一秒(或者把這個(gè)間隔改成可以由使用者配置的)再去檢查堆上面的時(shí)間.