libevent學習筆記(參考libevent深度剖析)

原文鏈接:https://www.cnblogs.com/secondtonone1/p/5535722.html

轉自:https://www.cnblogs.com/secondtonone1/p/5535722.html

 

最近自學libevent事件驅動庫,參考的資料爲libevent2.2版本以及張亮提供的《Libevent源碼深度剖析》,

參考資料: http://blog.csdn.net/sparkliang/article/details/4957667

libevent好處之類的就不贅述了,libevent和libiop,redis等一樣都是採用事件回調機制,這種模式

被稱作Reactor模式。正常事件處理流程是應用程序調用某個接口觸發某個功能,而Reactor模式需要

我們將這些接口和宿主指針(誰調用這些接口)註冊在Reactor,在合適的時機Reactor使用宿主指針

調用註冊好的回調函數。

 

一: Reactor基本知識

Reactor 模式是編寫高性能網絡服務器的必備技術之一,它具有如下的優點:
1)響應快,不必爲單個同步時間所阻塞,雖然 Reactor 本身依然是同步的;
2)編程相對簡單,可以最大程度的避免複雜的多線程及同步問題,並且避免了多線程/
進程的切換開銷;
3)可擴展性,可以方便的通過增加 Reactor 實例個數來充分利用 CPU 資源;
4)可複用性, reactor 框架本身與具體事件處理邏輯無關,具有很高的複用性;

 

Reactor模式框架

 

1) Handle 意思爲句柄,在Linux表示文件描述符,在windows是socket或者handle。

2)EventDemultiplexer 表示事件多路分發機制,調用系統提供的I/O多路複用機制,

比如select,epoll,程序先將關注的句柄註冊到EventDemultiplexer上,當有關注的事件

到來時,觸發EventDemultiplexer通知程序,程序調用之前註冊好的回調函數完成消息

相應。對應到 libevent 中,依然是 select、 poll、 epoll 等,但是 libevent 使用結構體eventop

進行了 封裝,以統一的接口來支持這些 I/O 多路複用機制,達到了對外隱藏底層系統機制的目的。

 

3)Reactor——反應器

Reactor,是事件管理的接口,內部使用 event demultiplexer 註冊、註銷事件;並運行事
件循環,當有事件進入“就緒”狀態時,調用註冊事件的回調函數處理事件。
對應到 libevent 中,就是 event_base 結構體。
一個典型的Reactor聲明方式
class Reactor
{
public:
int register_handler(Event_Handler *pHandler, int event);
int remove_handler(Event_Handler *pHandler, int event);
void handle_events(timeval *ptv);
// ...
};

 

 

4) Event Handler——事件處理程序
事件處理程序提供了一組接口,每個接口對應了一種類型的事件,供 Reactor 在相應的
事件發生時調用,執行相應的事件處理。通常它會綁定一個有效的句柄。
對應到 libevent 中,就是 event 結構體。
下面是兩種典型的 Event Handler 類聲明方式, 二者互有優缺點。
7
class Event_Handler
{
public:
virtual void handle_read() = 0;
virtual void handle_write() = 0;
virtual void handle_timeout() = 0;
virtual void handle_close() = 0;
virtual HANDLE get_handle() = 0;
// ...
};
class Event_Handler
{
public:
// events maybe read/write/timeout/close .etc
virtual void handle_events(int events) = 0;
virtual HANDLE get_handle() = 0;
// ...
};

 

二:如何使用libevent庫提供的API

1)首先初始化 libevent 庫,並保存返回的指針
struct event_base * base = event_init();
實際上這一步相當於初始化一個 Reactor 實例;在初始化 libevent 後,就可以註冊事件了。

2)設置event屬性和回調函數

調用函數void event_set(struct event *ev, int fd, short event, void (*cb)(int,
short, void *), void *arg);

每個參數的意義:

ev:執行要初始化的 event 對象;
fd:該 event 綁定的“句柄”,對於信號事件,它就是關注的信號;
event:在該 fd 上關注的事件類型,它可以是 EV_READ, EV_WRITE, EV_SIGNAL;
cb:這是一個函數指針,當 fd 上的事件 event 發生時,調用該函數執行處理,它有三個參數,

分別是關注的fd, 關注的事件類型(讀/寫/信號),回調函數的參數void* arg,調用時由

event_base 負責傳入,按順序,實際上就是 event_set 時的 fd, event 和 arg;

arg:傳遞給 cb 函數指針的參數;

由於定時事件不需要 fd,並且定時事件是根據添加時( event_add)的超時值設定的,因此
這裏 event 也不需要設置。
這一步相當於初始化一個 event handler,在 libevent 中事件類型保存在 event 結構體中。
注意: libevent 並不會管理 event 事件集合,這需要應用程序自行管理;

 

3)設置 event 從屬的 event_base
event_base_set(base, &ev);
這一步相當於指明 event 要註冊到哪個 event_base 實例上;


4)將事件添加到事件隊列裏
event_add(&ev, timeout);
基本信息都已設置完成,只要簡單的調用 event_add()函數即可完成,其中 timeout 是定時值;
10
這一步相當於調用 Reactor::register_handler()函數註冊事件。


