event_base是libevent的事件驅動,也是Reactor模式的直接體現。任何使用libevent的代碼最開始都需要建立一個base,之後的任何介面函數都和這個base關聯著,下面是struct event_base的定義
struct event_base { /* io多工函數的統一介面 */ const struct eventop *evsel; /* io多工函數的資料 */ void *evbase; /* 訊號處理的統一介面 */ const struct eventop *evsigsel; /* 訊號處理的資料 */ struct evsig_info sig; /* 註冊到base中的event,不包括內部event */ int event_count; /* 啟用的event個數 */ int event_count_active; /* 儲存通訊端/描述符以及對應的事件的map */ struct event_io_map io; /* 儲存訊號的map */ struct event_signal_map sigmap; /* 註冊隊列 */ struct event_list eventqueue; /* 啟用隊列 */ struct event_list *activequeues; /* 最小堆 */ struct min_heap timeheap; /* ... */};
struct event_base中主要包括上述變數,io多工操作,訊號操作,各種map,隊列,最小堆等等
其他的部分就是在程式設計過程中進行的各種判斷,各種標誌多一些
event_base的初始化是通過內部調用event_base_with_new_config實現的,函數意思是帶有一些配置構造一個event_base,配置主要包括的就是使用者不想要libevent使用的io複用函數名字。
函數中主要還是各種初始化,然後選擇合適的io複用函數等
使用者通過event_base_dispatch函數開啟事件驅動的主迴圈,內部調用event_base_loop執行無線迴圈,也沒有什麼特別的 選擇io複用函數的阻塞時間長度 調用io函數 判斷最小堆中的event 處理所有啟用的event
下面主要看一下如何處理啟用event
/* * 由base主迴圈調用,用來處理在base的啟用隊列中的event, * 根據優先順序遍曆base的啟用隊列數組,對於每一個隊列,調用event_process_active_single_queue處理 */static intevent_process_active(struct event_base *base){ /* Caller must hold th_base_lock */ struct event_list *activeq = NULL; int i, c = 0; for (i = 0; i < base->nactivequeues; ++i) { if (TAILQ_FIRST(&base->activequeues[i]) != NULL) { base->event_running_priority = i; activeq = &base->activequeues[i]; c = event_process_active_single_queue(base, activeq); } } return c;}
/* * 處理啟用/逾時event的主要函數 * 首先根據event是否是永久event判斷是否需要將event從base的所有隊列中刪除 * 如果是永久隊列,則只需要從base的啟用隊列中刪除 * 然後才開始調用event的回呼函數 * * 注意:此處需要處理具有逾時時間的event,因為最小堆中記錄的是絕對時間, * 所以如果這個event是具有逾時時間的event,那麼它是在從base中所有隊列中刪除後才加入到 * 啟用隊列中的,見timeout_process() * 所以需要再將其添加到base中,重新調用event_add_internal即可,見event_persist_closure * * 返回處理了多少個非內部event */static intevent_process_active_single_queue(struct event_base *base, struct event_list *activeq){ struct event *ev; int count = 0; for (ev = TAILQ_FIRST(activeq); ev; ev = TAILQ_FIRST(activeq)) { /* 如果是永久event,只需要從啟用隊列中刪除 */ if (ev->ev_events & EV_PERSIST) event_queue_remove(base, ev, EVLIST_ACTIVE); /* 否則就要從base的所有隊列中刪除,包括啟用隊列, 因為只處理一次 */ else event_del_internal(ev); /* * 如果是訊號或者永久event需要單獨處理,而其他event在刪除完之後直接調用回呼函數就可以了 * 以後就不再管這個event了,因為只處理一次 * ev_closure用於判斷是否是永久/訊號event,在event_new中賦值 */ switch (ev->ev_closure) { case EV_CLOSURE_SIGNAL: /* 訊號的特殊處理,調用使用者的訊號處理函數 */ event_signal_closure(base, ev); break; case EV_CLOSURE_PERSIST://io event /* 處理io事件,主要是特別處理有逾時時間的event,需要重新計算絕對時間,然後調用回呼函數 */ event_persist_closure(base, ev); break; } } return count;}
函數處理當前隊列中的每一個event,首先將其從啟用隊列中刪除,如果是一次性事件,則從base中刪除
然後就根據訊號/其他永久io事件調用相應的處理函數
總結
event_base是整個事件驅動的載體,但是使用時僅僅需要event_base_new,和event_base_dispatch即可,內部實現了對事件的監控,對逾時event的統一處理,對所有啟用event按優先順序處理,處理時再細分是永久/一次性event,io/signal event等。這種先統一再分散的好處是可以在一個函數中統一處理啟用event,實現更好的封裝介面,也更容易理清思路。
對訊號的處理需要特別注意,因為不是一有訊號發生馬上調用使用者的回呼函數,中間其實進行了兩次base迴圈,這是為了將訊號統一到event的結果,不然就直接sigaction綁定不是很好。
具有逾時時間的event在處理時也是需要注意的部分,需要重新計算逾時時間,然後使用event_add_internal重新添加(event在timeout_process已經被刪除了),這就回到了event_add調用的event_add_internal上,如果不是逾時event就僅僅是調用回呼函數而已。
整體思路很容易理清,重點是細節方面,需要仔細琢磨