一、Libevent簡介
Libevent是開源社區一款高性能的I/O框架庫,其具有如下特點:
1、跨平臺支持。Libevent支持Linux、UNIX和Windows。
2、統一事件源。libevent對i/o事件、信號和定時事件提供統一的處理。
3、線程安全。libevent使用libevent_pthreads庫來提供線程安全支持。
4、基于reactor模式的實現。
5、輕量級,專注于網絡,沒有ACE那么臃腫龐大
6、可以注冊事件優先級
二、Reactor 模式
2.1 Reactor簡介
首先來回想一下普通函數調用的機制:程序調用某函數->函數執行,程序等待->函數將結果和控制權返回給程->程序繼續處理。
Reactor 釋義“反應堆”,是一種事件驅動機制。和普通函數調用的不同之處在于:應用程序不是主動的調用某個 API 完成處理,而是恰恰相反,Reactor 逆置了事件處理流程,應用程序需要提供相應的接口并注冊到 Reactor 上,如果相應的事件發生,Reactor 將主動調用應用程序注冊的接口,這些接口又稱為“回調函數”。使用 Libevent 也是向 Libevent 框架注冊相應的事件和回調函數;當這些事件發生時,Libevent 會調用這些回調函數處理相應的事件(I/O 讀寫、定時和信號)。
2.2 Reactor 模式的優點
Reactor 模式是編寫高性能網絡服務器的必備技術之一,它具有如下的優點:
1)響應快,不必為單個同步時間所阻塞,雖然 Reactor 本身依然是同步的;
2)編程相對簡單,可以最大程度的避免復雜的多線程及同步問題,并且避免了多線程/ 進程的切換開銷;
3)可擴展性,可以方便的通過增加 Reactor 實例個數來充分利用 CPU 資源;
4)可復用性,reactor 框架本身與具體事件處理邏輯無關,具有很高的復用性;
2.3 Reactor 模式框架
使用 Reactor 模型,必備的幾個組件:事件源(描述符)、Reactor 框架、多路復用機制和事件處理程序,先來看看 Reactor 模型的整體框架,接下來再對每個組件做逐一說明。
1)事件源(handle)
由操作系統提供,用于識別每一個事件,如Socket描述符、文件描述符等。在Linux中,它用一個整數來表示。事件可以來自外部,如來自客戶端的連接請求、數據等。事件也可以來自內部,如定時器事件。
2)event demultiplexer——事件多路分發機制(同步事件分離器)
由操作系統提供的 I/O 多路復用機制,比如 select 和 epoll。 程序首先將其關心的句柄(事件源)及其事件注冊到 event demultiplexer 上; 當有事件到達時,event demultiplexer 會發出通知“在已經注冊的句柄集中,一個或多 個句柄的事件已經就緒”; 程序收到通知后,就可以在非阻塞的情況下對事件進行處理了。 對應到 libevent 中,依然是 select、poll、epoll 等,但是 libevent 使用結構體 eventop 進行了 封裝,以統一的接口來支持這些 I/O 多路復用機制,達到了對外隱藏底層系統機制的目的。
3)Reactor——反應器(管理器)
定義了一些接口,用于應用程序控制事件調度,以及應用程序注冊、刪除事件處理器和相關的描述符。它是事件處理器的調度核心。 Reactor管理器使用同步事件分離器來等待事件的發生。一旦事件發生,Reactor管理器先是分離每個事件,然后調度事件處理器,最后調用相關的模板函數來處理這個事件。
4) Event Handler——事件處理程序(事件處理器接口)
事件處理程序提供了一組接口,每個接口對應了一種類型的事件,供 Reactor 在相應的事件發生時調用,執行相應的事件處理。通常它會綁定一個有效的句柄。 對應到 libevent 中,就是 event 結構體。
5)具體的事件處理器
是事件處理器接口的實現。它實現了應用程序提供的某個服務。每個具體的事件處理器總和一個描述符相關。它使用描述符來識別事件、識別應用程序提供的服務。
2.4 Reactor 事件處理流程
三、libevent庫的使用
3.1 使用步驟
1、調用event_init函數創建event_base對象。一個event_base相當于一個reactor實例。
2、創建具體的事件處理器,并設置它們所從屬的reactor實例。evsignal_new和evtimer_new分別用于創建信號事件處理器和定時事件處理器,它們的統一入口是event_new函數,event_new函數成功時返回一個event類型的對象,也就是libevent的事件處理器
3、調用event_add函數,將事件處理器添加到注冊事件隊列中,并將該事件處理器對應的事件添加到事件多路分發器中。
4、調用event_base_dispatch函數來執行事件循環。
5、事件循環結束后,使用*_free系列函數來釋放系統資源。
3.2 事件處理流程
當應用程序向 libevent 注冊一個事件后,libevent 內部是怎么樣進行處理的呢?
下面的圖就給出了這一基本流程。
1)首先應用程序準備并初始化 event,設置好事件類型和回調函數;這對應于前面第步驟 2 和 3;
2)向 libevent 添加該事件 event。對于定時事件,libevent 使用一個小根堆管理,key 為超 時時間;對于 Signal 和 I/O 事件,libevent 將其放入到等待鏈表(wait list)中,這是一 個雙向鏈表結構;
3)程序調用 event_base_dispatch()系列函數進入無限循環,等待事件,以 select()函數為例; 每次循環前 libevent 會檢查定時事件的最小超時時間 tv,根據 tv 設置 select()的最大等待時間,以便于后面及時處理超時事件; 當 select()返回后,首先檢查超時事件,然后檢查 I/O 事件;
四、libevent 源代碼文件組織
4.1 源代碼組織結構
1)頭文件
主要就是 event.h:事件宏定義、接口函數聲明,主要結構體 event 的聲明;
2)內部頭文件
xxx-internal.h:內部數據結構和函數,對外不可見,以達到信息隱藏的目的;
3) libevent 框架
event.c: event 整體框架的代碼實現;
4)對系統 I/O 多路復用機制的封裝
epoll.c:對 epoll 的封裝;
select.c:對 select 的封裝;
devpoll.c:對 dev/poll 的封裝;
kqueue.c:對 kqueue 的封裝;
5)定時事件管理
min-heap.h:其實就是一個以時間作為 key 的小根堆結構;
6)信號管理
signal.c:對信號事件的處理;
7)輔助功能函數
evutil.h 和 evutil.c:一些輔助功能函數,包括創建 socket pair 和一些時間操作函數:加、減和比較等。
8)日志
log.h 和 log.c: log 日志函數
9)緩沖區管理
evbuffer.c 和 buffer.c: libevent 對緩沖區的封裝;
10)基本數據結構
compatsys 下的兩個源文件: queue.h 是 libevent 基本數據結構的實現,包括鏈表,雙向鏈表,隊列等; _libevent_time.h:一些用于時間操作的結構體定義、函數和宏定義;
11)實用網絡庫
http 和 evdns:是基于 libevent 實現的 http 服務器和異步 dns 查詢庫
五、libevent 的核心
5.1 ibevent 的核心---event
Libevent 是基于事件驅動(event-driven)的,從名字也可以看到 event 是整個庫的核心。 event 就是 Reactor 框架中的事件處理程序組件;它提供了函數接口,供 Reactor 在事件發生時調用,以執行相應的事件處理,通常它會綁定一個有效的句柄。 首先給出 event 結構體的聲明,它位于libevent-masterincludeevent2event_struct.h文件中:
/**
* ev_callback,event的回調函數,被ev_base調用,執行事件處理程序,這是一個函數指針,原型為:
* void (*ev_callback)(int fd, short events, void *arg)
* 其中參數fd對應于ev_fd;events對應于ev_events;
*
* 具體定義在上面
* 以下為一些常用的宏定義
* #define ev_pri ev_evcallback.evcb_pri
* #define ev_flags ev_evcallback.evcb_flags
* #define ev_closure ev_evcallback.evcb_closure
* #define ev_callback ev_evcallback.evcb_cb_union.evcb_callback
* #define ev_arg ev_evcallback.evcb_arg
*/
struct event_callback ev_evcallback; //event的回調函數,被ev_base調用
/* for managing timeouts */
/**
* min_heap_idx和ev_timeout,如果是timeout事件,它們是event在小根堆中的索引和超時值,
* libevent使用小根堆來管理定時事件
* 用來管理超時事件
*/
union {
// 公用超時隊列
TAILQ_ENTRY(event) ev_next_with_common_timeout;
// min_heap最小堆索引
int min_heap_idx;
} ev_timeout_pos;
evutil_socket_t ev_fd; //對于I/O事件,是綁定的文件描述符;對于signal事件,是綁定的信號;
short ev_events; //event關注的事件類型,它可以是以下3種類型:I/O事件、定時事件、信號、輔助選項(EV_PERSIST)
short ev_res; /* result passed to event callback 記錄了當前激活事件的類型*/
struct event_base *ev_base; //該事件所屬的反應堆實例,libevent句柄,每個事件都會保存一份句柄
/**
* 用共用體來同時表現IO事件和信號
* 以下為一些方便調用的宏定義
* mutually exclusive
* #define ev_signal_next ev_.ev_signal.ev_signal_next
* #define ev_io_next ev_.ev_io.ev_io_next
* #define ev_io_timeout ev_.ev_io.ev_timeout
*
* used only by signals
* #define ev_ncalls ev_.ev_signal.ev_ncalls
* #define ev_pncalls ev_.ev_signal.ev_pncalls
*/
union {
/* used for io events */
struct {
// 下一個io事件
LIST_ENTRY (event) ev_io_next; //使用雙向鏈表保存所有注冊的I/O和Signal事件
// 事件超時時間(既可以是相對時間,也可以是絕對時間)
struct timeval ev_timeout;
} ev_io;
/* used by signal events */
struct {
// 下一個信號
LIST_ENTRY (event) ev_signal_next;
short ev_ncalls; //事件就緒執行時,調用ev_callback的次數,通常為1;
/* Allows deletes in callback */
short *ev_pncalls; //指針,通常指向ev_ncalls或者為NULL
} ev_signal;
} ev_;
// 保存事件的超時時間
struct timeval ev_timeout;
};
下面詳細解釋一下結構體中各字段的含義,注意部分變量有可能位于event_callback結構體中。
1)ev_events:event關注的事件類型,它可以是以下3種類型:
- I/O事件:EV_WRITE和EV_READ
- 定時事件:EV_TIMEOUT
- 信號事件: EV_SIGNAL
- 輔助選項:EV_PERSIST,表明是一個永久事件
Libevent中的定義為:
* this flag to event_for new()/event_assign() to get a timeout. */
// 定時事件
#define EV_TIMEOUT 0x01
/** Wait for a socket or FD to become readable */
// 讀事件
#define EV_READ 0x02
/** Wait for a socket or FD to become writeable */
// 寫事件
#define EV_WRITE 0x04
/** Wait for a POSIX signal to be raised*/
// 信號事件
#define EV_SIGNAL 0x08
/**
* Persistent event: won't get removed automatically when activated.
*
* When a persistent event with a timeout becomes activated, its timeout
* is reset to 0.
*/
// 永久事件,激活執行后會重新加到隊列中等待下一次激活,否則激活執行后會自動移除
#define EV_PERSIST 0x10
/** Select edge-triggered behavior, if supported by the backend. */
// 邊沿觸發,一般需要后臺方法支持
#define EV_ET 0x20
/**
* If this option is provided, then event_del() will not block in one thread
* while waiting for the event callback to complete in another thread.
*
* To use this option safely, you may need to use event_finalize() or
* event_free_finalize() in order to safely tear down an event in a
* multithreaded application. See those functions for more information.
*
* THIS IS AN EXPERIMENTAL API. IT MIGHT CHANGE BEFORE THE LIBEVENT 2.1 SERIES
* BECOMES STABLE.
**/
// 終止事件,如果設置這個選項,則event_del不會阻塞,需要使用event_finalize或者
#define EV_FINALIZE 0x40
/**
* Detects connection close events. You can use this to detect when a
* connection has been closed, without having to read all the pending data
* from a connection.
*
* Not all backends support EV_CLOSED. To detect or require it, use the
* feature flag EV_FEATURE_EARLY_CLOSE.
**/
// 檢查事件連接是否關閉;可以使用這個選項來檢測鏈接是否關閉,而不需要讀取此鏈接所有未決數據;
#define EV_CLOSED 0x80
可以看出事件類型可以使用“|”運算符進行組合,需要說明的是,信號和I/O事件不能同時設置; 還可以看出libevent使用event結構體將這3種事件的處理統一起來;
2)ev_next,ev_active_next 和 ev_signal_next 都是雙向鏈表節點指針;它們是 libevent 對不同事件類型和在不同的時期,對事件的管理時使用到的字段。 libevent 使用雙向鏈表保存所有注冊的 I/O 和 Signal 事件,ev_next 就是該 I/O 事件在鏈表中的位置;稱此鏈表為“已注冊事件鏈表”; 同樣 ev_signal_next 就是 signal 事件在 signal 事件鏈表中的位置; ev_active_next:libevent 將所有的激活事件放入到鏈表 active list 中,然后遍歷 active list 執行調度,ev_active_next 就指明了 event 在 active list 中的位置;
3)min_heap_idx 和 ev_timeout,如果是 timeout 事件,它們是 event 在小根堆中的索引和超時值,libevent 使用小根堆來管理定時事件。
4)ev_base 該事件所屬的反應堆實例,這是一個 event_base 結構體。
5)ev_fd,對于 I/O 事件,是綁定的文件描述符;對于 signal 事件,是綁定的信號;
6)ev_callback,是一個結構體,里面包含有事件的回調函數(54-57行),這個回調函數被 ev_base 調用,執行事件處理程序,原型為:
//下一個回調事件
TAILQ_ENTRY(event_callback) evcb_active_next;
/**
*
* 回調事件的狀態標識,具體為:
* #define EVLIST_TIMEOUT 0x01 // event在time堆中,min_heap
* #define EVLIST_INSERTED 0x02 // event在已注冊事件鏈表中,event_base的queue中
* #define EVLIST_SIGNAL 0x04 // 未見使用
* #define EVLIST_ACTIVE 0x08 // event在激活鏈表中,event_base的active_queue中
* #define EVLIST_INTERNAL 0x10 // 內部使用標記
* #define EVLIST_ACTIVE_LATER 0x20 event在下一次激活鏈表中
* #define EVLIST_INIT 0x80 // event已被初始化
* #define EVLIST_ALL 0xff // 主要用于判斷事件狀態的合法性
*/
short evcb_flags;
// 回調函數的優先級,越小優先級越高
ev_uint8_t evcb_pri; /* smaller numbers are higher priority */
// 執行不同的回調函數
// /** @name Event closure codes
// Possible values for evcb_closure in struct event_callback
// @{
// */
// /** A regular event. Uses the evcb_callback callback 事件關閉時的回調函數模式類型 */
// // 常規事件,使用evcb_callback回調
// #define EV_CLOSURE_EVENT 0
// /** A signal event. Uses the evcb_callback callback */
// // 信號事件;使用evcb_callback回調
// #define EV_CLOSURE_EVENT_SIGNAL 1
// /** A persistent non-signal event. Uses the evcb_callback callback */
// // 永久性非信號事件;使用evcb_callback回調
// #define EV_CLOSURE_EVENT_PERSIST 2
// /** A simple callback. Uses the evcb_selfcb callback. */
// // 簡單回調,使用evcb_selfcb回調
// #define EV_CLOSURE_CB_SELF 3
// /** A finalizing callback. Uses the evcb_cbfinalize callback. */
// // 結束的回調,使用evcb_cbfinalize回調
// #define EV_CLOSURE_CB_FINALIZE 4
// /** A finalizing event. Uses the evcb_evfinalize callback. */
// // 結束事件回調,使用evcb_evfinalize回調
// #define EV_CLOSURE_EVENT_FINALIZE 5
// /** A finalizing event that should get freed after. Uses the evcb_evfinalize
// * callback. */
// // 結束事件之后應該釋放,使用evcb_evfinalize回調
// #define EV_CLOSURE_EVENT_FINALIZE_FREE 6
// /** @} */
ev_uint8_t evcb_closure;
/* allows us to adopt for different types of events */
// 允許我們自動適配不同類型的回調事件
union {
void (*evcb_callback)(evutil_socket_t, short, void *);
void (*evcb_selfcb)(struct event_callback *, void *);
void (*evcb_evfinalize)(struct event *, void *);
void (*evcb_cbfinalize)(struct event_callback *, void *);
} evcb_cb_union;
// 回調參數
void *evcb_arg;
};
7)ev_arg:void*,表明可以是任意類型的數據,在設置 event 時指定;
8)eb_flags:libevent 用于標記 event 信息的字段,表明其當前的狀態,可能的值有:
// 事件在time min_heap堆中
#define EVLIST_TIMEOUT 0x01
// 事件在已注冊事件鏈表中
#define EVLIST_INSERTED 0x02
// 目前未使用
#define EVLIST_SIGNAL 0x04
// 事件在激活鏈表中
#define EVLIST_ACTIVE 0x08
// 內部使用標記
#define EVLIST_INTERNAL 0x10
// 事件在下一次激活鏈表中
#define EVLIST_ACTIVE_LATER 0x20
// 事件已經終止
#define EVLIST_FINALIZING 0x40
// 事件初始化完成,但是哪兒都不在
#define EVLIST_INIT 0x80
// 包含所有事件狀態,用于判斷合法性的
#define EVLIST_ALL 0xff
9)ev_ncalls:事件就緒執行時,調用 ev_callback 的次數,通常為 1;
10)ev_pncalls:指針,通常指向 ev_ncalls 或者為 NULL;
11)ev_res:記錄了當前激活事件的類型
5.2 libevent 對 event 的管理
從event 結構體中的 3 個鏈表節點指針和一個堆索引出發,大體上也能窺出 libevent 對 event 的管理方法了,可以參見下面的示意圖。 每次當有事件 event 轉變為就緒狀態時,libevent 就會把它移入到 active event list[priority] 中,其中 priority 是 event 的優先級; 接著 libevent 會根據自己的調度策略選擇就緒事件,調用其 cb_callback()函數執行事件處理;并根據就緒的句柄和事件類型填充 cb_callback 函數的參數。
5.3 事件設置的接口函數
要向 libevent 添加一個事件,需要首先設置 event 對象,這通過調用 libevent 提供的函數有:event_set(), event_base_set(), event_priority_set()來完成;下面分別進行講解。
void event_set(struct event *ev, int fd, short events, void (*callback)(int, short, void *), void *arg)
1.設置事件 ev 綁定的文件描述符或者信號,對于定時事件,設為-1 即可;
2.設置事件類型,比如 EV_READ|EV_PERSIST, EV_WRITE, EV_SIGNAL 等;
3.設置事件的回調函數以及參數 arg;
4.初始化其它字段,比如缺省的 event_base 和優先級;
int event_base_set(struct event_base *base, struct event *ev)
設置 event ev 將要注冊到的 event_base;
libevent 有一個全局 event_base 指針 current_base,默認情況下事件 ev 將被注冊到 current_base 上,使用該函數可以指定不同的 event_base;
如果一個進程中存在多個 libevent 實例,則必須要調用該函數為 event 設置不同的 event_base;
int event_priority_set(struct event *ev, int pri)
設置event ev的優先級,沒什么可說的,注意的一點就是:當ev正處于就緒狀態時,不能設置,返回-1。
六、初見事件處理框架
前面已經對 libevent 的事件處理框架和 event 結構體做了描述,現在是時候剖析 libevent 對事件的詳細處理流程了,本節將分析 libevent 的事件處理框架 event_base 和 libevent 注冊、 刪除事件的具體流程,可結合前一節 libevent 對 event 的管理。
6.1 事件處理框架-event_base
回想 Reactor 模式的幾個基本組件,本節講解的部分對應于 Reactor 框架組件。在 libevent 中,這就表現為 event_base 結構體,結構體聲明如下,它位于libevent-masterevent-internal.h 文件中:
/** Function pointers and other data to describe this event_base's
* backend. */
/**
* 實際使用后臺方法的句柄,實際上指向的是靜態全局數組變量,從靜態全局變量eventops中選擇
*/
const struct eventop *evsel;
/** Pointer to backend-specific data. */
/**
* 指向后臺特定的數據,是由evsel->init返回的句柄
* 實際上是對實際后臺方法所需數據的封裝,void出于兼容性考慮
*/
void *evbase;
/** List of changes to tell backend about at next dispatch. Only used
* by the O(1) backends. */
// 告訴后臺方法下一次調度的變化列表
struct event_changelist changelist;
/** Function pointers used to describe the backend that this event_base
* uses for signals */
// 用于描述當前event_base用于信號的后臺方法
const struct eventop *evsigsel;
/** Data to implement the common signal handler code. */
// 用于實現公用信號句柄的代碼
struct evsig_info sig;
/** Number of virtual events */
// 虛擬事件的數量
int virtual_event_count;
/** Maximum number of virtual events active */
// 虛擬事件的最大數量
int virtual_event_count_max;
/** Number of total events added to this event_base */
// 添加到event_base上事件總數
int event_count;
/** Maximum number of total events added to this event_base */
// 添加到event_base上的最大個數
int event_count_max;
/** Number of total events active in this event_base */
// 當前event_base中活躍事件的個數
int event_count_active;
/** Maximum number of total events active in this event_base */
// 當前event_base中活躍事件的最大個數
int event_count_active_max;
/** Set if we should terminate the loop once we're done processing
* events. */
// 一旦我們完成處理事件了,如果我們應該終止loop,可以設置這個
int event_gotterm;
/** Set if we should terminate the loop immediately */
// 如果需要中止loop,可以設置這個變量
int event_break;
/** Set if we should start a new instance of the loop immediately. */
// 如果啟動新實例的loop,可以設置這個
int event_continue;
/** The currently running priority of events */
// 當前運行事件的優先級
int event_running_priority;
/** Set if we're running the event_base_loop function, to prevent
* reentrant invocation. */
// 防止event_base_loop重入的
int running_loop;
/** Set to the number of deferred_cbs we've made 'active' in the
* loop. This is a hack to prevent starvation; it would be smarter
* to just use event_config_set_max_dispatch_interval's max_callbacks
* feature */
/**
* 設置已經在loop中設置為’active’的deferred_cbs的個數,這是為了避免
* 饑餓的hack方法;只需要使用event_config_set_max_dispatch_interval’s的
* max_callbacks特征就可以變的更智能
*/
int n_deferreds_queued;
/* Active event management. // 活躍事件管理*/
/** An array of nactivequeues queues for active event_callbacks (ones
* that have triggered, and whose callbacks need to be called). Low
* priority numbers are more important, and stall higher ones.
* 存儲激活事件的event_callbacks的隊列,這些event_callbacks都需要調用;
* 數字越小優先級越高
*/
struct evcallback_list *activequeues;
/** The length of the activequeues array 活躍隊列的長度*/
int nactivequeues;
/** A list of event_callbacks that should become active the next time
* we process events, but not this time. */
// 下一次會變成激活狀態的回調函數的列表,但是當前這次不會調用
struct evcallback_list active_later_queue;
/* common timeout logic // 公用超時邏輯*/
/** An array of common_timeout_list* for all of the common timeout
* values we know.
* 公用超時事件列表,這是二級指針,每個元素都是具有同樣超時
* 時間事件的列表,
*/
struct common_timeout_list **common_timeout_queues;
/** The number of entries used in common_timeout_queues */
// 公用超時隊列中的項目個數
int n_common_timeouts;
/** The total size of common_timeout_queues. */
// 公用超時隊列的總個數
int n_common_timeouts_allocated;
/** Mapping from file descriptors to enabled (added) events */
// 文件描述符和事件之間的映射表
struct event_io_map io;
/** Mapping from signal numbers to enabled (added) events. */
// 信號數字和事件之間映射表
struct event_signal_map sigmap;
/** Priority queue of events with timeouts. */
// 事件超時的優先級隊列,使用最小堆實現
struct min_heap timeheap;
/** Stored timeval: used to avoid calling gettimeofday/clock_gettime
* too often. */
// 存儲時間:用來避免頻繁調用gettimeofday/clock_gettime
struct timeval tv_cache;
// monotonic格式的時間
struct evutil_monotonic_timer monotonic_timer;
/** Difference between internal time (maybe from clock_gettime) and
* gettimeofday. */
// 內部時間(可以從clock_gettime獲取)和gettimeofday之間的差異
struct timeval tv_clock_diff;
/** Second in which we last updated tv_clock_diff, in monotonic time. */
// 更新內部時間的間隔秒數
time_t last_updated_clock_diff;
#ifndef EVENT__DISABLE_THREAD_SUPPORT
/* threading support */
/** The thread currently running the event_loop for this base */
unsigned long th_owner_id;
/** A lock to prevent conflicting accesses to this event_base */
void *th_base_lock;
/** A condition that gets signalled when we're done processing an
* event with waiters on it. */
void *current_event_cond;
/** Number of threads blocking on current_event_cond. */
int current_event_waiters;
#endif
/** The event whose callback is executing right now */
// 當前執行的回調函數
struct event_callback *current_event;
#ifdef _WIN32
/** IOCP support structure, if IOCP is enabled. */
struct event_iocp_port *iocp;
#endif
/** Flags that this base was configured with */
// event_base配置的特征值
// 多線程調用是不安全的,單線程非阻塞模式
// EVENT_BASE_FLAG_NOLOCK = 0x01,
// 忽略檢查EVENT_*等環境變量
// EVENT_BASE_FLAG_IGNORE_ENV = 0x02,
// 只用于windows
// EVENT_BASE_FLAG_STARTUP_IOCP = 0x04,
// 不使用緩存的時間,每次回調都會獲取系統時間
// EVENT_BASE_FLAG_NO_CACHE_TIME = 0x08,
// 如果使用epoll方法,則使用epoll內部的changelist
// EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST = 0x10,
// 使用更精確的時間,但是可能性能會降低
// EVENT_BASE_FLAG_PRECISE_TIMER = 0x20
enum event_base_config_flag flags;
// 最大調度時間間隔
struct timeval max_dispatch_time;
// 最大調度的回調函數個數
int max_dispatch_callbacks;
// 優先級設置之后,對于活躍隊列中子隊列個數的限制
// 但是當子隊列個數超過這個限制之后,會以實際的回調函數個數為準
int limit_callbacks_after_prio;
/* Notify main thread to wake up break, etc. */
/** True if the base already has a pending notify, and we don't need
* to add any more. */
//如果為1表示當前可以喚醒主線程,否則不能喚醒主線程
int is_notify_pending;
/** A socketpair used by some th_notify functions to wake up the main
* thread. */
// 一端讀、一端寫,用來觸發喚醒事件
evutil_socket_t th_notify_fd[2];
/** An event used by some th_notify functions to wake up the main
* thread. */
// 喚醒event_base的event,被添加到監聽集合中的對象
struct event th_notify;
/** A function used to wake up the main thread from another thread. */
//執行喚醒操作的函數(不是喚醒event的回調函數)
int (*th_notify_fn)(struct event_base *base);
/** Saved seed for weak random number generator. Some backends use
* this to produce fairness among sockets. Protected by th_base_lock. */
// 保存弱隨機數產生器的種子。某些后臺方法會使用這個種子來公平的選擇sockets。
struct evutil_weakrand_state weakrand_seed;
/** List of event_onces that have not yet fired. */
LIST_HEAD(once_event_list, event_once) once_events;
};
下面詳細解釋一下結構體中部分字段的含義。
1)evsel 和 evbase 這兩個字段的設置可能會讓人有些迷惑,這里你可以把 evsel 和 evbase 看作是類和靜態函數的關系,比如添加事件時的調用行為:evsel->add(evbase, ev),實際執 行操作的是 evbase;這相當于 class::add(instance, ev),instance 就是 class 的一個對象實例。 evsel指向了全局變量static const struct eventop *eventops[]中的一個;libevent將系統提供的I/O demultiplex機制統一封裝成了eventop結構;因此 eventops[]包含了select、poll、kequeue和epoll等等其中的若干個全局實例對象。 evbase實際上是一個eventop實例對象; 先來看看eventop結構體,它的成員是一系列的函數指針, 在event-internal.h文件中:
struct eventop {
/** The name of this backend. 后臺方法名字,即epoll,select,poll等*/
const char *name;
/** Function to set up an event_base to use this backend. It should
* create a new structure holding whatever information is needed to
* run the backend, and return it. The returned pointer will get
* stored by event_init into the event_base.evbase field. On failure,
* this function should return NULL. */
/**
* 配置libevent句柄event_base使用當前后臺方法;他應該創建新的數據結構,
* 隱藏了后臺方法運行所需的信息,然后返回這些信息的結構體,為了支持多種
* 結構體,因此返回void*;返回的指針將保存在event_base.evbase中;如果失敗,
* 將返回NULL
*/
void *(*init)(struct event_base *);
/** Enable reading/writing on a given fd or signal. 'events' will be
* the events that we're trying to enable: one or more of EV_READ,
* EV_WRITE, EV_SIGNAL, and EV_ET. 'old' will be those events that
* were enabled on this fd previously. 'fdinfo' will be a structure
* associated with the fd by the evmap; its size is defined by the
* fdinfo field below. It will be set to 0 the first time the fd is
* added. The function should return 0 on success and -1 on error.
*/
/**
* 使給定的文件描述符或者信號變得可讀或者可寫。’events’將是我們嘗試添加的
* 事件類型:一個或者更多的EV_READ,EV_WRITE,EV_SIGNAL,EV_ET。’old’是這些事件
* 先前的事件類型;’fdinfo’將是fd在evmap中的輔助結構體信息,它的大小由下面的
* fdinfo_len給出。fd第一次添加時將設置為0.成功則返回0,失敗則返回-1
*/
int (*add)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
/** As "add", except 'events' contains the events we mean to disable. */
// 刪除事件
int (*del)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
/** Function to implement the core of an event loop. It must see which
added events are ready, and cause event_active to be called for each
active event (usually via event_io_active or such). It should
return 0 on success and -1 on error.
*/
/**
* event_loop實現的核心代碼。他必須察覺哪些添加的事件已經準備好,然后觸發每個
* 活躍事件都被調用(通常是通過event_io_active或者類似這樣)。成功返回0,失敗則-1
*/
int (*dispatch)(struct event_base *, struct timeval *);
/** Function to clean up and free our data from the event_base. */
// 清除event_base并釋放數據
void (*dealloc)(struct event_base *);
/** Flag: set if we need to reinitialize the event base after we fork.
*/
// 在執行fork之后是否需要重新初始化的標識位
int need_reinit;
/** Bit-array of supported event_method_features that this backend can
* provide. */
// 后臺方法可以提供的特征
// enum event_method_feature {
// 邊沿觸發
// EV_FEATURE_ET = 0x01,
// 要求事后臺方法在調度很多事件時大約為O(1)操作,select和poll無法提供這種特征,
// 這兩種方法具有N個事件時,可以提供O(N)操作
// EV_FEATURE_O1 = 0x02,
// 后臺方法可以處理各種文件描述符,而不僅僅是sockets
// EV_FEATURE_FDS = 0x04,
/** Require an event method that allows you to use EV_CLOSED to detect
* connection close without the necessity of reading all the pending data.
*
* Methods that do support EV_CLOSED may not be able to provide support on
* all kernel versions.
**/
// 要求后臺方法允許使用EV_CLOSED特征檢測鏈接是否中斷,而不需要讀取
// 所有未決數據;但是不是所有內核都能提供這種特征
// EV_FEATURE_EARLY_CLOSE = 0x08
// };
enum event_method_feature features;
/** Length of the extra information we should record for each fd that
has one or more active events. This information is recorded
as part of the evmap entry for each fd, and passed as an argument
to the add and del functions above.
*/
/**
* 應該為每個文件描述符保留的額外信息長度,額外信息可能包括一個或者多個
* 活躍事件。這個信息是存儲在每個文件描述符的evmap中,然后通過參數傳遞
* 到上面的add和del函數中。
*/
size_t fdinfo_len;
};
也就是說,在 libevent 中,每種 I/O demultiplex 機制的實現都必須提供這五個函數接口, 來完成自身的初始化、銷毀釋放;對事件的注冊、注銷和分發。 比如對于 epoll,libevent 實現了 5 個對應的接口函數,并在初始化時并將 eventop 的 5 個函數指針指向這 5 個函數,那么程序就可以使用 epoll 作為 I/O demultiplex 機制了,這個在后面會再次提到。
2)activequeues 是一個一級指針,前面講過 libevent 支持事件優先級,因此你可以把它看作是一維數組,其中的元素 activequeues[priority]是一個鏈表,鏈表的每個節點指向一個優先級為 priority 的就緒事件 event,實際上鏈表當中存放的是事件的回調函數。
3)io,管理IO事件的結構體變量。
4)sigmap 是由來管理信號的結構體,將在后面信號處理時專門講解;
5)timeheap 是管理定時事件的小根堆,將在后面定時事件處理時專門講解;
6)tv_cache 是 libevent 用于時間管理的變量,存儲時間:用來避免頻繁調用gettimeofday/clock_gettime;
6.2 創建和初始化 event_base
創建一個 event_base 對象也既是創建了一個新的 libevent 實例,程序需要通過調用 event_init()(內部調用 event_base_new 函數執行具體操作)函數來創建,該函數同時還對新生成的 libevent 實例進行了初始化。 該函數首先為 event_base 實例申請空間,然后初始化定時事件使用的mini-heap,選擇并初始化合適的系統 I/O 的 demultiplexer 機制,初始化各事件鏈表; 函數還檢測了系統的時間設置,為后面的時間管理打下基礎。
6.3 接口函數
前面提到 Reactor 框架的作用就是提供事件的注冊、注銷接口;根據系統提供的事件多路分發機制執行事件循環,當有事件進入“就緒”狀態時,調用注冊事件的回調函數來處理事件。 Libevent 中對應的接口函數主要就是:
int event_add(struct event *ev, const struct timeval *timeout);
int event_del(struct event *ev);
int event_base_loop(struct event_base *base, int loops);
void event_active(struct event *event, int res, short events);
void event_process_active(struct event_base *base);
6.3.1 注冊事件
函數原型:
* 事件注冊-event_add
* 1、將事件添加到等待事件中去,需要注意的是,event_add在event_new或者event_assign之后執行,
* 即添加的事件必須是經過基本初始化過后的事件;
* 2、此處添加的事件包括IO事件、信號事件、定時事件,根據事件申請時設置的事件類型決定添加的流程;
* 3、超時控制包括兩種方式:
* (1)最小堆:時間超時時間存儲在最小堆,每次執行超時任務都從最小堆堆頂取任務執行
* (2)最小堆+公用超時隊列:相同超時的任務存儲在同一個超時隊列,每一個超時隊列的隊首事件存儲在最小堆,
* 每次執行超時任務時都從最小堆堆頂取任務執行,然后遍歷執行該任務所在公用超時隊列中的所有超時任務。
*/
int event_add(struct event *ev, const struct timeval *tv)
參數:ev:指向要注冊的事件;
tv:超時時間;
函數將 ev 注冊到 ev->ev_base 上,事件類型由 ev->ev_events 指明,如果注冊成功,ev 將被插入到已注冊鏈表中;如果 tv 不是 NULL,則會同時注冊定時事件,將 ev 添加到 timer 堆上; 如果其中有一步操作失敗,那么函數保證沒有事件會被注冊,可以講這相當于一個原子操作。
{
int res;
if (EVUTIL_FAILURE_CHECK(!ev->ev_base)) {
event_warnx("%s: event has no event_base set.", __func__);
return -1;
}
EVBASE_ACQUIRE_LOCK(ev->ev_base, th_base_lock);//加鎖
// 實際時調用內部實現函數event_add_nolock實現的,下文會分析
res = event_add_nolock_(ev, tv, 0);
EVBASE_RELEASE_LOCK(ev->ev_base, th_base_lock);
return (res);
}
event_add_nolock()函數
* except: 1) it requires that we have the lock. 2) if tv_is_absolute is set,
* we treat tv as an absolute time, not as an interval to add to the current
* time
* 此函數真正實現將事件添加到event_base的等待列表中。
* 真正的將信號事件注冊在event_base上,也就是進行了信號的內部事件注冊
*
* 添加事件的實現函數;就像event_add一樣,異常:
* 1)它需要使用者加鎖;2)如果tv_is_absolute設置了,則將tv作為絕對時間對待,而不是相對于當前添加時間的時間間隔
*/
int
event_add_nolock_(struct event *ev, const struct timeval *tv,
int tv_is_absolute)
{
struct event_base *base = ev->ev_base;
int res = 0;
int notify = 0;
EVENT_BASE_ASSERT_LOCKED(base);
event_debug_assert_is_setup_(ev);
event_debug((
"event_add: event: %p (fd "EV_SOCK_FMT"), %s%s%s%scall %p",
ev,
EV_SOCK_ARG(ev->ev_fd),
ev->ev_events & EV_READ ? "EV_READ " : " ",
ev->ev_events & EV_WRITE ? "EV_WRITE " : " ",
ev->ev_events & EV_CLOSED ? "EV_CLOSED " : " ",
tv ? "EV_TIMEOUT " : " ",
ev->ev_callback));
// 事件狀態必須處于合法的某種事件狀態,否則報錯
EVUTIL_ASSERT(!(ev->ev_flags & ~EVLIST_ALL));
// 已經處于結束狀態的事件再次添加會報錯
if (ev->ev_flags & EVLIST_FINALIZING) {
/* XXXX debug */
return (-1);
}
/*
* prepare for timeout insertion further below, if we get a
* failure on any step, we should not change any state.
*/
/**
* 為超時插入做準備,如果超時控制不為空,且事件沒有處于超時狀態
* 首先為將要插入的超時事件準備插入節點,主要是為了防止后面出現這種情況:
* 事件狀態改變已經完成,但是最小堆申請節點卻失敗;
* 因此,如果在任何一步出現錯誤,都不能改變事件狀態,這是前提條件。
*/
if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {
if (min_heap_reserve_(&base->timeheap,
1 + min_heap_size_(&base->timeheap)) == -1)
return (-1); /* ENOMEM == errno */
}
/* If the main thread is currently executing a signal event's
* callback, and we are not the main thread, then we want to wait
* until the callback is done before we mess with the event, or else
* we can race on ev_ncalls and ev_pncalls below. */
/**
* 如果主線程當前正在執行信號事件的回調函數,同時又不在主線程,則
* 需要等待回調函數執行完畢才能繼續添加事件,否則可能會在
* ev_ncalls和ev_pncalls上產生競爭。
*/
#ifndef EVENT__DISABLE_THREAD_SUPPORT
if (base->current_event == event_to_event_callback(ev) &&
(ev->ev_events & EV_SIGNAL)
&& !EVBASE_IN_THREAD(base)) {
++base->current_event_waiters;
EVTHREAD_COND_WAIT(base->current_event_cond, base->th_base_lock);
}
#endif
/**
* 如果事件類型是IO事件/信號事件,同時事件狀態不是已經插入/激活/下一次激活狀態,
* 則根據事件類型將事件添加到不同的映射表或者隊列中
*/
if ((ev->ev_events & (EV_READ|EV_WRITE|EV_CLOSED|EV_SIGNAL)) &&
!(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE|EVLIST_ACTIVE_LATER))) {
// 如果事件是IO事件,則將事件插入到IO事件與文件描述符的映射表中
if (ev->ev_events & (EV_READ|EV_WRITE|EV_CLOSED))
res = evmap_io_add_(base, ev->ev_fd, ev);
// 如果事件是信號事件,則將事件插入信號與文件描述符的映射表中
else if (ev->ev_events & EV_SIGNAL)
res = evmap_signal_add_(base, (int)ev->ev_fd, ev);
// 如果上述添加行為正確,則將事件插入到event_base的事件列表中
if (res != -1)
event_queue_insert_inserted(base, ev);//其實就是這是事件的已注冊標志
/**
* 如果上述添加行為正確,則設置通知主線程的標志,因為已經添加了新事件,
* 防止1)優先級高的事件被優先級低的事件倒掛,2)防止主線程忙等,會通知主線程有新事件
*/
if (res == 1) {
/* evmap says we need to notify the main thread. */
notify = 1;
res = 0;
}
}
/*
* we should change the timeout state only if the previous event
* addition succeeded.
* 只有當前面事件條件成功執行之后,才能改變超時狀態
*/
if (res != -1 && tv != NULL) {
struct timeval now;
int common_timeout;
#ifdef USE_REINSERT_TIMEOUT
int was_common;
int old_timeout_idx;
#endif
/*
* for persistent timeout events, we remember the
* timeout value and re-add the event.
*
* If tv_is_absolute, this was already set.
*
* 對于持久化的定時事件,需要記住超時時間,并重新注冊事件
* 如果tv_is_absolute設置,則事件超時時間就等于輸入時間參數
*/
if (ev->ev_closure == EV_CLOSURE_EVENT_PERSIST && !tv_is_absolute)
ev->ev_io_timeout = *tv;
/**
* 如果沒有使用USE_REINSERT_TIMEOUT,則當事件處于超時狀態時,需要從隊列中移除事件
* 因為同樣的事件不能重新插入,所以當一個事件已經處于超時狀態時,為防止執行,需要先移除后插入
*/
#ifndef USE_REINSERT_TIMEOUT
if (ev->ev_flags & EVLIST_TIMEOUT) {
event_queue_remove_timeout(base, ev);
}
#endif
/* Check if it is active due to a timeout. Rescheduling
* this timeout before the callback can be executed
* removes it from the active list.
* 檢查事件當前狀態是否已經激活,而且是超時事件的激活狀態,
* 則在回調函數執行之前,需要重新調度這個超時事件,因此需要把它移出激活隊列
* */
if ((ev->ev_flags & EVLIST_ACTIVE) &&
(ev->ev_res & EV_TIMEOUT)) {
if (ev->ev_events & EV_SIGNAL) {
/* See if we are just active executing
* this event in a loop
*/
if (ev->ev_ncalls && ev->ev_pncalls) {
/* Abort loop */
*ev->ev_pncalls = 0;
}
}
// 將此事件的回調函數從激活隊列中移除
event_queue_remove_active(base, event_to_event_callback(ev));
}
// 獲取base中的緩存時間
gettime(base, &now);
// 檢查base是否使用了公用超時隊列機制
common_timeout = is_common_timeout(tv, base);
#ifdef USE_REINSERT_TIMEOUT
was_common = is_common_timeout(&ev->ev_timeout, base);
old_timeout_idx = COMMON_TIMEOUT_IDX(&ev->ev_timeout);
#endif
/**
* 1)如果設置絕對超時時間,則設置時間超時時間為輸入時間參數
* 2)如果使用的公用超時隊列機制,則根據當前base中時間和輸入超時時間間隔計算出時間超時時間,
* 并對超時時間進行公用超時掩碼計算
* 3)如果是其他情況,則直接根據base中時間和輸入超時時間間隔計算事件的超時時間
*/
if (tv_is_absolute) {
ev->ev_timeout = *tv;
} else if (common_timeout) {
struct timeval tmp = *tv;
tmp.tv_usec &= MICROSECONDS_MASK;
evutil_timeradd(&now, &tmp, &ev->ev_timeout);
ev->ev_timeout.tv_usec |=
(tv->tv_usec & ~MICROSECONDS_MASK);
} else {
evutil_timeradd(&now, tv, &ev->ev_timeout);
}
event_debug((
"event_add: event %p, timeout in %d seconds %d useconds, call %p",
ev, (int)tv->tv_sec, (int)tv->tv_usec, ev->ev_callback));
// 將事件插入超時隊列
#ifdef USE_REINSERT_TIMEOUT
// event_queue_reinsert_timeout會插入兩個隊列:一個是公用超時隊列,一個超時隊列
event_queue_reinsert_timeout(base, ev, was_common, common_timeout, old_timeout_idx);
#else
// 只會插入超時隊列
event_queue_insert_timeout(base, ev);
#endif
/**
* 如果使用了公用超時隊列機制,則需要根據當前事件的超時時間將當前事件插入具有相同超時時間的時間列表
*/
if (common_timeout) {
// 根據事件超時時間獲取應該插入的公用超時隊列,注意此處是從隊尾插入
struct common_timeout_list *ctl =
get_common_timeout_list(base, &ev->ev_timeout);
/**
* 如果當前事件是公用超時隊列的第一個事件,則因此需要將此超時事件插入最小堆
* 解釋:公用超時隊列機制:處于同一個公用超時隊列中的所有事件具有相同的超時控制,因此只需要將公用超時隊列
* 的第一個事件插入最小堆,當超時觸發時,可以通過遍歷公用超時隊列獲取同樣的超時事件。
*/
if (ev == TAILQ_FIRST(&ctl->events)) {
common_timeout_schedule(ctl, &now, ev);
}
} else {
// 如果沒有使用公用超時隊列,則調整最小堆
struct event* top = NULL;
/* See if the earliest timeout is now earlier than it
* was before: if so, we will need to tell the main
* thread to wake up earlier than it would otherwise.
* We double check the timeout of the top element to
* handle time distortions due to system suspension.
* 查看當前事件是否位于最小堆根部,如果是,則需要通知主線程
* 否則,需要查看最小堆根部超時時間是否已經小于當前時間,即已經超時了,如果是,則需要通知主線程
*/
if (min_heap_elt_is_top_(ev))
notify = 1;
else if ((top = min_heap_top_(&base->timeheap)) != NULL &&
evutil_timercmp(&top->ev_timeout, &now, <))
notify = 1;
}
}
/**
* if we are not in the right thread, we need to wake up the loop
* 如果本線程不是執行event_loop的主線程,就通知主線程
* */
if (res != -1 && notify && EVBASE_NEED_NOTIFY(base))
evthread_notify_base(base);
event_debug_note_add_(ev);
return (res);
}
6.3.2 刪除事件
函數原型為:
該函數將刪除事件 ev,對于 I/O 事件,從 I/O 的 demultiplexer 上將事件注銷;對于 Signal 事件,將從 Signal 事件鏈表中刪除;對于定時事件,將從堆上刪除; 同樣刪除事件的操作則不一定是原子的,比如刪除時間事件之后,有可能從系統 I/O 機 制中注銷會失敗。
event_del_(struct event *ev, int blocking)
{
int res;
struct event_base *base = ev->ev_base;
if (EVUTIL_FAILURE_CHECK(!base)) {
event_warnx("%s: event has no event_base set.", __func__);
return -1;
}
EVBASE_ACQUIRE_LOCK(base, th_base_lock);
res = event_del_nolock_(ev, blocking);
EVBASE_RELEASE_LOCK(base, th_base_lock);
return (res);
}
-
框架
+關注
關注
0文章
403瀏覽量
17515 -
函數
+關注
關注
3文章
4341瀏覽量
62806 -
線程
+關注
關注
0文章
505瀏覽量
19715
發布評論請先 登錄
相關推薦
評論