libevent 專案分析

我理解libevent是一個輕量級的,跨平臺+高效的(C語言實現)事件驅動庫,類似於ACE項目中的ACE_Reactor,它實現了網絡通訊套接口I/O事件,定時器事件,信號事件的監聽和事件處理函數回調機制。從項目主頁可以瞭解到libevent已經支持 /dev/poll , kqueue(2) , event ports , POSIX select(2) , Windows select() , poll(2) , and epoll(4) 方式的事件監測和驅動機制

項目主頁: http://libevent.org/
維基百科: http://zh.wikipedia.org/wiki/Libevent
參考資料: http://blog.csdn.net/sparkliang/article/details/4957667

PS:在分析開源項目代碼之前,需首先了解該項目的特性,應用場景和價值,這些信息一方面可以從項目的主頁獲取,另一方面可以通過搜索引擎從技術論壇,博客等方面獲取。最好選擇和自己工作/興趣比較相關的項目,這樣有利於分析的深入和堅持,並及時體現收益。

###下載源代碼 從項目主頁可以很方便的下載當前版本的源碼,我下載的版本是 libevent-2.0.17-stable.tar.gz

###代碼量分析 通過Wine運行SourceCounter工具對該項目進行代碼量統計,可以看到該項目代碼量大概5W多行,且代碼工程結構簡單,比較適合像我這樣對開源項目代碼分析經驗不足的人 PS:在開始分析項目源碼之前,分析該項目的代碼量可以大致評估該項目的難度和分析計劃,分析工程結構可以大致評估該項目的重點部分,以免一開始就滿腔熱血地栽在一個深坑裡(比較複雜的開源項目),而後面又不了了之

###編譯和安裝 在將源碼包在本地解壓後即可以編譯和安裝。這裡和其他開源項目差不多,沒有什麼特別的,只是為了方便後面通過調試的手段來分析源碼,編譯的時候最好編譯成debug模式,如下

#./configure --enable-debug-mode --enable-static --enable-thread-support 
#make 
#make install 

安裝完成後,libevent庫的頭文件會安裝在/usr/local/include目錄下,而庫文件會安裝在/usr/local/lib目錄下,故需確保/usr/local/lib在LD_LIBRARY_PATH變量包含的路徑中 PS:卸載的方法

#make uninstall 
#make clean 

編寫測試應用代碼 該項目源碼包中的sample目錄中其實已經有些例子,但我還是情願參考樣例自己寫一個,好久沒Coding了 :)

mytimer.c : 實現一個定時器事件處理函數,並通過libevent的事件驅動機制定時調用

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#include <event2/event-config.h>
#include <event2/event.h>
#include <event2/event_struct.h>
#include <event2/util.h>


static void handle_timer(evutil_socket_t fd, short event, void* arg)
{
    printf("handle_timer function is called \n");
    fflush(stdout);
}

int main(int argc, char** argv)
{
    /* Initalize the event library */
    struct event_base* base = event_base_new();

    if (NULL == base) {
        return -1;
    }

    /* Initalize one timeout event */
    struct event timeout = {0};
    event_assign(&timeout, base, -1, EV_PERSIST, handle_timer, (void*)&timeout);

    /* Register the event */
    struct timeval tv;
    evutil_timerclear(&tv);
    tv.tv_sec = 2;
    event_add(&timeout, &tv);

    /*event dispatch*/
    event_base_dispatch(base);

    event_base_free(base);

    return 0;
}

編譯 :

gcc -g -I/usr/local/include -o mytimer mytimer.c -L/usr/local/lib -levent 

運行 : $ ./mytimer

handle_timer function is called  
handle_timer function is called  
handle_timer function is called  
^C 

通過例程調試libevent