5)程序進入無限循環,等待就緒事件並執行事件處理
event_base_dispatch(base);

 

看一下libevent提供的sample

複製代碼

int
main(int argc, char **argv)
{
    struct event evfifo;
#ifdef WIN32
    HANDLE socket;
    /* Open a file. */
    socket = CreateFileA("test.txt",    /* open File */
            GENERIC_READ,        /* open for reading */
            0,            /* do not share */
            NULL,            /* no security */
            OPEN_EXISTING,        /* existing file only */
            FILE_ATTRIBUTE_NORMAL,    /* normal file */
            NULL);            /* no attr. template */

    if (socket == INVALID_HANDLE_VALUE)
        return 1;

#else
    struct stat st;
    const char *fifo = "event.fifo";
    int socket;

    if (lstat(fifo, &st) == 0) {
        if ((st.st_mode & S_IFMT) == S_IFREG) {
            errno = EEXIST;
            perror("lstat");
            exit(1);
        }
    }

    unlink(fifo);
    if (mkfifo(fifo, 0600) == -1) {
        perror("mkfifo");
        exit(1);
    }

    /* Linux pipes are broken, we need O_RDWR instead of O_RDONLY */
#ifdef __linux
    socket = open(fifo, O_RDWR | O_NONBLOCK, 0);
#else
    socket = open(fifo, O_RDONLY | O_NONBLOCK, 0);
#endif

    if (socket == -1) {
        perror("open");
        exit(1);
    }

    fprintf(stderr, "Write data to %s\n", fifo);
#endif
    /* Initalize the event library */
    event_init();

    /* Initalize one event */
#ifdef WIN32
    event_set(&evfifo, (evutil_socket_t)socket, EV_READ, fifo_read, &evfifo);
#else
    event_set(&evfifo, socket, EV_READ, fifo_read, &evfifo);
#endif

    /* Add it to the active events, without a timeout */
    event_add(&evfifo, NULL);

    event_dispatch();
#ifdef WIN32
    CloseHandle(socket);
#endif
    return (0);
}

複製代碼

main函數裏調用event_init()初始化一個event_base,

之後調用event_set對event設置了回調函數和讀事件關注,

event_add將此事件加入event隊列裏,超時設置爲空

最後調用event_dispatch()進行事件輪訓派發。

fifo_read是一個回調函數,格式就是之前說的cb格式

複製代碼

static void
fifo_read(evutil_socket_t fd, short event, void *arg)
{
    char buf[255];
    int len;
    struct event *ev = arg;
#ifdef WIN32
    DWORD dwBytesRead;
#endif

    /* Reschedule this event */
    event_add(ev, NULL);

    fprintf(stderr, "fifo_read called with fd: %d, event: %d, arg: %p\n",
        (int)fd, event, arg);
#ifdef WIN32
    len = ReadFile((HANDLE)fd, buf, sizeof(buf) - 1, &dwBytesRead, NULL);

    /* Check for end of file. */
    if (len && dwBytesRead == 0) {
        fprintf(stderr, "End Of File");
        event_del(ev);
        return;
    }

    buf[dwBytesRead] = '\0';
#else
    len = read(fd, buf, sizeof(buf) - 1);

    if (len == -1) {
        perror("read");
        return;
    } else if (len == 0) {
        fprintf(stderr, "Connection closed\n");
        return;
    }

    buf[len] = '\0';
#endif
    fprintf(stdout, "Read: %s\n", buf);
}

複製代碼

 

三:《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結構體,這是libevent核心結構

複製代碼

struct event {
    TAILQ_ENTRY(event) ev_active_next;
    TAILQ_ENTRY(event) ev_next;
    /* for managing timeouts */
    union {
        TAILQ_ENTRY(event) ev_next_with_common_timeout;
        int min_heap_idx;
    } ev_timeout_pos;
    evutil_socket_t ev_fd;

    struct event_base *ev_base;

    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;

    short ev_events;
    short ev_res;        /* result passed to event callback */
    short ev_flags;
    ev_uint8_t ev_pri;    /* smaller numbers are higher priority */
    ev_uint8_t ev_closure;
    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;
};

複製代碼

 

 ev_active_next: 表示就緒狀態的事件鏈表指針,當關注的事件就緒後,會把

對應的event放入active的隊列裏。表示該事件在active隊列裏的位置

ev_next:表示所有事件隊列鏈表的指針。表示該事件在所有時間列表的位置。

ev_timeout_pos:用於管理超時

ev_fd:event綁定的socket描述符

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

 

ev_res:記錄了當前激活事件的類型;

 

ev_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 已被初始化

 

ev_pri:當前事件的優先級

ev_timeout:超時時間設置

ev_callback:該事件對應的回調函數,和cb類型一樣

ev_arg:回調函數用到參數

ev_ncalls:事件就緒執行時,調用 ev_callback 的次數,通常爲 1;
ev_pncalls:指針,通常指向 ev_ncalls 或者爲 NULL;

 

 

五:libevent對於event的管理和使用

 

對於event使用流程之前有講過,需要設置event的屬性和

回調函數,然後將其加入event隊列裏。設置event屬性和回調函數

的api如下

 

