libevent源碼分析(轉)

 

一、入門算法

1.一、概述
Libevent是一個用於開發可擴展性網絡服務器的基於事件驅動(event-driven)模型的網絡庫。Libevent有幾個顯著的亮點:
(1)事件驅動(event-driven),高性能;
(2)輕量級,專一於網絡,不如 ACE 那麼臃腫龐大;
(3)源代碼至關精煉、易讀;
(4)跨平臺,支持 Windows、Linux、*BSD和 Mac Os;
(5)支持多種 I/O多路複用技術, epoll、poll、dev/poll、select 和kqueue 等;
(6)支持 I/O,定時器和信號等事件;
(7)註冊事件優先級;
 Libevent 已經被普遍的應用,做爲底層的網絡庫;好比 memcached、 Vomi t、 Nylon、 Netchat等等。編程

 

1.二、一個簡單示例數組

按 Ctrl+C 複製代碼
按 Ctrl+C 複製代碼

這是一個簡單的基於libevent的定時器程序,運行結果:緩存

 

用libevent編程很是簡單,只須要調用event_init初始化環境,而後調用event_add註冊相應的事件,接着調用event_dispatch等待並處理相應的事件便可。
調用event_add註冊事件時,設置其回調函數。Libevent檢測到事件發生時,便會調用事件對應的回調用函數,執行相關的業務邏輯。

1.三、源代碼結構
Libevent 的源代碼雖然都在一層文件夾下面,可是其代碼分類仍是至關清晰的,主要可分爲頭文件、內部使用的頭文件、輔助功能函數、日誌、libevent 框架、對系統 I/O 多路複用機制的封裝、信號管理、定時事件管理、緩衝區管理、基本數據結構和基於 libevent的兩個實用庫等幾個部分,有些部分可能就是一個源文件。
(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)基本數據結構
compat\sys 下的兩個源文件: queue.h是 libevent 基本數據結構的實現,包括鏈表,雙向鏈表,隊列等;_libevent_time.h:一些用於時間操做的結構體定義、函數和宏定義;
(11)實用網絡庫
     http 和evdns:是基於 libevent 實現的http 服務器和異步 dns 查詢庫;

二、核心對象
結構體event和event_base是libevent的兩個核心數據結構,前者表明一個事件對象,後者表明整個事件處理框架。
2.一、event(事件)服務器

複製代碼
代碼
 1 //event.h
 2 struct event {
 3 TAILQ_ENTRY (event) ev_next;          //已註冊事件鏈表
 4 TAILQ_ENTRY (event) ev_active_next;//就緒事件鏈表
 5 TAILQ_ENTRY (event) ev_signal_next; //signal鏈表
 6 unsigned int min_heap_idx;    /* for managing timeouts,事件在堆中的下標 */
 7 
 8 struct event_base *ev_base;
 9 
10 int ev_fd;      //對於I/O事件,是綁定的文件描述符;對於signal事件,是綁定的信號
11 short ev_events; //event關注的事件類型
12 short ev_ncalls; //事件就緒執行時,調用 ev_callback 的次數
13 short *ev_pncalls;    /* Allows deletes in callback */
14 
15 struct timeval ev_timeout;  //timout事件的超時值
16 
17 int ev_pri;   /* smaller numbers are higher priority,優先級 */
18 
19 void (*ev_callback)(intshortvoid *arg); //回調函數
20 void *ev_arg; //回調函數的參數
21 
22 int ev_res;        /* result passed to event callback */
23 int ev_flags; //event的狀態
24 };
25 
複製代碼