通過gdb去調試mytimer時發現其鏈接的是libevent的動態庫,且無法在libevent庫的函數上設置斷點 :( 安裝glibc的靜態庫:# yum install glibc-static libstdc++-static 靜態編譯命令:

gcc -g -I/usr/local/include -o mytimer mytimer.c -L/usr/local/lib -static -levent -lc -lrt

這樣就可以通過gdb調試例程時,在libevent庫的函數上設置斷點

###初始化 libevent庫的使用是從對event_base結構初始化開始的,前面例程中使用的方法是不帶任何參數的 event_base_new 函數,類似的還有 event_init 函數。前者創建並初始化了一個默認的event_config結構,然後調用event_base_new_with_config函數;而後者更加簡單,用了一個NULL做為參數調用event_base_new_with_config函數。所以可以理解libevent庫的初始化核心是 event_base_new_with_config 函數,對該函數的分析留給後面,這次不打算分析到具體函數的實現 :)

###事件定義 libevent庫實現的就是事件監聽和驅動機制,該庫中對事件的定義是event結構。該結構不是很複雜,但比較重要,這裡列出幾個比較重要的成員變量

1,雙向事件鏈表

TAILQ_ENTRY(event) ev_active_next; 
TAILQ_ENTRY(event) ev_next; 

其中

TAILQ_ENTRY宏在event2/event_struct.h文件中定義如下

/* Fix so that people don't have to run with <sys/queue.h> */ 
#ifndef TAILQ_ENTRY
#define _EVENT_DEFINED_TQENTRY
#define TAILQ_ENTRY(type) \
    struct { \
        struct type *tqe_next; /* next element */ \
        struct type **tqe_prev; /* address of previous next element */ \
    }
#endif /* !TAILQ_ENTRY */

2,事件對應的句柄

SOCKET事件句柄

evutil_socket_t ev_fd; 

IO事件句柄

/* 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;

PS:

1,IO事件句柄與信號事件句柄通過union方式定義;
2,定時器事件是靠時間差比較來監聽,所以這裡沒有定時器事件的句柄
3,event_base指針: 通過該成員變量,event事件可以知道自己註冊在哪個event_base

struct event_base *ev_base; 

4,event屬性 :

short ev_events;
/**
 * @name event flags
 *
 * Flags to pass to event_new(), event_assign(), event_pending(), and
 * anything else with an argument of the form "short events"
 */
/**@{*/
/** Indicates that a timeout has occurred.  It's not necessary to pass
 * 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
/**@}*/

5,event標識:

short ev_flags; 
#define EVLIST_TIMEOUT 0x01 
#define EVLIST_INSERTED 0x02 
#define EVLIST_SIGNAL 0x04 
#define EVLIST_ACTIVE 0x08 
#define EVLIST_INTERNAL 0x10 
#define EVLIST_INIT 0x80 

6,事件優先級:數值越小,優先級越高,默認為適中

ev_uint8_t ev_pri; 

7,事件對應的回調函數指針及參數

/* allows us to adopt for different types of events */ 
void (*ev_callback)(evutil_socket_t, short, void *arg); 
void *ev_arg; 

###事件初始化

和event相關的函數有下面幾個:

event創建: event_new 
event設置: event_set , event_assign 
event釋放: event_free 
其中最核心的是event_assign函數,這裡分析 其參數的意義,說明事件關注的方面 

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) 

struct event *ev  :event事件指針 
struct event_base *base  : event_base指針 
evutil_socket_t fd  : 套接口句柄,其他類型事件可以賦值-1 
short events  : 事件的屬性 
void (*callback)(evutil_socket_t, short, void *)  : 事件處理回調函數指針 
void *arg  : 事件處理回調函數參數 

###註冊事件

例程中通過event_add函數註冊一個定時事件,實際上註冊事件是由event_add_internal函數實現,event_add函數會為調用event_add_internal函數增加鎖保護。event_add_internal函數通過事件的ev_events屬性判斷事件類型,從而調用不同函數實現事件註冊 對於IO事件的註冊由evmap_io_add函數實現

int evmap_io_add(struct event_base *base, evutil_socket_t fd, struct event *ev) 

PS :相關的其他幾個和IO事件操作函數evmap_io_init,evmap_io_del,evmap_io_active

對於Signal事件的註冊由evmap_signal_add函數實現

int evmap_signal_add(struct event_base *base, int sig, struct event *ev) 

PS : 相關的其他幾個和Signal事件操作函數 evmap_signal_init, evmap_signal_del, evmap_signal_active

對於定時器事件的註冊由 event_add_internal參數中的const struct timeval *tv參數決定???

###事件驅動

例程通過調用event_base_dispatch函數驅動事件,實際上事件驅動機制是由event_base_loop函數封裝實現的。這裡需注意event_base結構的成員變量const struct eventop *evsel,這個指針指向 eventops數組中的一個元素,根據宏定義選擇不同的事件監聽機制(例如poll/select/epoll等),這裡先不細說。

事件回調 在源代碼中一時還沒發現是在哪裡回調了事件處理函數,換一種途徑,通過gdb在例程的時間事件處理函數設置斷點跟蹤,可以發現事件處理函數是由 event_process_active_single_queue 函數調用的,關係如下:

(gdb) where 
#0  handle_timer (fd=-1, event=1, arg=0xbffff054) at mytimer.c:19 
#1  0x0011e248 in event_process_active_single_queue (base=0x804a008, flags=0) 
    at event.c:1340 
#2   event_process_active (base=0x804a008, flags=0) at event.c:1407 
#3  event_base_loop (base=0x804a008, flags=0) at event.c:1604 
#4  0x0011f255 in event_base_dispatch (event_base=0x804a008) at event.c:1435 
#5  0x080486c4 in main (argc=1, argv=0xbffff164) at mytimer.c:43 

书籍推荐