複製代碼

void
event_set(struct event *ev, evutil_socket_t fd, short events,
      void (*callback)(evutil_socket_t, short, void *), void *arg)
{
    int r;
    r = event_assign(ev, current_base, fd, events, callback, arg);
    EVUTIL_ASSERT(r == 0);
}

複製代碼

 

ev:表示event指針

fd:event要關注的socket fd

events:event關注的事件類型(讀寫I/O,信號,時間事件等)

callback:event就緒後會調用的回調函數

arg:調用回調函數時,函數的參數

該函數內部調用了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)
{
    if (!base)
        base = current_base;

    _event_debug_assert_not_added(ev);

    ev->ev_base = base;

    ev->ev_callback = callback;
    ev->ev_arg = arg;
    ev->ev_fd = fd;
    ev->ev_events = events;
    ev->ev_res = 0;
    ev->ev_flags = EVLIST_INIT;
    ev->ev_ncalls = 0;
    ev->ev_pncalls = NULL;

    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;
        }
    }

    min_heap_elem_init(ev);

    if (base != NULL) {
        /* by default, we put new events into the middle priority */
        ev->ev_pri = base->nactivequeues / 2;
    }

    _event_debug_note_setup(ev);

    return 0;
}

複製代碼

 

 設置好event屬性和回調函數後,需要將event設置到指定的event_base中,

因爲有可能存在很多event_base。調用如下函數

複製代碼

int
event_base_set(struct event_base *base, struct event *ev)
{
    /* Only innocent events may be assigned to a different base */
    if (ev->ev_flags != EVLIST_INIT)
        return (-1);

    _event_debug_assert_is_setup(ev);

    ev->ev_base = base;
    ev->ev_pri = base->nactivequeues/2;

    return (0);
}

複製代碼

該函數設置了優先級和隸屬於哪個base

另外還有一個設置優先級的函數

int event_priority_set(struct event *ev, int pri)
設置event ev的優先級,沒什麼可說的,注意的一點就是:當ev正處於就緒狀態
時,不能設置,返回-1

 

 六:event_base結構分析和使用

 

event_base結構如下

 

複製代碼

struct event_base {
    /** Function pointers and other data to describe this event_base's
     * backend. */
    const struct eventop *evsel;
    /** Pointer to backend-specific data. */
    void *evbase;

    /** List of changes to tell backend about at next dispatch.  Only used
     * by the O(1) backends. */
    struct event_changelist changelist;

    /** Function pointers used to describe the backend that this event_base
     * uses for signals */
    const struct eventop *evsigsel;
    /** Data to implement the common signal handelr code. */
    struct evsig_info sig;

    /** Number of virtual events */
    int virtual_event_count;
    /** Number of total events added to this event_base */
    int event_count;
    /** Number of total events active in this event_base */
    int event_count_active;

    /** Set if we should terminate the loop once we're done processing
     * events. */
    int event_gotterm;
    /** Set if we should terminate the loop immediately */
    int event_break;
    /** Set if we should start a new instance of the loop immediately. */
    int event_continue;

    /** The currently running priority of events */
    int event_running_priority;

    /** Set if we're running the event_base_loop function, to prevent
     * reentrant invocation. */
    int running_loop;

    /* Active event management. */
    /** An array of nactivequeues queues for active events (ones that
     * have triggered, and whose callbacks need to be called).  Low
     * priority numbers are more important, and stall higher ones.
     */
    struct event_list *activequeues;
    /** The length of the activequeues array */
    int nactivequeues;

    /* common timeout logic */

    /** An array of common_timeout_list* for all of the common timeout
     * values we know. */
    struct common_timeout_list **common_timeout_queues;
    /** The number of entries used in common_timeout_queues */
    int n_common_timeouts;
    /** The total size of common_timeout_queues. */
    int n_common_timeouts_allocated;

    /** List of defered_cb that are active.  We run these after the active
     * events. */
    struct deferred_cb_queue defer_queue;

    /** Mapping from file descriptors to enabled (added) events */
    struct event_io_map io;

    /** Mapping from signal numbers to enabled (added) events. */
    struct event_signal_map sigmap;

    /** All events that have been enabled (added) in this event_base */
    struct event_list eventqueue;

    /** Stored timeval; used to detect when time is running backwards. */
    struct timeval event_tv;

    /** Priority queue of events with timeouts. */
    struct min_heap timeheap;

    /** Stored timeval: used to avoid calling gettimeofday/clock_gettime
     * too often. */
    struct timeval tv_cache;

#if defined(_EVENT_HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
    /** Difference between internal time (maybe from clock_gettime) and
     * gettimeofday. */
    struct timeval tv_clock_diff;
    /** Second in which we last updated tv_clock_diff, in monotonic time. */
    time_t last_updated_clock_diff;
#endif

#ifndef _EVENT_DISABLE_THREAD_SUPPORT
    /* threading support */
    /** The thread currently running the event_loop for this base */
    unsigned long th_owner_id;
    /** A lock to prevent conflicting accesses to this event_base */
    void *th_base_lock;
    /** The event whose callback is executing right now */
    struct event *current_event;
    /** A condition that gets signalled when we're done processing an
     * event with waiters on it. */
    void *current_event_cond;
    /** Number of threads blocking on current_event_cond. */
    int current_event_waiters;
#endif

#ifdef WIN32
    /** IOCP support structure, if IOCP is enabled. */
    struct event_iocp_port *iocp;
#endif

