libevent源碼分析一--io事件響應

  這篇文章將分析libevent如何組織io事件,如何捕捉事件的發生並進行相應的響應。這裏不會詳細分析event與event_base的細節,僅描述io事件如何存儲與如何響應。windows

1.  select

  libevent實現io事件的backend實際上使用的是io複用接口,如select, poll, epoll等,這裏以最簡單的select爲例進行說明。首先簡單介紹一下select接口:後端

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

readfds, writefds, exceptfds均是文件描述符集合指針,調用該函數後,若readfds集合中的fd可讀,或者writefds集合中的fd可寫,或者exceptfds集合中的fd發生錯誤,或者阻塞的時間達到了timeout,函數返回。數組

  函數返回0,返回結果readfds集合中包含了入參readfds中如今讀不會被阻塞的fd,返回結果writefds包含了對應的寫不會被阻塞的fd,exceptfds包含了全部發生異常的fd。若是超時,函數返回-1,這些集合爲空。數據結構

  能夠看到,select調用須要傳入感興趣的io的文件描述符fd,而libevent中你們熟悉的是event_base,event結構體,event添加到event_base後就能夠等待事件觸發了,libevent是如何關聯event, event_base與select的呢?函數

事實上,在libevent的源碼select.c的,定義了一個數據結構struct selectop,oop

struct selectop {
    int event_fds;        /* Highest fd in fd set */
    int event_fdsz;
    int resize_out_sets;
    fd_set *event_readset_in;
    fd_set *event_writeset_in;
    fd_set *event_readset_out;
    fd_set *event_writeset_out;
};

它保存了libevent每次調用select須要使用到的入參與出參,這裏保存的文件描述符是與加入到event_base中的event對應的。每當一個新的io的event加入到event_base, 內部都會將event對應的文件描述符fd加入到event_readset_in與event_writeset_in中,這取決於event感興趣的是讀事件仍是寫事件。相應的函數調用棧是:spa

  event_add->event_add_nolock_->evmap_io_add_->select_add指針

當libevent使用select做爲io的backend時,event_base中的成員evbase即指向動態分配的結構selectop,那麼全部感興趣的io事件的文件描述符都已經保存在了屬於event_base的selectop結構中,調用select時便可使用。code

2.  event_io_map

  前面分析了select使用的入參如何保存,但除了事件對應的文件描述符,libevent一樣須要保存結構event,由於event中還記錄了許多其它的信息,如事件發生時調用的回調函數,事件的超時時間等。所以,加入到event_base中的event結構也須要保存,event_base中的成員struct event_io_map io的做用正是用來保存添加的io事件的event結構。blog

   struct event_io_map是一個hash表,若是是在非windows環境下,這個hash表能夠簡單地以一個動態數組實現。爲描述簡單,這裏假設是在非windows環境下。其定義以下:

struct event_signal_map {
    /* An array of evmap_io * or of evmap_signal *; empty entries are
     * set to NULL. */
    void **entries;
    /* The number of entries available in entries */
    int nentries;
}; 

  它包含一個動態數組entries,數組長度以nentries表示。動態數組的內容是指向動態分配的結構evmap_io的指針。event對應的文件描述符fd做爲它在動態數組中的索引,而同一個fd可能有多個感興趣的事件加入到同一個event_base中,所以將它們鏈接起來構成雙向鏈表以解決衝突,這個雙向鏈表就是struct evmap_io,即以event的fd做爲索引便可以找到這些事件組成的雙向鏈表。event結構中的成員ev_io包含兩個指針即雙向鏈表的前驅指針與後繼指針。event_base中的成員io的存儲結構能夠用圖2-1表示,

圖2-1 event_io_map

3.  event_base_loop

  包含回調信息的event結構已經存入了event_base的io成員,對於select,event相應的文件描述符也已經保存在了event_base的evbase成員指向的struct selectop結構中。那麼libevent最終是如何等待讀寫事件的發生並最終調用相應的回調函數的呢?答案是event_base_loop函數。

  首先,針對io事件,event_base_loop每一次循環,都會調用後端的dispatch函數,針對select後端,這個dispatch函數是select_dispatch,而select_dispatch又會調用select函數。而後,當select返回結果後,select_dispatch根據文件描述符從event_base的hash表io中將觸發的event的回調函數加入到event_base的待執行回調函數鏈表,這個待執行回調函數鏈表由event_base的成員struct evcallback_list *activequeues保存。最後,event_base_loop在dispatch以後即會執行鏈表上的回調函數,完成事件響應。

4.  activequeues

  activequeues是一個evcallback_list類型的動態數組,用來實現事件的優先級,數組每個成員都是一個待執行回調函數的鏈表。event_base_loop中執行這些鏈表上的函數時,以索引0開始按遞增的順序掃描數組,若數組成員指向的鏈表不爲空,則依次執行上面的回調函數,索引越小,對應鏈表上的回調函數越先被執行,構成了事件的優先級。activequeues的結構可以使用圖4-1表示,

圖4-1 event_base的activequeues成員結構

  libevent處理io事件的流程簡化總結爲:首先將io事件的event結構加入到event_base的io成員中,並將對應的fd保存到evbase指向的結構中;而後event_base_loop調用dispatch,將觸發的事件的回調函數添加到activequeues多優先級鏈表上;最後event_base_loop中執行activequeues上的回調函數,完成事件響應。

5.  部分概念說明表

event_base

libevent中的基本結構,全部的event添加到該結構的實例中,再調用event_base_loop處理其中的事件

event

libevent中表達一個事件的結構,包含了文件描述符,回調函數等信息

struct selectop

select後端的結構,存儲select函數須要的參數信息的結構,event_base中使用evbase指向這些信息

select.c

libevent中實現select後端的源碼文件

struct event_io_map

event_base中io成員的類型,用來存儲io類型的event結構的hash表

struct evmap_io

雙向鏈表描述結構,用於io類型的event

activequeues

event_base結構中的成員,evcallback_list類型的動態數組,保存待執行的回調函數的鏈表。

 

 

 

 

 

 

 

 

 

 

 

 

select後端的定義在select.c源碼文件中

  event/event_callback結構定義在源碼文件event_struct.h中

  event_base結構定義在源碼文件event-internal.h中

  event_add/event_base_loop/event_add_nolock函數定義在event.c函數中

  evmap_io_add_函數與evmap_io/event_io_map結構定義在evmap.c文件中

相關文章
相關標籤/搜索