您好,登錄后才能下訂單哦!
libevent是一個輕量級的開源高性能事件驅動網絡庫,是一個典型的Reactor模型。其主要特點有事件驅動,高性能,跨平臺,統一事件源等等。
網上關于libevent的源碼分析有很多相關博客,本人在學習過程中也是借助了網絡。所以,在此,關于libevent中的許多具體實現部分就不做介紹,主要是從相關數據結構層面上去分析。僅供參考。
libevent中的事件處理類型是event結構類型,event結構體封裝了句柄,事件類型,回調函數,以及其他必要的標志和數據,是整個libevent庫的核心。
該結構的定義如下:
struct event{ /* * ev_next, ev_active_next都是雙向鏈表節點指針 * 它們是libevent對不同事件類型和在不同時期,對事件的管理時使用到的字段 * * libevent使用雙向鏈表保存所有注冊的IO和signal事件 * ev_next 就是該IO事件在鏈表中的位置,稱此鏈表為已注冊事件鏈表 * ev_active_next: libevent將所有激活事件放入鏈表active list中,然后遍歷active list * 執行調度,ev_active_next就指明了event在active list中的位置 */ TAILQ_ENTRY(event) ev_next; TAILQ_ENTRY(event) ev_active_next; /* * _ev 是一個聯合體,所有具有相同描述符的IO事件通過ev.ev_io.ev_io_next成員串聯成一個 * 尾隊列,稱之為IO事件隊列,所有具有相同信號值的信號事件通過ev.ev_signal.ev_signal_next * 串聯成一個尾隊列,稱之為信號事件隊列。ev.ev_signal.ev_ncalls成員指定時間發生時,Reactor * 需要執行多少次該事件對應的回調函數,ev.ev_signal.ev_pcalls要么是NULL,要么執行ev.ev_signal.ev_ncalls */ union{ struct { TAILQ_ENTRY(event) ev_io_next; struct timeval ev_timeout; }ev_io; struct { TAILQ_ENTRY(event) ev_signal_next; short ev_ncalls; short *ev_pcalls; }ev_signal; } _ev; /* * ev_timeout_pos是一個聯合體,它僅用于定時事件處理器,老版本libevnet中使用最小堆管理 * 定時器,但是開發者認為有時簡單鏈表的管理更加高效。所以新版本中引入了“通用定時器”的 * 概念。這些定時器不是存儲在時間堆中,而是存儲在尾隊列中,我們稱之為通用定時器隊列。 * 對于通用定時器而言,ev_timeout_pos中的ev_next_with_common_timeout成員指出了該定時器 * 在隊列中的位置;對于其他定時器,min_heap_idx成員指出了該定時器在時間堆中的位置。一個 * 定時器是否是通用定時器,取決于其超時值的大小。具體參考event.c中的is_common_timeout函數。 */ union{ TAILQ_ENTRY(event) ev_next_with_common_timeout; int min_heap_idx; }ev_timeout_pos; //如果是超時事件ev_timeout超時值 struct timeval ev_timeout; //ev_base :該事件所屬的反應堆實例,這是一個event_base結構體 struct event_base *ev_base; //對于IO事件,是綁定的文件描述符,對于signal事件,是綁定的信號 int ev_fd; /* * ev_events : event關注的事件類型,它可以是以下三種類型: * IO事件:EV_WRITE / EV_READ * 定時事件: EV_TIMEOUT * 信號: EV_SIGANL *輔助選項: EV_PERSIST, 表明是一個永久事件 */ short ev_events; //各個事件可以使用 "|"運算符進行組合,信號和IO事件不能同時設置 //事件就緒執行時,調用ev_callback的次數,通常為1 short ev_ncalls; //指針,指向ev_ncalls或NULL short *ev_pncalls; //allows deletes in callback int ev_pri; //smaller numbers are higher priority //ev_callback:event回調函數,被ev_base調用,執行事件處理程序,這是一個函數指針 //其中fd對應ev_fd, events對應ev_events, arg對應ev_arg void (*ev_callback)(int , short, void *arg); //void* 表明可以是任意類型,在設置event時指定 void *ev_arg; //記錄了當前激活事件的類型 int ev_res; //result passed to event callback /* * libevent用于標記event信息的字段,表明當前的狀態 */ int ev_flags; };
從event結構的定義可以看出,event中封裝了句柄,回調函數,和事件類型。包括該事件在相應鏈表或時間堆中的索引位置。宏TAILQ_ENTRY是尾隊列的節點類型,其定義為:
#define TAILQ_ENTRY(type) \ struct { \ struct type *tqe_next; \ /*下一個元素*/ struct type **tqe_prev; \ /*前一個元素的地址*/ }
每當有事件event轉變為就緒狀態時,libevent就會把它移入到active event list[priority]中,其中priority是event的優先級;接著libevent會根據自己的調度策略選擇就緒事件,調用其cb_callback()函數執行事件處理。
結構體event_base是libevent的Reactor,其聲明如下:
struct event_base { /* 初始化Reactor的時候選擇一種后端IO復用機制,并記錄在如下字段中*/ const struct eventop *evsel; /*指向IO復用機制真正存儲的數據,它通過evsel成員的init函數來初始化*/ void *evbase; /* 指向信號的后端處理機制,目前僅在signal.h文件中定義了一種處理方法*/ const struct eventop *evsigsel; void *evsigbase; /*信號處理器使用到的數據結構,其中封裝了一個socketpair創建的管道,它用于信號處理函數和事件多路分發器之間的通信。*/ struct evsig_info sig; /* 添加到該event_base的所有事件和激活事件的數量*/ int event_count; /**< counts number of total events */ int event_count_active; /**< counts number of active events */ /* 是否執行完活動事件隊列上的剩余任務之后就退出事件循環 */ int event_gotterm; /**< Set to terminate loop once done * processing events. */ /* 是否立即退出事件循環,而不管是否還有任務需要處理 */ int event_break; /**< Set to exit loop immediately */ /* 活動事件隊列數組。索引越小的隊列,優先級越高。高優先級的活動事件隊列中的事件處理器將被優先處理*/ struct event_list **activequeues; /* 活動事件隊列數組的大小,即該event_base一共有nactivequeues個不同優先級的活動事件隊列*/ int nactivequeues; /*是否應該啟動一個新的事件循環*/ int event_continue; //目前正在處理的活動事件隊列的優先級 int event_running_priority; //事件循環是否已經啟動 int running_loop; /** Deferred callback management: a list of deferred callbacks to * run active the active events. */ TAILQ_HEAD (deferred_cb_list, deferred_cb) deferred_cb_list; //文件描述符和IO事件之間的映射關系表 struct event_io_map io; /*信號值和信號事件之間的映射關系表*/ struct event_signal_map sigmap; /*注冊時間隊列,存放IO事件處理器和信號事件處理器*/ struct event_list eventqueue; /*時間堆*/ struct min_heap timeheap; //系統管理時間的一些成員 struct timeval event_tv; struct timeval tv_cache; };
其中:
evsel和evbase這兩個字段的額設置可能會讓人迷惑,可以將其看作是類和靜態函數的關系,比如添加事件時的調用行為:evsel->add(evbase, ev),實際上執行操作的是evbase,這相當于class::add(instance, ev),instance就是class的一個實例。evsel指向全局變量static const struct eventop *eventops[]中的一個。eventops[]包含了select,poll,kequeue和epoll等等其中的若干個全局實例對象。evbase實際上是一個eventop實例對象。
eventop結構體,是一系列的函數指針,定義如下:
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; };
在libevent中,每個IO事件分發機制的實現都必須提供這五個函數接口。
事件主循環主要是通過event_base_loop()函數來完成的。其代碼如下:
int event_base_loop(struct event_base *base, int flags){ const struct eventop *evsel = base->evsel; void *evbase = base->evbase; struct timeval tv; struct timeval *tv_p; int res, done; //清空時間緩存 base->tv_cache.tv_sec = 0; //evsignal_base是全局變量,在處理signal時,用于指明signal所屬的event_base實例 if(base->sig.ev_signal_added) evsignal_base = base; done = 0; //事件主循環 while(!done){ //查看是否需要跳出循環,程序可以調用event_loopexit_cb()設置event_gotterm標記 //調用event_base_loopbreak設置event_break標志 if(base->event_gotterm){ base->event_gotterm = 0; break; } if(base->event_break){ base->event_break = 0; break; } //you cannot use this interface for multi-threaded apps while(event_gotsig){ event_gotsig = 0; if(event_sigcb){ res = (*event_sigcb)(); if(res == -1){ errno = EINTR; return -1; } } } //校正系統時間,如果系統使用的是非MONOTONIC時間,用戶可能會向后調整了系統時間 //在timeout_correct函數中,比較last wait time和當前事件,如果 //當前時間 < last wait time //表明時間有問題,這需要更新timer_heap中所有定時事件的超時時間 timeout_correct(base, &tv); //根據time heap中事件的最小超時時間,計算系統IO demultiplexer的最大等待時間 tp_p = &tv; if(!base->event_count_active && !(flags & EVLOOP_NONBLOCK)){ timeout_next(base, &tv_p); } else{ //依然有未處理的就緒時間,就讓IO demultiplexer立即返回,不必等待 //下面會提到,在libevent中,低優先級的就緒事件可能不能立即被處理 evutil_timerclear(&tv); } //如果當前沒有注冊事件,就退出 if(!event_haveevents(base)){ event_debug("%s: no events registered.", __func__); return 1; } //更新last wait time,并清空time cache gettime(base, &base->event_tv); base->tv_cache.tv_sec = 0; //調用系統IO demultiplexer等待就緒IO events,可能是epoll_wait,或者select等 //在evsel->dispatch()中,會把就緒signal event / IO event插入到激活鏈表中 res = evsel->dispatch(base, evbase, tv_p); if(res == -1) return -1; //將time cache 賦值為當前系統時間 gettime(base, &base->tv_cache); //檢查heap中的timer events,將就緒的timer event從heap上刪除,并插入到激活鏈表中 timeout_process(base); //調用event_process_active()處理激活鏈表中的就緒event,調用其回調函數執行事件處理 //該函數會尋找最高優先級(priority值越小優先級越高)的激活事件鏈表 //然后處理鏈表中的所有就緒事件 //因此低優先級的就緒事件可能得不得及時處理 if(base->event_count_active){ event_process_active(base); if(!base->event_count_active && (flags & EVLOOP_ONCE)) done = 1; } else if(flags & EVLOOP_NONBLOCK) done = 1; } //循環結束,清空時間緩存 base->tv_cache.tv_sec = 0; event_debug("%s: asked to terminate loop.", __func__); return 0; }
統一事件源,libevent將timer和signal事件都統一到了系統的IO demultiplex機制中
通過socketpair來實現的。即一個socket對,其中有兩個socket,一個讀,一個寫。
將讀socket在事件主循環實例中注冊一個讀事件,當信號發生時,往寫socket中寫入一個字符,通常為信號值,此時讀socket上有讀事件,觸發IO demultiplex讀事件,然后同普通的IO事件一起被處理即可。
timer和IO事件的統一。因為系統的IO機制,例如select()和epoll_wait()都允許程序制定一個最大的等待時間,根據所有timer事件中的最小超時時間來設置IO demultiplex的最大等待時間,當IO返回時,再激活所有就緒的timer事件就可以了,這樣就將timer事件完美融合到了系統的IO機制中了。
IO和signal的統一。因為signal的出現對進程來說是完全隨機的。所以當signal發生時,并不立即調用event的callback函數處理信號,而是設法通知系統的IO機制,讓其返回,然后再統一和IO事件,以及timer一起處理。
事件主循環的流程如下
1) 開始 2) 調整系統時間與否 3) 根據timer heap中的event的最小超時時間計算系統IO demultiplexer的最大等待時間 4) 更新last wait time, 并清空time cache5) 調用系統I/O demultiplexer等待就緒I/O events6) 檢查signal的激活標記,如果被設置,則檢查激活signal event,并將event插入到激活鏈表中7) 將就緒的I/O event插入到激活鏈表中8) 檢查heap中的timer events,將就緒的timer event從heap上刪除,并插入到激活鏈表中9) 根據優先級處理激活鏈表中的就緒event,調用其回調函數執行事件處理(優先級越小越高)10) 結束
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。