    /** Flags that this base was configured with */
    enum event_base_config_flag flags;

    /* Notify main thread to wake up break, etc. */
    /** True if the base already has a pending notify, and we don't need
     * to add any more. */
    int is_notify_pending;
    /** A socketpair used by some th_notify functions to wake up the main
     * thread. */
    evutil_socket_t th_notify_fd[2];
    /** An event used by some th_notify functions to wake up the main
     * thread. */
    struct event th_notify;
    /** A function used to wake up the main thread from another thread. */
    int (*th_notify_fn)(struct event_base *base);
};


複製代碼

 evsel:eventop類型的指針,針對不同的模型封裝了同一套操作

evbase: 不同模型開闢的數據空間,放到event_base裏。

evbase和evsel配套使用,eventop結構如下

 

複製代碼

struct eventop {
    /** The name of this backend. */
    const char *name;
    /** Function to set up an event_base to use this backend.  It should
     * create a new structure holding whatever information is needed to
     * run the backend, and return it.  The returned pointer will get
     * stored by event_init into the event_base.evbase field.  On failure,
     * this function should return NULL. */
    void *(*init)(struct event_base *);
    /** Enable reading/writing on a given fd or signal.  'events' will be
     * the events that we're trying to enable: one or more of EV_READ,
     * EV_WRITE, EV_SIGNAL, and EV_ET.  'old' will be those events that
     * were enabled on this fd previously.  'fdinfo' will be a structure
     * associated with the fd by the evmap; its size is defined by the
     * fdinfo field below.  It will be set to 0 the first time the fd is
     * added.  The function should return 0 on success and -1 on error.
     */
    int (*add)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
    /** As "add", except 'events' contains the events we mean to disable. */
    int (*del)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
    /** Function to implement the core of an event loop.  It must see which
        added events are ready, and cause event_active to be called for each
        active event (usually via event_io_active or such).  It should
        return 0 on success and -1 on error.
     */
    int (*dispatch)(struct event_base *, struct timeval *);
    /** Function to clean up and free our data from the event_base. */
    void (*dealloc)(struct event_base *);
    /** Flag: set if we need to reinitialize the event base after we fork.
     */
    int need_reinit;
    /** Bit-array of supported event_method_features that this backend can
     * provide. */
    enum event_method_feature features;
    /** Length of the extra information we should record for each fd that
        has one or more active events.  This information is recorded
        as part of the evmap entry for each fd, and passed as an argument
        to the add and del functions above.
     */
    size_t fdinfo_len;
};

複製代碼

 

eventop封裝了epoll, select 等不同網絡模型的init,add,deldispatch等回調函數。

changelist:通知後端改變的列表。

evsigsel:告訴後臺 eventbase用於處理signal事件的函數指針

event_count:eventbase中總共的event數量

event_count_active: eventbase中激活的event數量

event_gotterm:這個參數設置後,一旦我們對事件做了處理,就要終止循環。

event_break:立刻結束循環

event_continue:立刻開啓一個新的循環實例

event_running_priority:當前事件隊列的優先級。

activequeues:激活的事件隊列,priority數字小的先觸發。

struct event_io_map io: 用於管理io事件的描述符

struct event_signal_map sigmap: 用於管理signal的描述符

eventqueue:所有被加入event_base的事件組成的隊列

event_tv:後臺記錄運行的時間

timeheap:用小根堆管理超時事件隊列

 

其他的參數不是很瞭解,以後用到了再琢磨。

 

libevent提供如下兩個函數可以生成eventbase實例

複製代碼

event_init(void)
{
    struct event_base *base = event_base_new_with_config(NULL);

    if (base == NULL) {
        event_errx(1, "%s: Unable to construct event_base", __func__);
        return NULL;
    }

    current_base = base;

    return (base);
}

struct event_base *
event_base_new(void)
{
    struct event_base *base = NULL;
    struct event_config *cfg = event_config_new();
    if (cfg) {
        base = event_base_new_with_config(cfg);
        event_config_free(cfg);
    }
    return base;
}

複製代碼

 

到此爲止基本的結構介紹完了。

 

七libevent事件添加/刪除/初始化/派發接口分析

1 event_base初始化和模型初始化

先看下初始化,實際上初始化是在event_base_new_with_config函數中完成的。

 

複製代碼

struct event_base *
event_base_new_with_config(const struct event_config *cfg)
{
    ...
   base->evsel = eventops[i];
    base->evbase = base->evsel->init(base);
    ...
}    

複製代碼

 

event_init和eventbase_new都會調用event_base_new_with_config這個函數.

而base->evsel是不同模型的指針,進而實現調用不同模型的init

 

2 事件添加註冊函數

複製代碼

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);
}

複製代碼

防止多線程訪問出錯,加了鎖,並且調用了

