libevent源码学习-----event操作
libevent核心結(jié)構(gòu)是event_base和event,接下來主要介紹event結(jié)構(gòu)
/* event的定義的主要部分 */ struct event {/* ... *//* event監(jiān)聽的描述符,也可以是信號(hào)值 */evutil_socket_t ev_fd;/* 事件驅(qū)動(dòng)主循環(huán) */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創(chuàng)建一個(gè)event
/* 創(chuàng)建事件驅(qū)動(dòng) */ struct event_base* base = event_base_new(); /**創(chuàng)建一個(gè)事件*@param base: 事件驅(qū)動(dòng)*@param fd: event對(duì)應(yīng)的文件描述符,通常是通過socket創(chuàng)建的套接字*@param EV_READ: 想要監(jiān)聽fd的哪些事件,EV_READ表示監(jiān)聽fd是否可讀,也可以是EV_PERSIST代表這個(gè)event是永久事件,在調(diào)用一次回調(diào)函數(shù)后仍然繼續(xù)監(jiān)聽,對(duì)應(yīng)一次性event,調(diào)用后不再監(jiān)聽*@param cb: 當(dāng)fd對(duì)應(yīng)的事件發(fā)生后調(diào)用的回調(diào)函數(shù),用戶提供*@param arg: 傳給回調(diào)函數(shù)cb的參數(shù)*/ struct event* ev = event_new(base, fd, EV_READ|EV_PERSIST, cb, arg);接下來一個(gè)個(gè)解釋每個(gè)變量的作用
evutil_socket_t ev_fd;
event負(fù)責(zé)監(jiān)聽的描述符,也可以是信號(hào)值
struct event_base *ev_base;
事件驅(qū)動(dòng)base
short ev_events;
對(duì)應(yīng)監(jiān)聽fd的某些事件,上述代碼中是EV_READ | EV_PERSIST,可用的events包括
- EV_READ:fd可讀
- EV_WRITE:fd可寫
- EV_PERSIST:永久事件,激活一次后仍然繼續(xù)監(jiān)聽,對(duì)應(yīng)一次事件,激活一次后不再監(jiān)聽
- EV_SIGNAL:代表這個(gè)event監(jiān)聽的是一個(gè)信號(hào)
- EV_TIMEOUT:代表這個(gè)event具有超時(shí)時(shí)長(zhǎng)
short ev_res;
當(dāng)event被激活時(shí),ev_res的值記錄著是被哪些事件(上述)激活,即激活的原因
short ev_flags;
event處于的狀態(tài),其實(shí)是event都在哪幾個(gè)隊(duì)列中(base中有多個(gè)隊(duì)列),可以是以下幾種的或運(yùn)算
- EVLIST_INIT:表示event剛被初始化,不在任何隊(duì)列中,通常是剛調(diào)用完event_new
- EVLIST_INSERTED:表示event處于base的注冊(cè)隊(duì)列中,通常是調(diào)用event_add后
- EVLIST_ACTIVE:表示event處于base的激活隊(duì)列中,通常是event被激活,等待調(diào)用回調(diào)函數(shù)
- EVLIST_TIMEOUT:表示event處于最小堆中,表示event具有超時(shí)時(shí)間
- EVLIST_ALL:私有空間,不明
ev_uint8_t ev_pri;
event的優(yōu)先級(jí),base的激活隊(duì)列是一個(gè)數(shù)組,每個(gè)數(shù)組元素是一個(gè)隊(duì)列,數(shù)組下標(biāo)越低優(yōu)先級(jí)越高,在統(tǒng)一處理激活event時(shí),從優(yōu)先級(jí)高的event開始調(diào)用回調(diào)函數(shù)
_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;主要用于記錄用戶提供的相對(duì)時(shí)間,ev_timeout變量
struct timeval ev_timeout;
event超時(shí)的絕對(duì)時(shí)間
void (*ev_callback)(evutil_socket_t, short, void *arg);
用戶提供的回調(diào)函數(shù),函數(shù)指針
void *ev_arg;
傳給回調(diào)函數(shù)的參數(shù)
對(duì)event的初始化操作主要集中在event_new,event_add上
event_new調(diào)用event_assign注冊(cè)一個(gè)event
/** 每次要添加事件都需要先調(diào)用event_new函數(shù)創(chuàng)建一個(gè)event,函數(shù)參數(shù)指明* 事件所屬的驅(qū)動(dòng)base* 事件對(duì)應(yīng)的文件描述符或者信號(hào)類型fd* fd對(duì)應(yīng)的事件events, 如EV_READ, EV_WRITE, EV_PERSIST,注意信號(hào)是EV_SIGNAL* 當(dāng)響應(yīng)事件發(fā)生時(shí)調(diào)用的回調(diào)函數(shù)cb以及傳給cb的參數(shù)* * 使用者不需要自己判斷什么時(shí)候事件發(fā)生然后調(diào)用事件處理函數(shù),而只需要將關(guān)注的這些東西* 傳給event,唯一需要的就是自己定義一個(gè)回調(diào)函數(shù)* * 當(dāng)調(diào)用event_add后* event_base會(huì)統(tǒng)一管理它接受的所有事件,當(dāng)某一個(gè)事件發(fā)生時(shí),取得相應(yīng)的event,然后調(diào)用event中存儲(chǔ)* 的回調(diào)函數(shù),同時(shí)將需要的參數(shù)傳入。包括fd, 回調(diào)函數(shù)這些變量都在每一個(gè)event中存儲(chǔ)著* 這就是Reactor事件驅(qū)動(dòng)* * event_new的內(nèi)部調(diào)用的是event_assign函數(shù),作用是創(chuàng)建一個(gè)event并初始化然后返回,* 用戶也可以自己調(diào)用這個(gè)函數(shù)* * 注意:這個(gè)函數(shù)并沒有將event注冊(cè)到base中,那是event_add的任務(wù)*/ 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其實(shí)就是各種初始化,沒什么特別的地方
/* 函數(shù)對(duì)struct event這個(gè)結(jié)構(gòu)體的成員變量賦值 */ 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成員變量進(jìn)行綁定,此時(shí)仍然沒有將event添加到base中* 如果需要添加,需要手動(dòng)調(diào)用event_add()函數(shù)*/ev->ev_base = base;ev->ev_callback = callback;ev->ev_arg = arg;ev->ev_fd = fd;/** ev_events表示的是需要監(jiān)聽的事件,此外有幾個(gè)值用于區(qū)分io和信號(hào),一次和永久* EV_READ/EV_WRITE:讀寫事件* EV_SIGNAL:表示這個(gè)事件是一個(gè)信號(hào)* EV_PERSIST:表示這個(gè)事件是一個(gè)永久事件,當(dāng)處理過一次之后仍然繼續(xù)監(jiān)聽,否則處理一次后就被刪除掉*/ev->ev_events = events;/* ev_res表示event被哪個(gè)事件激活 */ev->ev_res = 0;/** ev_flags表示的是這個(gè)event目前的狀態(tài),其實(shí)就是event在base的哪幾個(gè)隊(duì)列里/或最小堆* 初始狀態(tài)EVLIST_INIT:表示剛剛初始化,還沒有注冊(cè)到base中,不在任何隊(duì)列中* 注冊(cè)狀態(tài)EVLIST_INSERTED:表示已經(jīng)注冊(cè)到base中,在base的注冊(cè)隊(duì)列中* 活躍狀態(tài)EVLIST_ACTIVE:表示監(jiān)聽的某個(gè)事件發(fā)生,等待著調(diào)用回調(diào)函數(shù),在激活隊(duì)列中* 超時(shí)狀態(tài)EVLIST_TIMEOUT:表示具有超時(shí)時(shí)間的事件超時(shí),在最小堆中* 內(nèi)部事件EVLIST_INTERNAL:表示這個(gè)event是一個(gè)內(nèi)部event,用于信號(hào)的統(tǒng)一*/ev->ev_flags = EVLIST_INIT;/* 被激活的次數(shù),對(duì)信號(hào)有用,因?yàn)橐欢螘r(shí)間內(nèi)可能傳來多個(gè)同樣的信號(hào) */ev->ev_ncalls = 0;ev->ev_pncalls = NULL;/** ev_closure* 用于區(qū)分信號(hào)/io,永久/一次event* 在event_base_active_single_queue中用于判斷是否是永久/信號(hào)event* 對(duì)于永久event且有超時(shí)時(shí)間,重新計(jì)算時(shí)間然后調(diào)用event_add* 對(duì)于永久信號(hào),調(diào)用用戶的信號(hào)處理函數(shù),可能多次調(diào)用,如果同一個(gè)信號(hào)發(fā)生多次的話*/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;}}/** 如果事件具有超時(shí)時(shí)間,那么它將被添加到base的時(shí)間最小堆上* 而最小堆其實(shí)就是一個(gè)struct event*類型的數(shù)組,首先需要初始化每個(gè)事件* 在最小堆中的索引為-1,表示當(dāng)前不在最小堆中* 下標(biāo)用處不明*/min_heap_elem_init(ev);if (base != NULL) {/* by default, we put new events into the middle priority *//** base中有的激活隊(duì)列是一個(gè)隊(duì)列數(shù)組,數(shù)組的每一個(gè)元素是一個(gè)隊(duì)列* 數(shù)組下標(biāo)代表響應(yīng)隊(duì)列的優(yōu)先級(jí),下標(biāo)越低,優(yōu)先級(jí)越高* 所有如果某個(gè)事件發(fā)生了,會(huì)將響應(yīng)的event放入對(duì)應(yīng)優(yōu)先級(jí)的隊(duì)列里,* event優(yōu)先級(jí)初始化工作在這里,默認(rèn)優(yōu)先級(jí)時(shí)base中激活隊(duì)列數(shù)量的一半* 也就是優(yōu)先級(jí)是中等水平,可以手動(dòng)修改*/ev->ev_pri = base->nactivequeues / 2;}return 0; }event_add調(diào)用event_add_internal函數(shù)
/** 這個(gè)函數(shù)用來將event添加到base中,也就是添加到base的注冊(cè)隊(duì)列中* 函數(shù)內(nèi)部通過調(diào)用event_add_internal()來完成工作,不過再次之前需要為base加鎖,* 因?yàn)樾枰腷ase中的隊(duì)列,而其他線程此時(shí)可能正在訪問隊(duì)列,這就造成了race condition* 所以需要鎖來保護(hù)*/ 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比較重要,它根據(jù)event的不同類型添加到不同的base隊(duì)列中
/** event_add調(diào)用的內(nèi)部函數(shù),用于將event添加到base的注冊(cè)隊(duì)列中* 同時(shí)添加到相應(yīng)的map中* * 注意:這個(gè)函數(shù)不僅僅只由event_add調(diào)用,還有event_persist_closure調(diào)用* 由這個(gè)函數(shù)調(diào)用是因?yàn)楫?dāng)具有超時(shí)時(shí)間的event被激活后,需要先從base中的所有隊(duì)列中刪除* 然后重新計(jì)算超時(shí)時(shí)間,再重新添加到base中,所以又重新調(diào)用了這個(gè)函數(shù)* * 注意:event不僅代表文件描述符,還有可能是信號(hào)的event,當(dāng)是信號(hào)時(shí),會(huì)遞歸* 調(diào)用兩遍這個(gè)函數(shù),第一遍調(diào)用時(shí)判斷是信號(hào)則調(diào)用evsig_map_add函數(shù),在這個(gè)函數(shù)中* 進(jìn)行兩步* 將信號(hào)event添加到base的信號(hào)map中* 調(diào)用evsigops的add函數(shù),即調(diào)用evsig_add,這個(gè)函數(shù)中綁定內(nèi)部信號(hào)處理函數(shù),同時(shí)將socketpair的event* 添加到base中,使用event_add,也就是調(diào)用event_add_internal* 不過只會(huì)執(zhí)行兩遍,因?yàn)樵趀vsig_add中會(huì)進(jìn)行判斷,只有第一次添加socketpair的event時(shí)才會(huì)執(zhí)行第二次調(diào)用* * 見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;/** 這一步主要是用來讓最小堆增加一個(gè)位置,并沒有實(shí)際添加到最小堆上* 判斷條件是這是一個(gè)具有超時(shí)時(shí)間的event,同時(shí)在最小堆中沒有這個(gè)event* 這樣就需要在最小堆上留出一個(gè)位置來存放這個(gè)event* 因?yàn)橛脩艨梢詫?duì)同一個(gè)event調(diào)用event_add多次,這就可能兩次event_add除了超時(shí)時(shí)間不同* 其他的都相同,這樣就不需要在留出一個(gè)位置,直接替換以前的就可以* * 如果已經(jīng)在最小堆中,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 */}/** 這一步就是根據(jù)io還是signal添加到base的不同map中,然后加入到base的隊(duì)列中,* 注意event_queue_insert的最后一個(gè)參數(shù)* 這個(gè)函數(shù)可以根據(jù)參數(shù)的不同選擇添加不同的隊(duì)列中,同時(shí)為這個(gè)event的flags添加* 不同的狀態(tài),此時(shí)為EVLIST_INSERTED會(huì)另event->flags |= EVLIST_INSERTED,同時(shí)添加到注冊(cè)隊(duì)列上* 表示這個(gè)event處于base的注冊(cè)隊(duì)列中** 此時(shí)仍然沒有考慮具有超時(shí)時(shí)間的event,所以這種event也同樣會(huì)進(jìn)入這個(gè)語句* 添加到不同的map中,然后添加到base的注冊(cè)隊(duì)列中* 在下面還會(huì)單獨(dú)為具有超時(shí)時(shí)間的event調(diào)用一次,那時(shí)為EVLIST_TIMEOUT*/if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&!(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {/** 注意只有文件描述符的event會(huì)添加到io函數(shù)中,信號(hào)event不添加,它不需要監(jiān)聽* 有信號(hào)發(fā)生會(huì)由內(nèi)核通知進(jìn)程*/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);/** 注意如果是信號(hào),在兩層遞歸調(diào)用時(shí)會(huì)將* 信號(hào)event,socketpair讀端event都添加到signal map和base注冊(cè)隊(duì)列中*/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.*//* 這一步開始處理具有超時(shí)時(shí)間的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分為永久的和一次的,是用戶在調(diào)用event_new時(shí)傳入的參數(shù)* 對(duì)于永久的event,在被激活一次之后還需要繼續(xù)監(jiān)聽,* 而對(duì)于有超時(shí)時(shí)間的event,需要對(duì)event的超時(shí)時(shí)間進(jìn)行更新* * 為什么:因?yàn)閎ase在進(jìn)行超時(shí)判斷時(shí)是通過絕對(duì)時(shí)間進(jìn)行判斷的,也就是說在添加event的時(shí)候* 將當(dāng)前時(shí)間+時(shí)間間隔獲得的絕對(duì)時(shí)間作為判斷超時(shí)的依據(jù)* 這樣做的原因是不需要在判斷超時(shí)時(shí)比較時(shí)間差,只需要比較當(dāng)前時(shí)間和超時(shí)時(shí)間即可* * 所以,如果event是永久的,那么再處理過一次之后需要更新超時(shí)絕對(duì)時(shí)間,方法就是保存用戶* 傳入的時(shí)間間隔,再下一次添加時(shí)使用** tv_is_absolute是傳入的參數(shù),event_add傳入時(shí)設(shè)為0,表示傳入的時(shí)間是時(shí)間間隔,不是絕對(duì)時(shí)間*/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.*//** 對(duì)于用戶對(duì)同一個(gè)event調(diào)用event_add多次的情況,先將以前的從最小堆* 中刪除,再添加更新的這個(gè)*/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. *//* * 如果此時(shí)event正處于激活隊(duì)列中,從激活隊(duì)列刪了 * 如果是信號(hào),將其發(fā)生次數(shù)設(shè)為0,就不會(huì)調(diào)用信號(hào)處理函數(shù)了*/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);}/* 計(jì)算超時(shí)絕對(duì)事件 */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);}/* 調(diào)用event_queue_insert()將具有超時(shí)時(shí)間的event添加到base最小堆中 */event_queue_insert(base, ev, EVLIST_TIMEOUT);if (min_heap_elt_is_top(ev))notify = 1;}return (res); }總結(jié)
event是整個(gè)libevent的核心,通過指針保存回調(diào)函數(shù),這樣當(dāng)fd被激活,就可以由libevent調(diào)用這個(gè)回調(diào)函數(shù)而無需用戶關(guān)系什么時(shí)候調(diào)用。libevent內(nèi)部函數(shù)指針的使用特別頻繁,主要就集中在這里
對(duì)于初始化工作,其實(shí)就是程序用到什么就初始化什么,比較無腦,但是具體的細(xì)節(jié)還是值得細(xì)看的
event_add_internal這個(gè)函數(shù)尤為重要,它不僅由event_add函數(shù)調(diào)用,對(duì)于超時(shí)event仍然需要重復(fù)調(diào)用這個(gè)函數(shù),所以在對(duì)超時(shí)event的管理上libevent的做法還是很值得學(xué)習(xí)的,不過在設(shè)計(jì)過程中要做到思路清晰真的不容易,可以細(xì)細(xì)研究大神之作
總結(jié)
以上是生活随笔為你收集整理的libevent源码学习-----event操作的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: libevent源码学习-----时间管
- 下一篇: libevent源码学习-----eve