Libevent經過event對象將I/O事件、信號事件和定時器事件封裝,從而統一處理,這也是libevent的精妙全部。
各個字段的具體含義:
(1) ev_events:event關注的事件類型,它能夠是如下3種類型:
I/O事件: EV_WRITE和EV_READ
定時事件:EV_TIMEOUT
信號:    EV_SIGNAL
輔助選項:EV_PERSIST,代表是一個永久事件
libevent中的定義爲:
#define EV_TIMEOUT    0x01
#define EV_READ    0x02
#define EV_WRITE    0x04
#define EV_SIGNAL    0x08
#define EV_PERSIST    0x10    /* Persistant event */
(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指向事件框架實例。
(5)ev_fd,對於 I/O事件,是綁定的文件描述符;對於 signal 事件,是事件對應的信號;
(6)eb_flags:libevent 用於標記 event信息的字段,代表事件當前的狀態,可能的值有:
#define EVLIST_TIMEOUT   0x01 // event在time堆中
#define EVLIST_INSERTED 0x02 // event在已註冊事件鏈表中
#define EVLIST_SIGNAL    0x04 // 未見使用
#define EVLIST_ACTIVE    0x08 // event在激活鏈表中
#define EVLIST_INTERNAL 0x10 // 內部使用標記
#define EVLIST_INIT      0x80 // event 已被初始化

2.二、event_base(事件處理框架)網絡

 

複製代碼
代碼
 1 //evenet_internal.h
 2 struct event_base {
 3 const struct eventop *evsel; //底層具體I/O demultiplex操做函數集
 4 void *evbase;
 5 int event_count;        /* counts number of total events,總的事件數量 */
 6 int event_count_active;    /* counts number of active events,就緒事件數量 */
 7 
 8 int event_gotterm;        /* Set to terminate loop */
 9 int event_break;        /* Set to terminate loop immediately */
10 
11 /* active event management */
12 //就緒事件鏈表數組
13 struct event_list **activequeues;
14 int nactivequeues;//就緒事件隊列個數
15 
16 /* signal handling info */
17 struct evsignal_info sig; //用於管理信號
18 
19 struct event_list eventqueue; //註冊事件隊列
20 struct timeval event_tv;
21 
22 struct min_heap timeheap; //管理定時器的小根堆
23 struct timeval tv_cache; //記錄時間緩存
24 };
複製代碼

(1)evsel:libevent支持Linux、Windows等多種平臺,也支持epoll、poll、select、kqueue等多種 I/O多路複用模型。若是把event_init、event_add當作高層抽象的統一事件操做接口,則evsel爲這些函數在底層具體的I/O demultiplex的對應的操做函數集。eventop爲函數指針的集合:數據結構

複製代碼
代碼
 1 struct eventop {
 2 const char *name;
 3 void *(*init)(struct event_base *);
 4 int (*add)(void *struct event *);
 5 int (*del)(void *struct event *);
 6 int (*dispatch)(struct event_base *void *struct timeval *);
 7 void (*dealloc)(struct event_base *void *);
 8 /* set if we need to reinitialize the event base */
 9 int need_reinit;
10 };
11 
複製代碼

在初始化函數event_base_new中,libevent將evsel指向全局數組eventops的具體元素:多線程

代碼

 

2.三、主要函數
2.3.一、event_int(初始化libevent實例)
struct event_base *
event_init(void)
初始化事件處理框架實例,內部調用event_base_new。

event_base_new的主要邏輯:框架

複製代碼
代碼
 1 struct event_base *
 2 event_base_new(void)
 3 {
 4 
 5 //初始化小根堆
 6 min_heap_ctor(&base->timeheap);
 7 
 8 //初始化註冊事件隊列
 9 TAILQ_INIT(&base->eventqueue);
10 
11 for (i = 0; eventops[i] && !base->evbase; i++) {
12 //I/O demultiplex機制實例
13 base->evsel = eventops[i];
14 
15 //初始化I/O demultiplex實例(參見win32_init)
16 base->evbase = base->evsel->init(base);
17 }
18 
19 //分配1個就緒事件隊列
20 event_base_priority_init(base1);
21 
22 }
複製代碼

2.3.二、event_add(註冊事件)
//註冊事件
int
event_add(struct event *ev, const struct timeval *tv)
該函數主要將事件ev加入到事件框架event_base的註冊事件鏈表base->eventqueue。

2.3.三、event_del(刪除事件)
//刪除事件
int
event_del(struct event *ev)
該函數主要將事件ev從相應的鏈表上刪除。

2.3.四、event_set(設置事件)異步

複製代碼
代碼
/*設置event對象
**ev:事件對象
**fd:事件對應的文件描述符或信號,對於定時器設爲-1
**events:事件類型,好比 EV_READ,EV_PERSIST, EV_WRITE, EV_SIGNAL
**callback:事件的回調函數
**arg:回調函數參數
*/
void
event_set(struct event *ev, int fd, short events,
      void (*callback)(intshortvoid *), void *arg)
複製代碼

在將事件註冊事件處理框架以前,應該先調用event_set對事件進行相關設置。


2.四、libevent對event的管理
event結構有3個鏈表結點域和一個小根堆索引,libevent經過3個鏈表和一個小根堆對I/O事件、signal事件和timer事件進行管理。
對於I/O事件,經過event_add將其加入event_base的註冊事件鏈表eventqueue ;就緒時會加入event_base的就緒鏈表activequeues[];
對於timer事件,event_add將其加入到event_base的小根堆timeheap;
Signale 事件的管理相對複雜些,event_add將其加入到註冊事件鏈表,同時,event_add內部會調用I/O demultiplex的add函數(對於I/O事件也同樣),好比epoll_add。而add函數又會調用evsignal_add將其加入到 evsignal_info的evsigevents[signo]鏈表(關於signal,後面會詳細介紹)。


三、事件處理框架主循環
Libevent將I/O事件、signal事件和timer事件用統一的模型進行處理,這是很是精妙的。libevent主循環函數不斷檢測註冊事件,若是有事件發生,則將其放入就緒鏈表,並調用事件的回調函數,完成業務邏輯處理。
3.一、event_dispatch
//事件處理主循環
int
event_dispatch(void)
這是呈現給外部的接口,它的實現很簡單,即調用event_loop,而event_loop調用event_base_loop,event_base_loop完成實際的主循環邏輯。

3.二、event_base_loop
主要算法:

代碼

 

3.三、timeout_next

複製代碼
 1 /*根據timer heap中事件的最小超時時間,計算I/O demultiplex的最大等待時間.
 2 **爲了及時處理timer事件,I/O demultiplex的最大等待時間不該該超過timer事件中最小的超時時間,
 3 **不然,timer事件就不能獲得及時處理
 4 */
 5 static int
 6 timeout_next(struct event_base *basestruct timeval **tv_p)
 7 {
 8     struct timeval now;
 9     struct event *ev;
10     struct timeval *tv = *tv_p;
11 
12     //若是沒有timer事件,則直接返回
13     if ((ev = min_heap_top(&base->timeheap)) == NULL) {
14         /* if no time-based events are active wait for I/O */
15         *tv_p = NULL;
16         return (0);
17     }
18 
19     if (gettime(base&now) == -1)
20         return (-1);
21 
22     //若是最小的timer事件已經超時,則清除tv,即I/O demultiplex不該該等待.
23     if (evutil_timercmp(&ev->ev_timeout, &now, <=)) {
24         evutil_timerclear(tv);
25         return (0);
26     }
27 
28     //更新I/O demultiplex能夠等待的最大時間:ev->ev_timeout - now
29     evutil_timersub(&ev->ev_timeout, &now, tv);
30     return (0);
31 }
32 
複製代碼

 

3.四、dispatch函數
調用底層I/O multiplex的dispatch函數,具體的實現能夠參見epoll的實現epoll_dispatch。

3.五、event_process_active

複製代碼
 1 /*處理就緒事件.
 2 **就緒事件位於優先級隊列中,低優先級一般比高優先級隊列先處理,因此,
 3 **高優先級隊列可能餓死.
 4 */
 5 static void
 6 event_process_active(struct event_base *base)
 7 {
 8     struct event *ev;
 9     struct event_list *activeq = NULL;
10     int i;
11     short ncalls;
12 
13     for (i = 0; i < base->nactivequeues; ++i) {
14         if (TAILQ_FIRST(base->activequeues[i]) != NULL) {
15             //一次只處理一個就緒事件鏈表
16             activeq = base->activequeues[i];
17             break;
18         }
19     }
20 
21     assert(activeq != NULL);
22 
23     //處理就緒事件鏈表上的全部事件
24     for (ev = TAILQ_FIRST(activeq); ev; ev = TAILQ_FIRST(activeq)) {
25         
26         //先將事件從鏈表上刪除
27         if (ev->ev_events & EV_PERSIST)
28             event_queue_remove(base, ev, EVLIST_ACTIVE);
29         else
30             event_del(ev); //刪除事件
31         
32         /* Allows deletes to work */
33         ncalls = ev->ev_ncalls;
34         ev->ev_pncalls = &ncalls;
35         while (ncalls) {
36             ncalls--//調用次數減1
37             ev->ev_ncalls = ncalls;
38             //調用事件的回調函數
39             (*ev->ev_callback)((int)ev->ev_fd, ev->ev_res, ev->ev_arg);40             if (base->event_break)41                 return;42         }43     }44 }45 
複製代碼

 

四、Timer事件
Timer事件的處理自己比較簡單,再也不贅述。

五、signal事件
5.一、socket pair
Libevent經過socketpair,將signal事件與I/O事件完美的統一塊兒來。Socketpair,簡單的說就一對socket,一端用於寫,一端用於讀。工做方式以下:

 爲了與I/O事件統一塊兒來,libevent內部使用了一個針對read socket的讀事件。

5.1.一、Socketpair的建立
與 信號事件的初始化工做都是在evsignal_init中完成的,而evsignal_init經過調用evutil_socketpair建立 socketpair。對於Unix平臺,有socketpair系統調用;對於Windows,則相對複雜一些,具體見 evutil_socketpair函數的實現。

5.二、evsignal_info
在event_base內部有一個evsignal_info類型的字段sig,它是用於管理signal事件的核心數據結構:

複製代碼
代碼
 1 //evsignal.h
 2 struct evsignal_info {
 3 struct event ev_signal;    //內部socket讀事件
 4 int ev_signal_pair[2];  //對應socket pair的兩個socket描述符
 5 int ev_signal_added;  //內部socket讀事件是否已經加入註冊鏈表
 6 volatile sig_atomic_t evsignal_caught; //是否有信號發生
 7 //信號事件鏈表數組,evsigevents[signo]表示註冊信號signo的事件
 8 struct event_list evsigevents[NSIG]; 
 9 //具體記錄每一個信號觸發的次數,evsigcaught[signo]是記錄信號 signo被觸發的次數
10 sig_atomic_t evsigcaught[NSIG];
11     
12 //sh_old記錄了原來的 signal 處理函數指針,當信號 signo 註冊的 event 被清空時,須要從新設置其處理函數
13 #ifdef HAVE_SIGACTION
14 struct sigaction **sh_old;
15 #else
16 ev_sighandler_t **sh_old;
17 #endif
18 int sh_old_max;
19 };
20 
複製代碼

5.三、主要函數
5.3.一、evsignal_init
主要完成evsignal_info的初始化,主要算法:

複製代碼
代碼
 1 int
 2 evsignal_init(struct event_base *base)
 3 {
 4 evutil_socketpair(AF_UNIX, SOCK_STREAM, 0base->sig.ev_signal_pair);
 5 base->sig.sh_old = NULL;
 6 base->sig.sh_old_max = 0;
 7 
 8 //事件發生次數設爲0
 9 base->sig.evsignal_caught = 0;
10 memset(&base->sig.evsigcaught, 0sizeof(sig_atomic_t)*NSIG);
11 /* initialize the queues for all events */
12 for (i = 0; i < NSIG; ++i)
13 TAILQ_INIT(&base->sig.evsigevents[i]);
14 
15 evutil_make_socket_nonblocking(base->sig.ev_signal_pair[0]); //寫端
16 
17 //設置內部讀事件
18 event_set(&base->sig.ev_signal, base->sig.ev_signal_pair[1], 
19 EV_READ | EV_PERSIST, evsignal_cb, &base->sig.ev_signal); //讀端
20 base->sig.ev_signal.ev_base = base;
21 
22 //sig.ev_signal == EV_READ | EV_PERSIST | EVLIST_INTERNAL
23 base->sig.ev_signal.ev_flags |= EVLIST_INTERNAL;
24 }
複製代碼

該函數的關鍵在於這裏會設置libevent用於管理信號事件的內部讀事件evsignal_info的ev_signal,並將該事件對應的文件 描述符設爲socket pair的讀端。該函數由I/O multiplex的init函數調用。注:這裏只是設置,而並無註冊socket pair的讀事件(見下一節)。

5.3.二、evsignal_add
當調用event_add註冊信號事件時,內部會先調用 I/O multiplex的add函數,add函數又會調用evsignal_add,將事件加到evsignal_info內部的信號事件鏈表。而後再 event_queue_insert將其添加到event_base的註冊事件鏈表。

代碼

這裏有兩個地方須要注意,一是調用_evsignal_set_handler設置外部註冊信號事件對應的信號的信號處理函數evsignal_handler:

複製代碼
代碼
 1 static void
 2 evsignal_handler(int sig)
 3 {
 4     int save_errno = errno;
 5 
 6     //設置信號事件的發生次數
 7     evsignal_base->sig.evsigcaught[sig]++;
 8     evsignal_base->sig.evsignal_caught = 1;
 9 
10 #ifndef HAVE_SIGACTION
11     signal(sig, evsignal_handler);
12 #endif
13 
14     /* Wake up our notification mechanism */
15     //向socket pair的寫端寫數據
16     send(evsignal_base->sig.ev_signal_pair[0], "a"10);
17     errno = save_errno;
18 }
複製代碼

當用戶註冊信號事件對應的信號發生時,OS轉到evsignal_handler函數,從而設置sig.evsignal_caught,並向socket pair的寫端發送數據。
二是經過調用event_add完成內部socket pair的讀事件sig->ev_signal的註冊。最後,將(外部)事件添加到信號事件鏈表。
5.3.二、與主循環結合
信號事件完成了註冊,libevent就會在主循環中,等待事件發生,並處理事件。爲了理解,來看看具體I/O demultiplex的dispatch函數:

複製代碼
代碼
 1 static int
 2 epoll_dispatch(struct event_base *basevoid *arg, struct timeval *tv)
 3 {
 4     struct epollop *epollop = arg;
 5     struct epoll_event *events = epollop->events;
 6     struct evepoll *evep;
 7     int i, res, timeout = -1;
 8 
 9     if (tv != NULL)
10         timeout = tv->tv_sec * 1000 + (tv->tv_usec + 999/ 1000;
11 
12     if (timeout > MAX_EPOLL_TIMEOUT_MSEC) {
13         /* Linux kernels can wait forever if the timeout is too big;
14          * see comment on MAX_EPOLL_TIMEOUT_MSEC. */
15         timeout = MAX_EPOLL_TIMEOUT_MSEC;
16     }
17 
18     //等待I/O事件
19     res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout);
20 
21     if (res == -1) {
22         if (errno != EINTR) {
23             event_warn("epoll_wait");
24             return (-1);
25         }
26         //epoll_wait被信號中斷
27         evsignal_process(base);
28         return (0);
29     } else if (base->sig.evsignal_caught) {//發生了信號事件
30         //處理信號事件
31         evsignal_process(base);
32     }
33 //
34 }
複製代碼

epoll_dispatch函數調用epoll_wait函數等待I/O發生。而後,若是有信號事件發生,則調用evsignal_process處理信號事件,evsignal_process的邏輯比較簡單,它只是將事件從註冊事件鏈表轉移到就緒事件鏈表。

還 記得evsignal_handler函數嗎?它是全部(外部)信號事件對應的信號的信號處理函數,將實際的信號發生時,OS會轉而執行 evsignal_handler函數,而它便向socket pair的寫端寫數據,而讀端收到數據。而此時,libevent的內部socket pair讀事件已經完成註冊。libevent正阻塞在epoll_wait處,當socketp pair讀端收到數據時,libevent便從epoll_wait處返回。總之,signal事件經過socket pair,與I/O事件實現完美的統一。

Libevent從epoll_wait返回後,它調用evsignal_process處理信號事件,而後調用event_active將I/O事件(包括內部的socket pair讀事件)轉移到就緒事件鏈表。

Socket pair的讀事件回調函數:

複製代碼
代碼
 1 static void
 2 evsignal_cb(int fd, short what, void *arg)
 3 {
 4 static char signals[1];
 5 #ifdef WIN32
 6 SSIZE_T n;
 7 #else
 8 ssize_t n;
 9 #endif
10 //接收數據
11 = recv(fd, signals, sizeof(signals), 0);
12 if (n == -1)
13 event_err(1"%s: read", __func__);
14 }
複製代碼

 

六、libevent的應用
Libevent是一個很是優秀的開源網絡庫,它被許多其它開源程序使用。Memcache使用 libevent做爲底層的網絡處理組件,並採用主線程(main thread,單一)+工做線程(work thread,多個)的多線程模型(這將在Memcached的分析中詳細介紹)。

相關文章
相關標籤/搜索