複製代碼

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;
 
    ...
    /*
     * 新的timer事件,調用timer heap接口在堆上預留一個位置
   *向系統I/O機制註冊可能會失敗,而當在堆上預留成功後,
    * 定時事件的添加將肯定不會失敗;

    */
    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,信號,超時事件等將fd放入不同的map
    if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&
        !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {
        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);
        if (res != -1)
           //將event放入事件隊列
            event_queue_insert(base, ev, EVLIST_INSERTED);
        if (res == 1) {
            /* evmap says we need to notify the main thread. */
            notify = 1;
            res = 0;
        }
    }

    /*
         EVLIST_TIMEOUT表明event已經在定時器堆中了,刪除舊的
         */
        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);
        }

        /* // 如果事件已經是就緒狀態則從激活鏈表中刪除 */
        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);

       ...       // 計算時間,並插入到timer小根堆中
        event_queue_insert(base, ev, EVLIST_TIMEOUT);
        return (res);
}

複製代碼

 

在evmap_io_add裏完成了io事件對於不同網絡模型(select/epoll等)的綁定

複製代碼

int
evmap_io_add(struct event_base *base, evutil_socket_t fd, struct event *ev)
{
     ...
     const struct eventop *evsel = base->evsel;
     struct event_io_map *io = &base->io;
     ...
     //將event的fd和類型註冊到不同的網絡模型
     if (evsel->add(base, ev->ev_fd,
            old, (ev->ev_events & EV_ET) | res, extra) == -1)
            return (-1);
     ...
   
}

複製代碼

 

event_queue_insert()負責將事件插入到對應的鏈表中;
event_queue_remove()負責將事件從對應的鏈表中刪除;

 

複製代碼

void event_queue_insert(struct event_base *base, struct event *ev,
int queue)
{
    // ev可能已經在激活列表中了,避免重複插入
    if (ev->ev_flags & queue) {
    if (queue & EVLIST_ACTIVE)
    return;
    }
    // // 記錄queue標記
    ev->ev_flags |= queue;
    switch (queue) {
         // I/O或Signal事件,加入已註冊事件鏈表
        case EVLIST_INSERTED:
        TAILQ_INSERT_TAIL(&base->eventqueue, ev, ev_next);
        break;
        // 就緒事件,加入激活鏈表
        case EVLIST_ACTIVE: 
        base->event_count_active++;
        TAILQ_INSERT_TAIL(base->activequeues[ev->ev_pri], ev,
        ev_active_next);
        break;
        // 定時事件,加入堆
        case EVLIST_TIMEOUT: 
        min_heap_push(&base->timeheap, ev);
        break;
    }
}
                                          

複製代碼

3刪除事件

複製代碼

int
event_del(struct event *ev)
{
    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_del_internal(ev);

    EVBASE_RELEASE_LOCK(ev->ev_base, th_base_lock);

    return (res);
}

複製代碼

內部調用了

複製代碼

static inline int
event_del_internal(struct event *ev)
{
    struct event_base *base;
    int res = 0, notify = 0;

    event_debug(("event_del: %p (fd "EV_SOCK_FMT"), callback %p",
        ev, EV_SOCK_ARG(ev->ev_fd), ev->ev_callback));

    /* An event without a base has not been added */
    if (ev->ev_base == NULL)
        return (-1);

  
    base = ev->ev_base;


    EVUTIL_ASSERT(!(ev->ev_flags & ~EVLIST_ALL));
    //根據不同flag從隊列中移除
    if (ev->ev_flags & EVLIST_TIMEOUT) {
        /* NOTE: We never need to notify the main thread because of a
         * deleted timeout event: all that could happen if we don't is
         * that the dispatch loop might wake up too early.  But the
         * point of notifying the main thread _is_ to wake up the
         * dispatch loop early anyway, so we wouldn't gain anything by
         * doing it.
         */
        event_queue_remove(base, ev, EVLIST_TIMEOUT);
    }

    if (ev->ev_flags & EVLIST_ACTIVE)
        event_queue_remove(base, ev, EVLIST_ACTIVE);

    if (ev->ev_flags & EVLIST_INSERTED) {
        event_queue_remove(base, ev, EVLIST_INSERTED);
        if (ev->ev_events & (EV_READ|EV_WRITE))
           //io事件從map中移除
            res = evmap_io_del(base, ev->ev_fd, ev);
        else
            res = evmap_signal_del(base, (int)ev->ev_fd, ev);
        if (res == 1) {
            /* evmap says we need to notify the main thread. */
            notify = 1;
            res = 0;
        }
    }return (res);
}

複製代碼

evmap_io_del 中完成了不同模型del函數調用

複製代碼

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

    if (evsel->del(base, ev->ev_fd, old, res, extra) == -1)
            return (-1);

}

複製代碼

 

 4 事件派發

複製代碼


 

int
event_base_loop(struct event_base *base, int flags)

