libevent源码学习-----event操作
libevent核心結構是event_base和event,接下來主要介紹event結構
/* event的定義的主要部分 */ struct event {/* ... *//* event監聽的描述符,也可以是信號值 */evutil_socket_t ev_fd;/* 事件驅動主循環 */struct event_base *ev_base;short ev_events;short ev_res; /* result passed to event callback */short ev_flags;ev_uint8_t ev_pri; /* smaller numbers are higher priority */union {/* used for io events */struct {TAILQ_ENTRY(event) ev_io_next;struct timeval ev_timeout;} ev_io;/* used by signal events */struct {TAILQ_ENTRY(event) ev_signal_next;short ev_ncalls;/* Allows deletes in callback */short *ev_pncalls;} ev_signal;} _ev;struct timeval ev_timeout;/* allows us to adopt for different types of events */void (*ev_callback)(evutil_socket_t, short, void *arg);void *ev_arg; };程序中使用event最開始需要event_new創建一個event
/* 創建事件驅動 */ struct event_base* base = event_base_new(); /**創建一個事件*@param base: 事件驅動*@param fd: event對應的文件描述符,通常是通過socket創建的套接字*@param EV_READ: 想要監聽fd的哪些事件,EV_READ表示監聽fd是否可讀,也可以是EV_PERSIST代表這個event是永久事件,在調用一次回調函數后仍然繼續監聽,對應一次性event,調用后不再監聽*@param cb: 當fd對應的事件發生后調用的回調函數,用戶提供*@param arg: 傳給回調函數cb的參數*/ struct event* ev = event_new(base, fd, EV_READ|EV_PERSIST, cb, arg);接下來一個個解釋每個變量的作用
evutil_socket_t ev_fd;
event負責監聽的描述符,也可以是信號值
struct event_base *ev_base;
事件驅動base
short ev_events;
對應監聽fd的某些事件,上述代碼中是EV_READ | EV_PERSIST,可用的events包括
- EV_READ:fd可讀
- EV_WRITE:fd可寫
- EV_PERSIST:永久事件,激活一次后仍然繼續監聽,對應一次事件,激活一次后不再監聽
- EV_SIGNAL:代表這個event監聽的是一個信號
- EV_TIMEOUT:代表這個event具有超時時長
short ev_res;
當event被激活時,ev_res的值記錄著是被哪些事件(上述)激活,即激活的原因
short ev_flags;
event處于的狀態,其實是event都在哪幾個隊列中(base中有多個隊列),可以是以下幾種的或運算
- EVLIST_INIT:表示event剛被初始化,不在任何隊列中,通常是剛調用完event_new
- EVLIST_INSERTED:表示event處于base的注冊隊列中,通常是調用event_add后
- EVLIST_ACTIVE:表示event處于base的激活隊列中,通常是event被激活,等待調用回調函數
- EVLIST_TIMEOUT:表示event處于最小堆中,表示event具有超時時間
- EVLIST_ALL:私有空間,不明
ev_uint8_t ev_pri;
event的優先級,base的激活隊列是一個數組,每個數組元素是一個隊列,數組下標越低優先級越高,在統一處理激活event時,從優先級高的event開始調用回調函數
_ev
union {/* used for io events */struct {TAILQ_ENTRY(event) ev_io_next;struct timeval ev_timeout;} ev_io;/* used by signal events */struct {TAILQ_ENTRY(event) ev_signal_next;short ev_ncalls;/* Allows deletes in callback */short *ev_pncalls;} ev_signal;} _ev;主要用于記錄用戶提供的相對時間,ev_timeout變量
struct timeval ev_timeout;
event超時的絕對時間
void (*ev_callback)(evutil_socket_t, short, void *arg);
用戶提供的回調函數,函數指針
void *ev_arg;
傳給回調函數的參數
對event的初始化操作主要集中在event_new,event_add上
event_new調用event_assign注冊一個event
/** 每次要添加事件都需要先調用event_new函數創建一個event,函數參數指明* 事件所屬的驅動base* 事件對應的文件描述符或者信號類型fd* fd對應的事件events, 如EV_READ, EV_WRITE, EV_PERSIST,注意信號是EV_SIGNAL* 當響應事件發生時調用的回調函數cb以及傳給cb的參數* * 使用者不需要自己判斷什么時候事件發生然后調用事件處理函數,而只需要將關注的這些東西* 傳給event,唯一需要的就是自己定義一個回調函數* * 當調用event_add后* event_base會統一管理它接受的所有事件,當某一個事件發生時,取得相應的event,然后調用event中存儲* 的回調函數,同時將需要的參數傳入。包括fd, 回調函數這些變量都在每一個event中存儲著* 這就是Reactor事件驅動* * event_new的內部調用的是event_assign函數,作用是創建一個event并初始化然后返回,* 用戶也可以自己調用這個函數* * 注意:這個函數并沒有將event注冊到base中,那是event_add的任務*/ struct event * event_new(struct event_base *base, evutil_socket_t fd, short events, void (*cb)(evutil_socket_t, short, void *), void *arg) {struct event *ev;ev = mm_malloc(sizeof(struct event));if (ev == NULL)return (NULL);if (event_assign(ev, base, fd, events, cb, arg) < 0) {mm_free(ev);return (NULL);}return (ev); }event_assign其實就是各種初始化,沒什么特別的地方
/* 函數對struct event這個結構體的成員變量賦值 */ int event_assign(struct event *ev, struct event_base *base, evutil_socket_t fd, short events, void (*callback)(evutil_socket_t, short, void *), void *arg) {if (!base)base = current_base;/* * 僅僅是將event的base成員變量進行綁定,此時仍然沒有將event添加到base中* 如果需要添加,需要手動調用event_add()函數*/ev->ev_base = base;ev->ev_callback = callback;ev->ev_arg = arg;ev->ev_fd = fd;/** ev_events表示的是需要監聽的事件,此外有幾個值用于區分io和信號,一次和永久* EV_READ/EV_WRITE:讀寫事件* EV_SIGNAL:表示這個事件是一個信號* EV_PERSIST:表示這個事件是一個永久事件,當處理過一次之后仍然繼續監聽,否則處理一次后就被刪除掉*/ev->ev_events = events;/* ev_res表示event被哪個事件激活 */ev->ev_res = 0;/** ev_flags表示的是這個event目前的狀態,其實就是event在base的哪幾個隊列里/或最小堆* 初始狀態EVLIST_INIT:表示剛剛初始化,還沒有注冊到base中,不在任何隊列中* 注冊狀態EVLIST_INSERTED:表示已經注冊到base中,在base的注冊隊列中* 活躍狀態EVLIST_ACTIVE:表示監聽的某個事件發生,等待著調用回調函數,在激活隊列中* 超時狀態EVLIST_TIMEOUT:表示具有超時時間的事件超時,在最小堆中* 內部事件EVLIST_INTERNAL:表示這個event是一個內部event,用于信號的統一*/ev->ev_flags = EVLIST_INIT;/* 被激活的次數,對信號有用,因為一段時間內可能傳來多個同樣的信號 */ev->ev_ncalls = 0;ev->ev_pncalls = NULL;/** ev_closure* 用于區分信號/io,永久/一次event* 在event_base_active_single_queue中用于判斷是否是永久/信號event* 對于永久event且有超時時間,重新計算時間然后調用event_add* 對于永久信號,調用用戶的信號處理函數,可能多次調用,如果同一個信號發生多次的話*/if (events & EV_SIGNAL) {if ((events & (EV_READ|EV_WRITE)) != 0) {event_warnx("%s: EV_SIGNAL is not compatible with ""EV_READ or EV_WRITE", __func__);return -1;}ev->ev_closure = EV_CLOSURE_SIGNAL;} else {if (events & EV_PERSIST) {evutil_timerclear(&ev->ev_io_timeout);ev->ev_closure = EV_CLOSURE_PERSIST;} else {ev->ev_closure = EV_CLOSURE_NONE;}}/** 如果事件具有超時時間,那么它將被添加到base的時間最小堆上* 而最小堆其實就是一個struct event*類型的數組,首先需要初始化每個事件* 在最小堆中的索引為-1,表示當前不在最小堆中* 下標用處不明*/min_heap_elem_init(ev);if (base != NULL) {/* by default, we put new events into the middle priority *//** base中有的激活隊列是一個隊列數組,數組的每一個元素是一個隊列* 數組下標代表響應隊列的優先級,下標越低,優先級越高* 所有如果某個事件發生了,會將響應的event放入對應優先級的隊列里,* event優先級初始化工作在這里,默認優先級時base中激活隊列數量的一半* 也就是優先級是中等水平,可以手動修改*/ev->ev_pri = base->nactivequeues / 2;}return 0; }event_add調用event_add_internal函數
/** 這個函數用來將event添加到base中,也就是添加到base的注冊隊列中* 函數內部通過調用event_add_internal()來完成工作,不過再次之前需要為base加鎖,* 因為需要更改base中的隊列,而其他線程此時可能正在訪問隊列,這就造成了race condition* 所以需要鎖來保護*/ int event_add(struct event *ev, const struct timeval *tv) {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);res = event_add_internal(ev, tv, 0);EVBASE_RELEASE_LOCK(ev->ev_base, th_base_lock);return (res); }event_add_internal比較重要,它根據event的不同類型添加到不同的base隊列中
/** event_add調用的內部函數,用于將event添加到base的注冊隊列中* 同時添加到相應的map中* * 注意:這個函數不僅僅只由event_add調用,還有event_persist_closure調用* 由這個函數調用是因為當具有超時時間的event被激活后,需要先從base中的所有隊列中刪除* 然后重新計算超時時間,再重新添加到base中,所以又重新調用了這個函數* * 注意:event不僅代表文件描述符,還有可能是信號的event,當是信號時,會遞歸* 調用兩遍這個函數,第一遍調用時判斷是信號則調用evsig_map_add函數,在這個函數中* 進行兩步* 將信號event添加到base的信號map中* 調用evsigops的add函數,即調用evsig_add,這個函數中綁定內部信號處理函數,同時將socketpair的event* 添加到base中,使用event_add,也就是調用event_add_internal* 不過只會執行兩遍,因為在evsig_add中會進行判斷,只有第一次添加socketpair的event時才會執行第二次調用* * 見evsig_add*/ static inline int event_add_internal(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,同時在最小堆中沒有這個event* 這樣就需要在最小堆上留出一個位置來存放這個event* 因為用戶可以對同一個event調用event_add多次,這就可能兩次event_add除了超時時間不同* 其他的都相同,這樣就不需要在留出一個位置,直接替換以前的就可以* * 如果已經在最小堆中,ev_flags將是EVLIST_TIMEOUT*/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 */}/** 這一步就是根據io還是signal添加到base的不同map中,然后加入到base的隊列中,* 注意event_queue_insert的最后一個參數* 這個函數可以根據參數的不同選擇添加不同的隊列中,同時為這個event的flags添加* 不同的狀態,此時為EVLIST_INSERTED會另event->flags |= EVLIST_INSERTED,同時添加到注冊隊列上* 表示這個event處于base的注冊隊列中** 此時仍然沒有考慮具有超時時間的event,所以這種event也同樣會進入這個語句* 添加到不同的map中,然后添加到base的注冊隊列中* 在下面還會單獨為具有超時時間的event調用一次,那時為EVLIST_TIMEOUT*/if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&!(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {/** 注意只有文件描述符的event會添加到io函數中,信號event不添加,它不需要監聽* 有信號發生會由內核通知進程*/if (ev->ev_events & (EV_READ|EV_WRITE))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,socketpair讀端event都添加到signal map和base注冊隊列中*/if (res != -1)event_queue_insert(base, ev, EVLIST_INSERTED);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.*//* 這一步開始處理具有超時時間的event */if (res != -1 && tv != NULL) {struct timeval now;int common_timeout;/** for persistent timeout events, we remember the* timeout value and re-add the event.** If tv_is_absolute, this was already set.*//** event分為永久的和一次的,是用戶在調用event_new時傳入的參數* 對于永久的event,在被激活一次之后還需要繼續監聽,* 而對于有超時時間的event,需要對event的超時時間進行更新* * 為什么:因為base在進行超時判斷時是通過絕對時間進行判斷的,也就是說在添加event的時候* 將當前時間+時間間隔獲得的絕對時間作為判斷超時的依據* 這樣做的原因是不需要在判斷超時時比較時間差,只需要比較當前時間和超時時間即可* * 所以,如果event是永久的,那么再處理過一次之后需要更新超時絕對時間,方法就是保存用戶* 傳入的時間間隔,再下一次添加時使用** tv_is_absolute是傳入的參數,event_add傳入時設為0,表示傳入的時間是時間間隔,不是絕對時間*/if (ev->ev_closure == EV_CLOSURE_PERSIST && !tv_is_absolute)ev->ev_io_timeout = *tv;/** we already reserved memory above for the case where we* are not replacing an existing timeout.*//** 對于用戶對同一個event調用event_add多次的情況,先將以前的從最小堆* 中刪除,再添加更新的這個*/if (ev->ev_flags & EVLIST_TIMEOUT) {/* XXX I believe this is needless. */if (min_heap_elt_is_top(ev))notify = 1;event_queue_remove(base, ev, EVLIST_TIMEOUT);}/* Check if it is active due to a timeout. Rescheduling* this timeout before the callback can be executed* removes it from the active list. *//* * 如果此時event正處于激活隊列中,從激活隊列刪了 * 如果是信號,將其發生次數設為0,就不會調用信號處理函數了*/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(base, ev, EVLIST_ACTIVE);}/* 計算超時絕對事件 */gettime(base, &now);common_timeout = is_common_timeout(tv, 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_queue_insert()將具有超時時間的event添加到base最小堆中 */event_queue_insert(base, ev, EVLIST_TIMEOUT);if (min_heap_elt_is_top(ev))notify = 1;}return (res); }總結
event是整個libevent的核心,通過指針保存回調函數,這樣當fd被激活,就可以由libevent調用這個回調函數而無需用戶關系什么時候調用。libevent內部函數指針的使用特別頻繁,主要就集中在這里
對于初始化工作,其實就是程序用到什么就初始化什么,比較無腦,但是具體的細節還是值得細看的
event_add_internal這個函數尤為重要,它不僅由event_add函數調用,對于超時event仍然需要重復調用這個函數,所以在對超時event的管理上libevent的做法還是很值得學習的,不過在設計過程中要做到思路清晰真的不容易,可以細細研究大神之作
總結
以上是生活随笔為你收集整理的libevent源码学习-----event操作的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: libevent源码学习-----时间管
- 下一篇: libevent源码学习-----eve