{
    const struct eventop *evsel = base->evsel;
    struct timeval tv;
    struct timeval *tv_p;
    int res, done, retval = 0;

   while (!done) {
        base->event_continue = 0;

        /* Terminate the loop if we have been asked to */

    //設置了event_gotterm或者event_break都會導致break
        if (base->event_gotterm) {
            break;
        }

        if (base->event_break) {
            break;
        }
        //校正系統時間
        timeout_correct(base, &tv);
        // 根據timer heap中事件的最小超時時間,計算系統I/O demultiplexer
         //的最大等待時間
        tv_p = &tv;
        if (!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK)) {
            timeout_next(base, &tv_p);
        } else {
            /*
             * 如果有激活的事件,我們立即處理,不用等待
             */
            evutil_timerclear(&tv);
        }

        /* If we have no events, we just exit */
        if (!event_haveevents(base) && !N_ACTIVE_CALLBACKS(base)) {
            event_debug(("%s: no events registered.", __func__));
            retval = 1;
            goto done;
        }

        /* update last old time */
        gettime(base, &base->event_tv);

        clear_time_cache(base);
        //完成事件的派發,將就緒的事件放倒active列表裏
        res = evsel->dispatch(base, tv_p);

        if (res == -1) {
            event_debug(("%s: dispatch returned unsuccessfully.",
                __func__));
            retval = -1;
            goto done;
        }

        update_time_cache(base);

        timeout_process(base);
    // 調用event_process_active()處理激活鏈表中的就緒event,調用其
       //回調函數執行事件處理
      // 該函數會尋找最高優先級(priority值越小優先級越高)的激活事件
      //鏈表,
      // 然後處理鏈表中的所有就緒事件;
      // 因此低優先級的就緒事件可能得不到及時處理;
        if (N_ACTIVE_CALLBACKS(base)) {
            int n = event_process_active(base);
            if ((flags & EVLOOP_ONCE)
                && N_ACTIVE_CALLBACKS(base) == 0
                && n != 0)
                done = 1;
        } else if (flags & EVLOOP_NONBLOCK)
            done = 1;
    }
    event_debug(("%s: asked to terminate loop.", __func__));

done:
    clear_time_cache(base);
    base->running_loop = 0;

    EVBASE_RELEASE_LOCK(base, th_base_lock);

    return (retval);
}

複製代碼

 調用evsel->dispatch後將事件放入active隊列

然後process_active處理激活隊列裏的事件

複製代碼

static int
event_process_active(struct event_base *base)
{
    /* Caller must hold th_base_lock */
    struct event_list *activeq = NULL;
    int i, c = 0;

    for (i = 0; i < base->nactivequeues; ++i) {
        if (TAILQ_FIRST(&base->activequeues[i]) != NULL) {
            base->event_running_priority = i;
            activeq = &base->activequeues[i];
            c = event_process_active_single_queue(base, activeq);
            if (c < 0) {
                base->event_running_priority = -1;
                return -1;
            } else if (c > 0)
                break; /* Processed a real event; do not
                    * consider lower-priority events */
            /* If we get here, all of the events we processed
             * were internal.  Continue. */
        }
    }

    event_process_deferred_callbacks(&base->defer_queue,&base->event_break);
    base->event_running_priority = -1;
    return c;
}

複製代碼

優先級數字base->nactivequeues 以下的會被先處理。

到目前爲止介紹了libevent庫的基本api和流程。
對於不同的網絡模型libevent是如何封裝的呢?

八libevent對於網絡模型的封裝(epoll爲例)

epoll基本單元封裝
struct epollop {
    struct epoll_event *events;
    int nevents;
    int epfd;
};

epfd:epoll_create 返回的epoll表句柄

events: 表示epoll表監聽的epoll_event 隊列

nevents:epoll_event隊列大小

 在介紹epoll封裝的一些接口前,先看以下兩個結構體定義的對象

複製代碼

static const struct eventop epollops_changelist = {
    "epoll (with changelist)",
    epoll_init,
    event_changelist_add,
    event_changelist_del,
    epoll_dispatch,
    epoll_dealloc,
    1, /* need reinit */
    EV_FEATURE_ET|EV_FEATURE_O1,
    EVENT_CHANGELIST_FDINFO_SIZE
};

複製代碼

結構體對象封裝了epoll操作的函數指針,

event_changelist_add表示設置changlist標記位時將事件加入changelist

event_changelist_del表示如果設置了changelist標記位事件從changelist

中移除。

複製代碼

const struct eventop epollops = {
    "epoll",
    epoll_init,
    epoll_nochangelist_add,
    epoll_nochangelist_del,
    epoll_dispatch,
    epoll_dealloc,
    1, /* need reinit */
    EV_FEATURE_ET|EV_FEATURE_O1,
    0
};

複製代碼

這個結構體對象對應的是沒設置changelist標記位時epoll的操作接口

接下來看下epoll_init函數

複製代碼

epoll_init(struct event_base *base)
{
    int epfd;
    struct epollop *epollop;

    /* Initialize the kernel queue.  (The size field is ignored since
     * 2.6.8.) */
    if ((epfd = epoll_create(32000)) == -1) {
        if (errno != ENOSYS)
            event_warn("epoll_create");
        return (NULL);
    }

    evutil_make_socket_closeonexec(epfd);

    if (!(epollop = mm_calloc(1, sizeof(struct epollop)))) {
        close(epfd);
        return (NULL);
    }

    epollop->epfd = epfd;

    /* Initialize fields */
    epollop->events = mm_calloc(INITIAL_NEVENT, sizeof(struct epoll_event));
    if (epollop->events == NULL) {
        mm_free(epollop);
        close(epfd);
        return (NULL);
    }
    epollop->nevents = INITIAL_NEVENT;
    //如果設置了EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST
    //添加和刪除事件的回調函數變爲changelist_add 和changelist_del

    if ((base->flags & EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST) != 0 ||
        ((base->flags & EVENT_BASE_FLAG_IGNORE_ENV) == 0 &&
        evutil_getenv("EVENT_EPOLL_USE_CHANGELIST") != NULL))
        base->evsel = &epollops_changelist;

    evsig_init(base);

    return (epollop);
}

複製代碼

接下來看下epoll封裝的事件註冊接口,
只看epoll_nochangelist_add,
epoll_changelist_add都是類似的。


複製代碼

static int
epoll_nochangelist_add(struct event_base *base, evutil_socket_t fd,
    short old, short events, void *p)
{
    struct event_change ch;
    ch.fd = fd;
    ch.old_events = old;
    ch.read_change = ch.write_change = 0;
    if (events & EV_WRITE)
        ch.write_change = EV_CHANGE_ADD |
            (events & EV_ET);
    if (events & EV_READ)
        ch.read_change = EV_CHANGE_ADD |
            (events & EV_ET);

    return epoll_apply_one_change(base, base->evbase, &ch);
}

複製代碼

epoll_apply_one_change中完成事件添加

複製代碼

static int
epoll_apply_one_change(struct event_base *base,
    struct epollop *epollop,
    const struct event_change *ch)
{
    struct epoll_event epev;
    int op, events = 0;

    if (1) {
        
        if ((ch->read_change & EV_CHANGE_ADD) ||
            (ch->write_change & EV_CHANGE_ADD)) {
           
            events = 0;
            op = EPOLL_CTL_ADD;
             //關注讀事件
            if (ch->read_change & EV_CHANGE_ADD) {
                events |= EPOLLIN;
            } else if (ch->read_change & EV_CHANGE_DEL) {
                ;
            } else if (ch->old_events & EV_READ) {
                events |= EPOLLIN;
            }
            //關注寫事件
            if (ch->write_change & EV_CHANGE_ADD) {
                events |= EPOLLOUT;
            } else if (ch->write_change & EV_CHANGE_DEL) {
                ;
            } else if (ch->old_events & EV_WRITE) {
                events |= EPOLLOUT;
            }
            //設置et模型
            if ((ch->read_change|ch->write_change) & EV_ET)
                events |= EPOLLET;
            //之前有事件關注,將操作改爲EPOLL_CTL_MOD
            if (ch->old_events) {
              
                op = EPOLL_CTL_MOD;
            }
        } else if ((ch->read_change & EV_CHANGE_DEL) ||
            (ch->write_change & EV_CHANGE_DEL)) {
          
            op = EPOLL_CTL_DEL;
            //之前關注過刪除該事件
            if (ch->read_change & EV_CHANGE_DEL) {
                if (ch->write_change & EV_CHANGE_DEL) {
                   //讀寫事件都設置刪除標記
                    events = EPOLLIN|EPOLLOUT;
                } else if (ch->old_events & EV_WRITE) {
                    events = EPOLLOUT;
                    //只有寫事件要求刪除,那麼更改爲讀事件
                    op = EPOLL_CTL_MOD;
                } else {
                    //只刪除讀事件
                    events = EPOLLIN;
                }
            } else if (ch->write_change & EV_CHANGE_DEL) {
                if (ch->old_events & EV_READ) {
                    events = EPOLLIN;
                    //更改爲關注讀事件
                    op = EPOLL_CTL_MOD;
                } else {
                   //只刪除寫事件
                    events = EPOLLOUT;
                }
            }
        }

        if (!events)
            return 0;

        memset(&epev, 0, sizeof(epev));
        epev.data.fd = ch->fd;
        epev.events = events;
        //調用epoll_ctl設置event進入epoll監聽隊列
        if (epoll_ctl(epollop->epfd, op, ch->fd, &epev) == -1) {
            if (op == EPOLL_CTL_MOD && errno == ENOENT) {
             
                if (epoll_ctl(epollop->epfd, EPOLL_CTL_ADD, ch->fd, &epev) == -1) {
                    event_warn("Epoll MOD(%d) on %d retried as ADD; that failed too",
                        (int)epev.events, ch->fd);
                    return -1;
                } else {
                    event_debug(("Epoll MOD(%d) on %d retried as ADD; succeeded.",
                        (int)epev.events,
                        ch->fd));
                }
            } else if (op == EPOLL_CTL_ADD && errno == EEXIST) {
              
                if (epoll_ctl(epollop->epfd, EPOLL_CTL_MOD, ch->fd, &epev) == -1) {
                    event_warn("Epoll ADD(%d) on %d retried as MOD; that failed too",
                        (int)epev.events, ch->fd);
                    return -1;
                } else {
                    event_debug(("Epoll ADD(%d) on %d retried as MOD; succeeded.",
                        (int)epev.events,
                        ch->fd));
                }
            } else if (op == EPOLL_CTL_DEL &&
                (errno == ENOENT || errno == EBADF ||
                errno == EPERM)) {
              
                event_debug(("Epoll DEL(%d) on fd %d gave %s: DEL was unnecessary.",
                    (int)epev.events,
                    ch->fd,
                    strerror(errno)));
            } else {
                event_warn("Epoll %s(%d) on fd %d failed.  Old events were %d; read change was %d (%s); write change was %d (%s)",
                    epoll_op_to_string(op),
                    (int)epev.events,
                    ch->fd,
                    ch->old_events,
                    ch->read_change,
                    change_to_string(ch->read_change),
                    ch->write_change,
                    change_to_string(ch->write_change));
                return -1;
            }
        } else {
            event_debug(("Epoll %s(%d) on fd %d okay. [old events were %d; read change was %d; write change was %d]",
                epoll_op_to_string(op),
                (int)epev.events,
                (int)ch->fd,
                ch->old_events,
                ch->read_change,
                ch->write_change));
        }
    }
    return 0;
}

複製代碼

 

刪除事件也是類似的,內部調用epoll_apply_onechange
知識將事件標記位EV_CHANGE_DEL,到函數內部解析爲epoll_del或者
epoll_mod

複製代碼

static int
epoll_nochangelist_del(struct event_base *base, evutil_socket_t fd,
    short old, short events, void *p)
{
    struct event_change ch;
    ch.fd = fd;
    ch.old_events = old;
    ch.read_change = ch.write_change = 0;
    if (events & EV_WRITE)
        ch.write_change = EV_CHANGE_DEL;
    if (events & EV_READ)
        ch.read_change = EV_CHANGE_DEL;

    return epoll_apply_one_change(base, base->evbase, &ch);
}

複製代碼

 

最後是事件派發

複製代碼

static int
epoll_dispatch(struct event_base *base, struct timeval *tv)
{
    struct epollop *epollop = base->evbase;
    struct epoll_event *events = epollop->events;
    int i, res;
    long timeout = -1;

    if (tv != NULL) {
        timeout = evutil_tv_to_msec(tv);
        if (timeout < 0 || timeout > MAX_EPOLL_TIMEOUT_MSEC) {
            /* Linux kernels can wait forever if the timeout is
             * too big; see comment on MAX_EPOLL_TIMEOUT_MSEC. */
            timeout = MAX_EPOLL_TIMEOUT_MSEC;
        }
    }
    
    //處理changelis列表的所有事件
    epoll_apply_changes(base);
    event_changelist_remove_all(&base->changelist, base);

    EVBASE_RELEASE_LOCK(base, th_base_lock);

    //處理epoll_wait返回就緒描述符個數
    res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout);

    EVBASE_ACQUIRE_LOCK(base, th_base_lock);

    if (res == -1) {
        if (errno != EINTR) {
            event_warn("epoll_wait");
            return (-1);
        }

        return (0);
    }

    event_debug(("%s: epoll_wait reports %d", __func__, res));
    EVUTIL_ASSERT(res <= epollop->nevents);

    for (i = 0; i < res; i++) {
        int what = events[i].events;
        short ev = 0;

        if (what & (EPOLLHUP|EPOLLERR)) {
            ev = EV_READ | EV_WRITE;
        } else {
            if (what & EPOLLIN)
                ev |= EV_READ;
            if (what & EPOLLOUT)
                ev |= EV_WRITE;
        }

        if (!ev)
            continue;
        //將event放入active隊列中
        evmap_io_active(base, events[i].data.fd, ev | EV_ET);
    }

    //epoll就緒數量等於隊列長度,額外開闢空間確保新的事件可以觸發
    if (res == epollop->nevents && epollop->nevents < MAX_NEVENT) {
        /* We used all of the event space this time.  We should
           be ready for more events next time. */
        int new_nevents = epollop->nevents * 2;
        struct epoll_event *new_events;

        new_events = mm_realloc(epollop->events,
            new_nevents * sizeof(struct epoll_event));
        if (new_events) {
            epollop->events = new_events;
            epollop->nevents = new_nevents;
        }
    }

    return (0);
}

複製代碼

 

以上就是livevent對於epoll的基本封裝。
整個結構體對象epollops在event.c中被賦值給eventops[]的

複製代碼

static const struct eventop *eventops[] = {
#ifdef _EVENT_HAVE_EVENT_PORTS
    &evportops,
#endif
#ifdef _EVENT_HAVE_WORKING_KQUEUE
    &kqops,
#endif
#ifdef _EVENT_HAVE_EPOLL
    &epollops,
#endif
#ifdef _EVENT_HAVE_DEVPOLL
    &devpollops,
#endif
#ifdef _EVENT_HAVE_POLL
    &pollops,
#endif
#ifdef _EVENT_HAVE_SELECT
    &selectops,
#endif
#ifdef WIN32
    &win32ops,
#endif
    NULL
};

複製代碼

 

之後在event_base_new_with_config中調用
 base->evsel = eventops[i];完成不同模型的結構體對象賦值給
base的evsel指針的。

目前對於libevent的分析和理解先告一段落,以後會更新對libevent的
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章