函數(shù)執(zhí)行,程序" />
0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Libevent框架庫(kù)簡(jiǎn)介

科技綠洲 ? 來(lái)源:Linux開(kāi)發(fā)架構(gòu)之路 ? 作者:Linux開(kāi)發(fā)架構(gòu)之路 ? 2023-11-09 16:43 ? 次閱讀

一、Libevent簡(jiǎn)介

Libevent是開(kāi)源社區(qū)一款高性能的I/O框架庫(kù),其具有如下特點(diǎn):

1、跨平臺(tái)支持。Libevent支持Linux、UNIX和Windows。

2、統(tǒng)一事件源。libevent對(duì)i/o事件、信號(hào)和定時(shí)事件提供統(tǒng)一的處理。

3、線程安全。libevent使用libevent_pthreads庫(kù)來(lái)提供線程安全支持。

4、基于reactor模式的實(shí)現(xiàn)。

5、輕量級(jí),專(zhuān)注于網(wǎng)絡(luò),沒(méi)有ACE那么臃腫龐大

6、可以注冊(cè)事件優(yōu)先級(jí)

二、Reactor 模式

2.1 Reactor簡(jiǎn)介

首先來(lái)回想一下普通函數(shù)調(diào)用的機(jī)制:程序調(diào)用某函數(shù)->函數(shù)執(zhí)行,程序等待->函數(shù)將結(jié)果和控制權(quán)返回給程->程序繼續(xù)處理。

Reactor 釋義“反應(yīng)堆”,是一種事件驅(qū)動(dòng)機(jī)制。和普通函數(shù)調(diào)用的不同之處在于:應(yīng)用程序不是主動(dòng)的調(diào)用某個(gè) API 完成處理,而是恰恰相反,Reactor 逆置了事件處理流程,應(yīng)用程序需要提供相應(yīng)的接口并注冊(cè)到 Reactor 上,如果相應(yīng)的事件發(fā)生,Reactor 將主動(dòng)調(diào)用應(yīng)用程序注冊(cè)的接口,這些接口又稱(chēng)為“回調(diào)函數(shù)”。使用 Libevent 也是向 Libevent 框架注冊(cè)相應(yīng)的事件和回調(diào)函數(shù);當(dāng)這些事件發(fā)生時(shí),Libevent 會(huì)調(diào)用這些回調(diào)函數(shù)處理相應(yīng)的事件(I/O 讀寫(xiě)、定時(shí)和信號(hào))。

2.2 Reactor 模式的優(yōu)點(diǎn)

Reactor 模式是編寫(xiě)高性能網(wǎng)絡(luò)服務(wù)器的必備技術(shù)之一,它具有如下的優(yōu)點(diǎn):

1)響應(yīng)快,不必為單個(gè)同步時(shí)間所阻塞,雖然 Reactor 本身依然是同步的;

2)編程相對(duì)簡(jiǎn)單,可以最大程度的避免復(fù)雜的多線程及同步問(wèn)題,并且避免了多線程/ 進(jìn)程的切換開(kāi)銷(xiāo);

3)可擴(kuò)展性,可以方便的通過(guò)增加 Reactor 實(shí)例個(gè)數(shù)來(lái)充分利用 CPU 資源;

4)可復(fù)用性,reactor 框架本身與具體事件處理邏輯無(wú)關(guān),具有很高的復(fù)用性;

2.3 Reactor 模式框架

使用 Reactor 模型,必備的幾個(gè)組件:事件源(描述符)、Reactor 框架、多路復(fù)用機(jī)制和事件處理程序,先來(lái)看看 Reactor 模型的整體框架,接下來(lái)再對(duì)每個(gè)組件做逐一說(shuō)明。

圖片

1)事件源(handle)

操作系統(tǒng)提供,用于識(shí)別每一個(gè)事件,如Socket描述符、文件描述符等。在Linux中,它用一個(gè)整數(shù)來(lái)表示。事件可以來(lái)自外部,如來(lái)自客戶(hù)端的連接請(qǐng)求、數(shù)據(jù)等。事件也可以來(lái)自?xún)?nèi)部,如定時(shí)器事件。

2)event demultiplexer——事件多路分發(fā)機(jī)制(同步事件分離器)

由操作系統(tǒng)提供的 I/O 多路復(fù)用機(jī)制,比如 select 和 epoll。 程序首先將其關(guān)心的句柄(事件源)及其事件注冊(cè)到 event demultiplexer 上; 當(dāng)有事件到達(dá)時(shí),event demultiplexer 會(huì)發(fā)出通知“在已經(jīng)注冊(cè)的句柄集中,一個(gè)或多 個(gè)句柄的事件已經(jīng)就緒”; 程序收到通知后,就可以在非阻塞的情況下對(duì)事件進(jìn)行處理了。 對(duì)應(yīng)到 libevent 中,依然是 select、poll、epoll 等,但是 libevent 使用結(jié)構(gòu)體 eventop 進(jìn)行了 封裝,以統(tǒng)一的接口來(lái)支持這些 I/O 多路復(fù)用機(jī)制,達(dá)到了對(duì)外隱藏底層系統(tǒng)機(jī)制的目的。

3)Reactor——反應(yīng)器(管理器)

定義了一些接口,用于應(yīng)用程序控制事件調(diào)度,以及應(yīng)用程序注冊(cè)、刪除事件處理器和相關(guān)的描述符。它是事件處理器的調(diào)度核心。 Reactor管理器使用同步事件分離器來(lái)等待事件的發(fā)生。一旦事件發(fā)生,Reactor管理器先是分離每個(gè)事件,然后調(diào)度事件處理器,最后調(diào)用相關(guān)的模板函數(shù)來(lái)處理這個(gè)事件。

4) Event Handler——事件處理程序(事件處理器接口)

事件處理程序提供了一組接口,每個(gè)接口對(duì)應(yīng)了一種類(lèi)型的事件,供 Reactor 在相應(yīng)的事件發(fā)生時(shí)調(diào)用,執(zhí)行相應(yīng)的事件處理。通常它會(huì)綁定一個(gè)有效的句柄。 對(duì)應(yīng)到 libevent 中,就是 event 結(jié)構(gòu)體。

5)具體的事件處理器

是事件處理器接口的實(shí)現(xiàn)。它實(shí)現(xiàn)了應(yīng)用程序提供的某個(gè)服務(wù)。每個(gè)具體的事件處理器總和一個(gè)描述符相關(guān)。它使用描述符來(lái)識(shí)別事件、識(shí)別應(yīng)用程序提供的服務(wù)。

2.4 Reactor 事件處理流程

圖片

三、libevent庫(kù)的使用

3.1 使用步驟

1、調(diào)用event_init函數(shù)創(chuàng)建event_base對(duì)象。一個(gè)event_base相當(dāng)于一個(gè)reactor實(shí)例。

2、創(chuàng)建具體的事件處理器,并設(shè)置它們所從屬的reactor實(shí)例。evsignal_new和evtimer_new分別用于創(chuàng)建信號(hào)事件處理器和定時(shí)事件處理器,它們的統(tǒng)一入口是event_new函數(shù),event_new函數(shù)成功時(shí)返回一個(gè)event類(lèi)型的對(duì)象,也就是libevent的事件處理器

3、調(diào)用event_add函數(shù),將事件處理器添加到注冊(cè)事件隊(duì)列中,并將該事件處理器對(duì)應(yīng)的事件添加到事件多路分發(fā)器中。

4、調(diào)用event_base_dispatch函數(shù)來(lái)執(zhí)行事件循環(huán)。

5、事件循環(huán)結(jié)束后,使用*_free系列函數(shù)來(lái)釋放系統(tǒng)資源。

3.2 事件處理流程

當(dāng)應(yīng)用程序向 libevent 注冊(cè)一個(gè)事件后,libevent 內(nèi)部是怎么樣進(jìn)行處理的呢?

下面的圖就給出了這一基本流程。

圖片

1)首先應(yīng)用程序準(zhǔn)備并初始化 event,設(shè)置好事件類(lèi)型和回調(diào)函數(shù);這對(duì)應(yīng)于前面第步驟 2 和 3;

2)向 libevent 添加該事件 event。對(duì)于定時(shí)事件,libevent 使用一個(gè)小根堆管理,key 為超 時(shí)時(shí)間;對(duì)于 Signal 和 I/O 事件,libevent 將其放入到等待鏈表(wait list)中,這是一 個(gè)雙向鏈表結(jié)構(gòu);

3)程序調(diào)用 event_base_dispatch()系列函數(shù)進(jìn)入無(wú)限循環(huán),等待事件,以 select()函數(shù)為例; 每次循環(huán)前 libevent 會(huì)檢查定時(shí)事件的最小超時(shí)時(shí)間 tv,根據(jù) tv 設(shè)置 select()的最大等待時(shí)間,以便于后面及時(shí)處理超時(shí)事件; 當(dāng) select()返回后,首先檢查超時(shí)事件,然后檢查 I/O 事件;

四、libevent 源代碼文件組織

4.1 源代碼組織結(jié)構(gòu)

1)頭文件

主要就是 event.h:事件宏定義、接口函數(shù)聲明,主要結(jié)構(gòu)體 event 的聲明;

2)內(nèi)部頭文件

xxx-internal.h:內(nèi)部數(shù)據(jù)結(jié)構(gòu)和函數(shù),對(duì)外不可見(jiàn),以達(dá)到信息隱藏的目的;

3) libevent 框架

event.c: event 整體框架的代碼實(shí)現(xiàn);

4)對(duì)系統(tǒng) I/O 多路復(fù)用機(jī)制的封裝

epoll.c:對(duì) epoll 的封裝;

select.c:對(duì) select 的封裝;

devpoll.c:對(duì) dev/poll 的封裝;

kqueue.c:對(duì) kqueue 的封裝;

5)定時(shí)事件管理

min-heap.h:其實(shí)就是一個(gè)以時(shí)間作為 key 的小根堆結(jié)構(gòu);

6)信號(hào)管理

signal.c:對(duì)信號(hào)事件的處理;

7)輔助功能函數(shù)

evutil.h 和 evutil.c:一些輔助功能函數(shù),包括創(chuàng)建 socket pair 和一些時(shí)間操作函數(shù):加、減和比較等。

8)日志

log.h 和 log.c: log 日志函數(shù)

9)緩沖區(qū)管理

evbuffer.c 和 buffer.c: libevent 對(duì)緩沖區(qū)的封裝;

10)基本數(shù)據(jù)結(jié)構(gòu)

compatsys 下的兩個(gè)源文件: queue.h 是 libevent 基本數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn),包括鏈表,雙向鏈表,隊(duì)列等; _libevent_time.h:一些用于時(shí)間操作的結(jié)構(gòu)體定義、函數(shù)和宏定義;

11)實(shí)用網(wǎng)絡(luò)庫(kù)

http 和 evdns:是基于 libevent 實(shí)現(xiàn)的 http 服務(wù)器和異步 dns 查詢(xún)庫(kù)

五、libevent 的核心

5.1 ibevent 的核心---event

Libevent 是基于事件驅(qū)動(dòng)(event-driven)的,從名字也可以看到 event 是整個(gè)庫(kù)的核心。 event 就是 Reactor 框架中的事件處理程序組件;它提供了函數(shù)接口,供 Reactor 在事件發(fā)生時(shí)調(diào)用,以執(zhí)行相應(yīng)的事件處理,通常它會(huì)綁定一個(gè)有效的句柄。 首先給出 event 結(jié)構(gòu)體的聲明,它位于libevent-masterincludeevent2event_struct.h文件中:

struct event {
/**
* ev_callback,event的回調(diào)函數(shù),被ev_base調(diào)用,執(zhí)行事件處理程序,這是一個(gè)函數(shù)指針,原型為:
* void (*ev_callback)(int fd, short events, void *arg)
* 其中參數(shù)fd對(duì)應(yīng)于ev_fd;events對(duì)應(yīng)于ev_events;
*
* 具體定義在上面
* 以下為一些常用的宏定義
* #define ev_pri ev_evcallback.evcb_pri
* #define ev_flags ev_evcallback.evcb_flags
* #define ev_closure ev_evcallback.evcb_closure
* #define ev_callback ev_evcallback.evcb_cb_union.evcb_callback
* #define ev_arg ev_evcallback.evcb_arg
*/
struct event_callback ev_evcallback; //event的回調(diào)函數(shù),被ev_base調(diào)用

/* for managing timeouts */
/**
* min_heap_idx和ev_timeout,如果是timeout事件,它們是event在小根堆中的索引和超時(shí)值,
* libevent使用小根堆來(lái)管理定時(shí)事件
* 用來(lái)管理超時(shí)事件
*/
union {
// 公用超時(shí)隊(duì)列
TAILQ_ENTRY(event) ev_next_with_common_timeout;
// min_heap最小堆索引
int min_heap_idx;
} ev_timeout_pos;
evutil_socket_t ev_fd; //對(duì)于I/O事件,是綁定的文件描述符;對(duì)于signal事件,是綁定的信號(hào);

short ev_events; //event關(guān)注的事件類(lèi)型,它可以是以下3種類(lèi)型:I/O事件、定時(shí)事件、信號(hào)、輔助選項(xiàng)(EV_PERSIST)
short ev_res; /* result passed to event callback 記錄了當(dāng)前激活事件的類(lèi)型*/

struct event_base *ev_base; //該事件所屬的反應(yīng)堆實(shí)例,libevent句柄,每個(gè)事件都會(huì)保存一份句柄

/**
* 用共用體來(lái)同時(shí)表現(xiàn)IO事件和信號(hào)
* 以下為一些方便調(diào)用的宏定義
* mutually exclusive
* #define ev_signal_next ev_.ev_signal.ev_signal_next
* #define ev_io_next ev_.ev_io.ev_io_next
* #define ev_io_timeout ev_.ev_io.ev_timeout
*
* used only by signals
* #define ev_ncalls ev_.ev_signal.ev_ncalls
* #define ev_pncalls ev_.ev_signal.ev_pncalls
*/



union {
/* used for io events */
struct {
// 下一個(gè)io事件
LIST_ENTRY (event) ev_io_next; //使用雙向鏈表保存所有注冊(cè)的I/O和Signal事件
// 事件超時(shí)時(shí)間(既可以是相對(duì)時(shí)間,也可以是絕對(duì)時(shí)間)
struct timeval ev_timeout;
} ev_io;

/* used by signal events */
struct {
// 下一個(gè)信號(hào)
LIST_ENTRY (event) ev_signal_next;
short ev_ncalls; //事件就緒執(zhí)行時(shí),調(diào)用ev_callback的次數(shù),通常為1;
/* Allows deletes in callback */
short *ev_pncalls; //指針,通常指向ev_ncalls或者為NULL
} ev_signal;
} ev_;

// 保存事件的超時(shí)時(shí)間
struct timeval ev_timeout;
};

下面詳細(xì)解釋一下結(jié)構(gòu)體中各字段的含義,注意部分變量有可能位于event_callback結(jié)構(gòu)體中。

1)ev_events:event關(guān)注的事件類(lèi)型,它可以是以下3種類(lèi)型:

  • I/O事件:EV_WRITE和EV_READ
  • 定時(shí)事件:EV_TIMEOUT
  • 信號(hào)事件: EV_SIGNAL
  • 輔助選項(xiàng):EV_PERSIST,表明是一個(gè)永久事件

Libevent中的定義為:

/** Indicates that a timeout has occurred. It's not necessary to pass
* this flag to event_for new()/event_assign() to get a timeout. */
// 定時(shí)事件
#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 */
// 寫(xiě)事件
#define EV_WRITE 0x04
/** Wait for a POSIX signal to be raised*/
// 信號(hào)事件
#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.
*/
// 永久事件,激活執(zhí)行后會(huì)重新加到隊(duì)列中等待下一次激活,否則激活執(zhí)行后會(huì)自動(dòng)移除
#define EV_PERSIST 0x10
/** Select edge-triggered behavior, if supported by the backend. */
// 邊沿觸發(fā),一般需要后臺(tái)方法支持
#define EV_ET 0x20
/**
* If this option is provided, then event_del() will not block in one thread
* while waiting for the event callback to complete in another thread.
*
* To use this option safely, you may need to use event_finalize() or
* event_free_finalize() in order to safely tear down an event in a
* multithreaded application. See those functions for more information.
*
* THIS IS AN EXPERIMENTAL API. IT MIGHT CHANGE BEFORE THE LIBEVENT 2.1 SERIES
* BECOMES STABLE.
**/
// 終止事件,如果設(shè)置這個(gè)選項(xiàng),則event_del不會(huì)阻塞,需要使用event_finalize或者
#define EV_FINALIZE 0x40
/**
* Detects connection close events. You can use this to detect when a
* connection has been closed, without having to read all the pending data
* from a connection.
*
* Not all backends support EV_CLOSED. To detect or require it, use the
* feature flag EV_FEATURE_EARLY_CLOSE.
**/
// 檢查事件連接是否關(guān)閉;可以使用這個(gè)選項(xiàng)來(lái)檢測(cè)鏈接是否關(guān)閉,而不需要讀取此鏈接所有未決數(shù)據(jù);
#define EV_CLOSED 0x80

可以看出事件類(lèi)型可以使用“|”運(yùn)算符進(jìn)行組合,需要說(shuō)明的是,信號(hào)和I/O事件不能同時(shí)設(shè)置; 還可以看出libevent使用event結(jié)構(gòu)體將這3種事件的處理統(tǒng)一起來(lái);

2)ev_next,ev_active_next 和 ev_signal_next 都是雙向鏈表節(jié)點(diǎn)指針;它們是 libevent 對(duì)不同事件類(lèi)型和在不同的時(shí)期,對(duì)事件的管理時(shí)使用到的字段。 libevent 使用雙向鏈表保存所有注冊(cè)的 I/O 和 Signal 事件,ev_next 就是該 I/O 事件在鏈表中的位置;稱(chēng)此鏈表為“已注冊(cè)事件鏈表”; 同樣 ev_signal_next 就是 signal 事件在 signal 事件鏈表中的位置; ev_active_next:libevent 將所有的激活事件放入到鏈表 active list 中,然后遍歷 active list 執(zhí)行調(diào)度,ev_active_next 就指明了 event 在 active list 中的位置;

3)min_heap_idx 和 ev_timeout,如果是 timeout 事件,它們是 event 在小根堆中的索引和超時(shí)值,libevent 使用小根堆來(lái)管理定時(shí)事件。

4)ev_base 該事件所屬的反應(yīng)堆實(shí)例,這是一個(gè) event_base 結(jié)構(gòu)體。

5)ev_fd,對(duì)于 I/O 事件,是綁定的文件描述符;對(duì)于 signal 事件,是綁定的信號(hào);

6)ev_callback,是一個(gè)結(jié)構(gòu)體,里面包含有事件的回調(diào)函數(shù)(54-57行),這個(gè)回調(diào)函數(shù)被 ev_base 調(diào)用,執(zhí)行事件處理程序,原型為:

struct event_callback {
//下一個(gè)回調(diào)事件
TAILQ_ENTRY(event_callback) evcb_active_next;
/**
*
* 回調(diào)事件的狀態(tài)標(biāo)識(shí),具體為:
* #define EVLIST_TIMEOUT 0x01 // event在time堆中,min_heap
* #define EVLIST_INSERTED 0x02 // event在已注冊(cè)事件鏈表中,event_base的queue中
* #define EVLIST_SIGNAL 0x04 // 未見(jiàn)使用
* #define EVLIST_ACTIVE 0x08 // event在激活鏈表中,event_base的active_queue中
* #define EVLIST_INTERNAL 0x10 // 內(nèi)部使用標(biāo)記
* #define EVLIST_ACTIVE_LATER 0x20 event在下一次激活鏈表中
* #define EVLIST_INIT 0x80 // event已被初始化
* #define EVLIST_ALL 0xff // 主要用于判斷事件狀態(tài)的合法性
*/
short evcb_flags;
// 回調(diào)函數(shù)的優(yōu)先級(jí),越小優(yōu)先級(jí)越高
ev_uint8_t evcb_pri; /* smaller numbers are higher priority */
// 執(zhí)行不同的回調(diào)函數(shù)
// /** @name Event closure codes

// Possible values for evcb_closure in struct event_callback

// @{
// */
// /** A regular event. Uses the evcb_callback callback 事件關(guān)閉時(shí)的回調(diào)函數(shù)模式類(lèi)型 */
// // 常規(guī)事件,使用evcb_callback回調(diào)
// #define EV_CLOSURE_EVENT 0
// /** A signal event. Uses the evcb_callback callback */
// // 信號(hào)事件;使用evcb_callback回調(diào)
// #define EV_CLOSURE_EVENT_SIGNAL 1
// /** A persistent non-signal event. Uses the evcb_callback callback */
// // 永久性非信號(hào)事件;使用evcb_callback回調(diào)
// #define EV_CLOSURE_EVENT_PERSIST 2
// /** A simple callback. Uses the evcb_selfcb callback. */
// // 簡(jiǎn)單回調(diào),使用evcb_selfcb回調(diào)
// #define EV_CLOSURE_CB_SELF 3
// /** A finalizing callback. Uses the evcb_cbfinalize callback. */
// // 結(jié)束的回調(diào),使用evcb_cbfinalize回調(diào)
// #define EV_CLOSURE_CB_FINALIZE 4
// /** A finalizing event. Uses the evcb_evfinalize callback. */
// // 結(jié)束事件回調(diào),使用evcb_evfinalize回調(diào)
// #define EV_CLOSURE_EVENT_FINALIZE 5
// /** A finalizing event that should get freed after. Uses the evcb_evfinalize
// * callback. */
// // 結(jié)束事件之后應(yīng)該釋放,使用evcb_evfinalize回調(diào)
// #define EV_CLOSURE_EVENT_FINALIZE_FREE 6
// /** @} */

ev_uint8_t evcb_closure;
/* allows us to adopt for different types of events */
// 允許我們自動(dòng)適配不同類(lèi)型的回調(diào)事件
union {
void (*evcb_callback)(evutil_socket_t, short, void *);
void (*evcb_selfcb)(struct event_callback *, void *);
void (*evcb_evfinalize)(struct event *, void *);
void (*evcb_cbfinalize)(struct event_callback *, void *);
} evcb_cb_union;
// 回調(diào)參數(shù)
void *evcb_arg;
};

7)ev_arg:void*,表明可以是任意類(lèi)型的數(shù)據(jù),在設(shè)置 event 時(shí)指定;

8)eb_flags:libevent 用于標(biāo)記 event 信息的字段,表明其當(dāng)前的狀態(tài),可能的值有:

//事件狀態(tài)標(biāo)志

// 事件在time min_heap堆中
#define EVLIST_TIMEOUT 0x01
// 事件在已注冊(cè)事件鏈表中
#define EVLIST_INSERTED 0x02
// 目前未使用
#define EVLIST_SIGNAL 0x04
// 事件在激活鏈表中
#define EVLIST_ACTIVE 0x08
// 內(nèi)部使用標(biāo)記
#define EVLIST_INTERNAL 0x10
// 事件在下一次激活鏈表中
#define EVLIST_ACTIVE_LATER 0x20
// 事件已經(jīng)終止
#define EVLIST_FINALIZING 0x40
// 事件初始化完成,但是哪兒都不在
#define EVLIST_INIT 0x80
// 包含所有事件狀態(tài),用于判斷合法性的
#define EVLIST_ALL 0xff

9)ev_ncalls:事件就緒執(zhí)行時(shí),調(diào)用 ev_callback 的次數(shù),通常為 1;

10)ev_pncalls:指針,通常指向 ev_ncalls 或者為 NULL;

11)ev_res:記錄了當(dāng)前激活事件的類(lèi)型

5.2 libevent 對(duì) event 的管理

從event 結(jié)構(gòu)體中的 3 個(gè)鏈表節(jié)點(diǎn)指針和一個(gè)堆索引出發(fā),大體上也能窺出 libevent 對(duì) event 的管理方法了,可以參見(jiàn)下面的示意圖。 每次當(dāng)有事件 event 轉(zhuǎn)變?yōu)榫途w狀態(tài)時(shí),libevent 就會(huì)把它移入到 active event list[priority] 中,其中 priority 是 event 的優(yōu)先級(jí); 接著 libevent 會(huì)根據(jù)自己的調(diào)度策略選擇就緒事件,調(diào)用其 cb_callback()函數(shù)執(zhí)行事件處理;并根據(jù)就緒的句柄和事件類(lèi)型填充 cb_callback 函數(shù)的參數(shù)。

5.3 事件設(shè)置的接口函數(shù)

要向 libevent 添加一個(gè)事件,需要首先設(shè)置 event 對(duì)象,這通過(guò)調(diào)用 libevent 提供的函數(shù)有:event_set(), event_base_set(), event_priority_set()來(lái)完成;下面分別進(jìn)行講解。

void event_set(struct event *ev, int fd, short events, void (*callback)(int, short, void *), void *arg)

1.設(shè)置事件 ev 綁定的文件描述符或者信號(hào),對(duì)于定時(shí)事件,設(shè)為-1 即可;

2.設(shè)置事件類(lèi)型,比如 EV_READ|EV_PERSIST, EV_WRITE, EV_SIGNAL 等;

3.設(shè)置事件的回調(diào)函數(shù)以及參數(shù) arg;

4.初始化其它字段,比如缺省的 event_base 和優(yōu)先級(jí);

int event_base_set(struct event_base *base, struct event *ev)

設(shè)置 event ev 將要注冊(cè)到的 event_base;

libevent 有一個(gè)全局 event_base 指針 current_base,默認(rèn)情況下事件 ev 將被注冊(cè)到 current_base 上,使用該函數(shù)可以指定不同的 event_base;

如果一個(gè)進(jìn)程中存在多個(gè) libevent 實(shí)例,則必須要調(diào)用該函數(shù)為 event 設(shè)置不同的 event_base;

int event_priority_set(struct event *ev, int pri)

設(shè)置event ev的優(yōu)先級(jí),沒(méi)什么可說(shuō)的,注意的一點(diǎn)就是:當(dāng)ev正處于就緒狀態(tài)時(shí),不能設(shè)置,返回-1。

六、初見(jiàn)事件處理框架

前面已經(jīng)對(duì) libevent 的事件處理框架和 event 結(jié)構(gòu)體做了描述,現(xiàn)在是時(shí)候剖析 libevent 對(duì)事件的詳細(xì)處理流程了,本節(jié)將分析 libevent 的事件處理框架 event_base 和 libevent 注冊(cè)、 刪除事件的具體流程,可結(jié)合前一節(jié) libevent 對(duì) event 的管理。

6.1 事件處理框架-event_base

回想 Reactor 模式的幾個(gè)基本組件,本節(jié)講解的部分對(duì)應(yīng)于 Reactor 框架組件。在 libevent 中,這就表現(xiàn)為 event_base 結(jié)構(gòu)體,結(jié)構(gòu)體聲明如下,它位于libevent-masterevent-internal.h 文件中:

struct event_base {
/** Function pointers and other data to describe this event_base's
* backend. */
/**
* 實(shí)際使用后臺(tái)方法的句柄,實(shí)際上指向的是靜態(tài)全局?jǐn)?shù)組變量,從靜態(tài)全局變量eventops中選擇
*/
const struct eventop *evsel;
/** Pointer to backend-specific data. */
/**
* 指向后臺(tái)特定的數(shù)據(jù),是由evsel->init返回的句柄
* 實(shí)際上是對(duì)實(shí)際后臺(tái)方法所需數(shù)據(jù)的封裝,void出于兼容性考慮
*/
void *evbase;

/** List of changes to tell backend about at next dispatch. Only used
* by the O(1) backends. */
// 告訴后臺(tái)方法下一次調(diào)度的變化列表
struct event_changelist changelist;

/** Function pointers used to describe the backend that this event_base
* uses for signals */
// 用于描述當(dāng)前event_base用于信號(hào)的后臺(tái)方法
const struct eventop *evsigsel;
/** Data to implement the common signal handler code. */
// 用于實(shí)現(xiàn)公用信號(hào)句柄的代碼
struct evsig_info sig;

/** Number of virtual events */
// 虛擬事件的數(shù)量
int virtual_event_count;
/** Maximum number of virtual events active */
// 虛擬事件的最大數(shù)量
int virtual_event_count_max;
/** Number of total events added to this event_base */
// 添加到event_base上事件總數(shù)
int event_count;
/** Maximum number of total events added to this event_base */
// 添加到event_base上的最大個(gè)數(shù)
int event_count_max;
/** Number of total events active in this event_base */
// 當(dāng)前event_base中活躍事件的個(gè)數(shù)
int event_count_active;
/** Maximum number of total events active in this event_base */
// 當(dāng)前event_base中活躍事件的最大個(gè)數(shù)
int event_count_active_max;

/** Set if we should terminate the loop once we're done processing
* events. */
// 一旦我們完成處理事件了,如果我們應(yīng)該終止loop,可以設(shè)置這個(gè)
int event_gotterm;
/** Set if we should terminate the loop immediately */
// 如果需要中止loop,可以設(shè)置這個(gè)變量
int event_break;
/** Set if we should start a new instance of the loop immediately. */
// 如果啟動(dòng)新實(shí)例的loop,可以設(shè)置這個(gè)
int event_continue;

/** The currently running priority of events */
// 當(dāng)前運(yùn)行事件的優(yōu)先級(jí)
int event_running_priority;

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

/** Set to the number of deferred_cbs we've made 'active' in the
* loop. This is a hack to prevent starvation; it would be smarter
* to just use event_config_set_max_dispatch_interval's max_callbacks
* feature */
/**
* 設(shè)置已經(jīng)在loop中設(shè)置為’active’的deferred_cbs的個(gè)數(shù),這是為了避免
* 饑餓的hack方法;只需要使用event_config_set_max_dispatch_interval’s的
* max_callbacks特征就可以變的更智能
*/
int n_deferreds_queued;

/* Active event management. // 活躍事件管理*/
/** An array of nactivequeues queues for active event_callbacks (ones
* that have triggered, and whose callbacks need to be called). Low
* priority numbers are more important, and stall higher ones.
* 存儲(chǔ)激活事件的event_callbacks的隊(duì)列,這些event_callbacks都需要調(diào)用;
* 數(shù)字越小優(yōu)先級(jí)越高
*/
struct evcallback_list *activequeues;
/** The length of the activequeues array 活躍隊(duì)列的長(zhǎng)度*/
int nactivequeues;
/** A list of event_callbacks that should become active the next time
* we process events, but not this time. */
// 下一次會(huì)變成激活狀態(tài)的回調(diào)函數(shù)的列表,但是當(dāng)前這次不會(huì)調(diào)用
struct evcallback_list active_later_queue;

/* common timeout logic // 公用超時(shí)邏輯*/

/** An array of common_timeout_list* for all of the common timeout
* values we know.
* 公用超時(shí)事件列表,這是二級(jí)指針,每個(gè)元素都是具有同樣超時(shí)
* 時(shí)間事件的列表,
*/
struct common_timeout_list **common_timeout_queues;
/** The number of entries used in common_timeout_queues */
// 公用超時(shí)隊(duì)列中的項(xiàng)目個(gè)數(shù)
int n_common_timeouts;
/** The total size of common_timeout_queues. */
// 公用超時(shí)隊(duì)列的總個(gè)數(shù)
int n_common_timeouts_allocated;

/** Mapping from file descriptors to enabled (added) events */
// 文件描述符和事件之間的映射表
struct event_io_map io;

/** Mapping from signal numbers to enabled (added) events. */
// 信號(hào)數(shù)字和事件之間映射表
struct event_signal_map sigmap;

/** Priority queue of events with timeouts. */
// 事件超時(shí)的優(yōu)先級(jí)隊(duì)列,使用最小堆實(shí)現(xiàn)
struct min_heap timeheap;

/** Stored timeval: used to avoid calling gettimeofday/clock_gettime
* too often. */
// 存儲(chǔ)時(shí)間:用來(lái)避免頻繁調(diào)用gettimeofday/clock_gettime
struct timeval tv_cache;

// monotonic格式的時(shí)間
struct evutil_monotonic_timer monotonic_timer;

/** Difference between internal time (maybe from clock_gettime) and
* gettimeofday. */
// 內(nèi)部時(shí)間(可以從clock_gettime獲?。┖蚲ettimeofday之間的差異
struct timeval tv_clock_diff;
/** Second in which we last updated tv_clock_diff, in monotonic time. */
// 更新內(nèi)部時(shí)間的間隔秒數(shù)
time_t last_updated_clock_diff;

#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;
/** 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
/** The event whose callback is executing right now */
// 當(dāng)前執(zhí)行的回調(diào)函數(shù)
struct event_callback *current_event;

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

/** Flags that this base was configured with */

// event_base配置的特征值
// 多線程調(diào)用是不安全的,單線程非阻塞模式
// EVENT_BASE_FLAG_NOLOCK = 0x01,
// 忽略檢查EVENT_*等環(huán)境變量
// EVENT_BASE_FLAG_IGNORE_ENV = 0x02,
// 只用于windows
// EVENT_BASE_FLAG_STARTUP_IOCP = 0x04,
// 不使用緩存的時(shí)間,每次回調(diào)都會(huì)獲取系統(tǒng)時(shí)間
// EVENT_BASE_FLAG_NO_CACHE_TIME = 0x08,
// 如果使用epoll方法,則使用epoll內(nèi)部的changelist
// EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST = 0x10,
// 使用更精確的時(shí)間,但是可能性能會(huì)降低
// EVENT_BASE_FLAG_PRECISE_TIMER = 0x20

enum event_base_config_flag flags;

// 最大調(diào)度時(shí)間間隔
struct timeval max_dispatch_time;
// 最大調(diào)度的回調(diào)函數(shù)個(gè)數(shù)
int max_dispatch_callbacks;
// 優(yōu)先級(jí)設(shè)置之后,對(duì)于活躍隊(duì)列中子隊(duì)列個(gè)數(shù)的限制
// 但是當(dāng)子隊(duì)列個(gè)數(shù)超過(guò)這個(gè)限制之后,會(huì)以實(shí)際的回調(diào)函數(shù)個(gè)數(shù)為準(zhǔn)
int limit_callbacks_after_prio;

/* 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. */
//如果為1表示當(dāng)前可以喚醒主線程,否則不能喚醒主線程
int is_notify_pending;
/** A socketpair used by some th_notify functions to wake up the main
* thread. */
// 一端讀、一端寫(xiě),用來(lái)觸發(fā)喚醒事件
evutil_socket_t th_notify_fd[2];
/** An event used by some th_notify functions to wake up the main
* thread. */
// 喚醒event_base的event,被添加到監(jiān)聽(tīng)集合中的對(duì)象
struct event th_notify;
/** A function used to wake up the main thread from another thread. */
//執(zhí)行喚醒操作的函數(shù)(不是喚醒event的回調(diào)函數(shù))
int (*th_notify_fn)(struct event_base *base);

/** Saved seed for weak random number generator. Some backends use
* this to produce fairness among sockets. Protected by th_base_lock. */
// 保存弱隨機(jī)數(shù)產(chǎn)生器的種子。某些后臺(tái)方法會(huì)使用這個(gè)種子來(lái)公平的選擇sockets。
struct evutil_weakrand_state weakrand_seed;

/** List of event_onces that have not yet fired. */
LIST_HEAD(once_event_list, event_once) once_events;

};

下面詳細(xì)解釋一下結(jié)構(gòu)體中部分字段的含義。

1)evsel 和 evbase 這兩個(gè)字段的設(shè)置可能會(huì)讓人有些迷惑,這里你可以把 evsel 和 evbase 看作是類(lèi)和靜態(tài)函數(shù)的關(guān)系,比如添加事件時(shí)的調(diào)用行為:evsel->add(evbase, ev),實(shí)際執(zhí) 行操作的是 evbase;這相當(dāng)于 class::add(instance, ev),instance 就是 class 的一個(gè)對(duì)象實(shí)例。 evsel指向了全局變量static const struct eventop *eventops[]中的一個(gè);libevent將系統(tǒng)提供的I/O demultiplex機(jī)制統(tǒng)一封裝成了eventop結(jié)構(gòu);因此 eventops[]包含了select、poll、kequeue和epoll等等其中的若干個(gè)全局實(shí)例對(duì)象。 evbase實(shí)際上是一個(gè)eventop實(shí)例對(duì)象; 先來(lái)看看eventop結(jié)構(gòu)體,它的成員是一系列的函數(shù)指針, 在event-internal.h文件中:

/** Structure to define the backend of a given event_base. */
struct eventop {
/** The name of this backend. 后臺(tái)方法名字,即epoll,select,poll等*/
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. */
/**
* 配置libevent句柄event_base使用當(dāng)前后臺(tái)方法;他應(yīng)該創(chuàng)建新的數(shù)據(jù)結(jié)構(gòu),
* 隱藏了后臺(tái)方法運(yùn)行所需的信息,然后返回這些信息的結(jié)構(gòu)體,為了支持多種
* 結(jié)構(gòu)體,因此返回void*;返回的指針將保存在event_base.evbase中;如果失敗,
* 將返回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.
*/
/**
* 使給定的文件描述符或者信號(hào)變得可讀或者可寫(xiě)?!痚vents’將是我們嘗試添加的
* 事件類(lèi)型:一個(gè)或者更多的EV_READ,EV_WRITE,EV_SIGNAL,EV_ET?!痮ld’是這些事件
* 先前的事件類(lèi)型;’fdinfo’將是fd在evmap中的輔助結(jié)構(gòu)體信息,它的大小由下面的
* fdinfo_len給出。fd第一次添加時(shí)將設(shè)置為0.成功則返回0,失敗則返回-1
*/
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.
*/
/**
* event_loop實(shí)現(xiàn)的核心代碼。他必須察覺(jué)哪些添加的事件已經(jīng)準(zhǔn)備好,然后觸發(fā)每個(gè)
* 活躍事件都被調(diào)用(通常是通過(guò)event_io_active或者類(lèi)似這樣)。成功返回0,失敗則-1
*/
int (*dispatch)(struct event_base *, struct timeval *);
/** Function to clean up and free our data from the event_base. */
// 清除event_base并釋放數(shù)據(jù)
void (*dealloc)(struct event_base *);
/** Flag: set if we need to reinitialize the event base after we fork.
*/
// 在執(zhí)行fork之后是否需要重新初始化的標(biāo)識(shí)位
int need_reinit;
/** Bit-array of supported event_method_features that this backend can
* provide. */

// 后臺(tái)方法可以提供的特征
// enum event_method_feature {
// 邊沿觸發(fā)
// EV_FEATURE_ET = 0x01,
// 要求事后臺(tái)方法在調(diào)度很多事件時(shí)大約為O(1)操作,select和poll無(wú)法提供這種特征,
// 這兩種方法具有N個(gè)事件時(shí),可以提供O(N)操作
// EV_FEATURE_O1 = 0x02,

// 后臺(tái)方法可以處理各種文件描述符,而不僅僅是sockets
// EV_FEATURE_FDS = 0x04,
/** Require an event method that allows you to use EV_CLOSED to detect
* connection close without the necessity of reading all the pending data.
*
* Methods that do support EV_CLOSED may not be able to provide support on
* all kernel versions.
**/
// 要求后臺(tái)方法允許使用EV_CLOSED特征檢測(cè)鏈接是否中斷,而不需要讀取
// 所有未決數(shù)據(jù);但是不是所有內(nèi)核都能提供這種特征
// EV_FEATURE_EARLY_CLOSE = 0x08
// };

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.
*/
/**
* 應(yīng)該為每個(gè)文件描述符保留的額外信息長(zhǎng)度,額外信息可能包括一個(gè)或者多個(gè)
* 活躍事件。這個(gè)信息是存儲(chǔ)在每個(gè)文件描述符的evmap中,然后通過(guò)參數(shù)傳遞
* 到上面的add和del函數(shù)中。
*/
size_t fdinfo_len;
};

也就是說(shuō),在 libevent 中,每種 I/O demultiplex 機(jī)制的實(shí)現(xiàn)都必須提供這五個(gè)函數(shù)接口, 來(lái)完成自身的初始化、銷(xiāo)毀釋放;對(duì)事件的注冊(cè)、注銷(xiāo)和分發(fā)。 比如對(duì)于 epoll,libevent 實(shí)現(xiàn)了 5 個(gè)對(duì)應(yīng)的接口函數(shù),并在初始化時(shí)并將 eventop 的 5 個(gè)函數(shù)指針指向這 5 個(gè)函數(shù),那么程序就可以使用 epoll 作為 I/O demultiplex 機(jī)制了,這個(gè)在后面會(huì)再次提到。

2)activequeues 是一個(gè)一級(jí)指針,前面講過(guò) libevent 支持事件優(yōu)先級(jí),因此你可以把它看作是一維數(shù)組,其中的元素 activequeues[priority]是一個(gè)鏈表,鏈表的每個(gè)節(jié)點(diǎn)指向一個(gè)優(yōu)先級(jí)為 priority 的就緒事件 event,實(shí)際上鏈表當(dāng)中存放的是事件的回調(diào)函數(shù)。

3)io,管理IO事件的結(jié)構(gòu)體變量。

4)sigmap 是由來(lái)管理信號(hào)的結(jié)構(gòu)體,將在后面信號(hào)處理時(shí)專(zhuān)門(mén)講解;

5)timeheap 是管理定時(shí)事件的小根堆,將在后面定時(shí)事件處理時(shí)專(zhuān)門(mén)講解;

6)tv_cache 是 libevent 用于時(shí)間管理的變量,存儲(chǔ)時(shí)間:用來(lái)避免頻繁調(diào)用gettimeofday/clock_gettime;

6.2 創(chuàng)建和初始化 event_base

創(chuàng)建一個(gè) event_base 對(duì)象也既是創(chuàng)建了一個(gè)新的 libevent 實(shí)例,程序需要通過(guò)調(diào)用 event_init()(內(nèi)部調(diào)用 event_base_new 函數(shù)執(zhí)行具體操作)函數(shù)來(lái)創(chuàng)建,該函數(shù)同時(shí)還對(duì)新生成的 libevent 實(shí)例進(jìn)行了初始化。 該函數(shù)首先為 event_base 實(shí)例申請(qǐng)空間,然后初始化定時(shí)事件使用的mini-heap,選擇并初始化合適的系統(tǒng) I/O 的 demultiplexer 機(jī)制,初始化各事件鏈表; 函數(shù)還檢測(cè)了系統(tǒng)的時(shí)間設(shè)置,為后面的時(shí)間管理打下基礎(chǔ)。

6.3 接口函數(shù)

前面提到 Reactor 框架的作用就是提供事件的注冊(cè)、注銷(xiāo)接口;根據(jù)系統(tǒng)提供的事件多路分發(fā)機(jī)制執(zhí)行事件循環(huán),當(dāng)有事件進(jìn)入“就緒”狀態(tài)時(shí),調(diào)用注冊(cè)事件的回調(diào)函數(shù)來(lái)處理事件。 Libevent 中對(duì)應(yīng)的接口函數(shù)主要就是:

int event_add(struct event *ev, const struct timeval *timeout);

int event_del(struct event *ev);

int event_base_loop(struct event_base *base, int loops);

void event_active(struct event *event, int res, short events);

void event_process_active(struct event_base *base);

6.3.1 注冊(cè)事件

函數(shù)原型:

/**
* 事件注冊(cè)-event_add
* 1、將事件添加到等待事件中去,需要注意的是,event_add在event_new或者event_assign之后執(zhí)行,
* 即添加的事件必須是經(jīng)過(guò)基本初始化過(guò)后的事件;
* 2、此處添加的事件包括IO事件、信號(hào)事件、定時(shí)事件,根據(jù)事件申請(qǐng)時(shí)設(shè)置的事件類(lèi)型決定添加的流程;
* 3、超時(shí)控制包括兩種方式:
* (1)最小堆:時(shí)間超時(shí)時(shí)間存儲(chǔ)在最小堆,每次執(zhí)行超時(shí)任務(wù)都從最小堆堆頂取任務(wù)執(zhí)行
* (2)最小堆+公用超時(shí)隊(duì)列:相同超時(shí)的任務(wù)存儲(chǔ)在同一個(gè)超時(shí)隊(duì)列,每一個(gè)超時(shí)隊(duì)列的隊(duì)首事件存儲(chǔ)在最小堆,
* 每次執(zhí)行超時(shí)任務(wù)時(shí)都從最小堆堆頂取任務(wù)執(zhí)行,然后遍歷執(zhí)行該任務(wù)所在公用超時(shí)隊(duì)列中的所有超時(shí)任務(wù)。
*/
int event_add(struct event *ev, const struct timeval *tv)

參數(shù):ev:指向要注冊(cè)的事件;

tv:超時(shí)時(shí)間;

函數(shù)將 ev 注冊(cè)到 ev->ev_base 上,事件類(lèi)型由 ev->ev_events 指明,如果注冊(cè)成功,ev 將被插入到已注冊(cè)鏈表中;如果 tv 不是 NULL,則會(huì)同時(shí)注冊(cè)定時(shí)事件,將 ev 添加到 timer 堆上; 如果其中有一步操作失敗,那么函數(shù)保證沒(méi)有事件會(huì)被注冊(cè),可以講這相當(dāng)于一個(gè)原子操作。

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);//加鎖

// 實(shí)際時(shí)調(diào)用內(nèi)部實(shí)現(xiàn)函數(shù)event_add_nolock實(shí)現(xiàn)的,下文會(huì)分析
res = event_add_nolock_(ev, tv, 0);

EVBASE_RELEASE_LOCK(ev->ev_base, th_base_lock);

return (res);
}

event_add_nolock()函數(shù)

/* Implementation function to add an event. Works just like event_add,
* except: 1) it requires that we have the lock. 2) if tv_is_absolute is set,
* we treat tv as an absolute time, not as an interval to add to the current
* time
* 此函數(shù)真正實(shí)現(xiàn)將事件添加到event_base的等待列表中。
* 真正的將信號(hào)事件注冊(cè)在event_base上,也就是進(jìn)行了信號(hào)的內(nèi)部事件注冊(cè)
*
* 添加事件的實(shí)現(xiàn)函數(shù);就像event_add一樣,異常:
* 1)它需要使用者加鎖;2)如果tv_is_absolute設(shè)置了,則將tv作為絕對(duì)時(shí)間對(duì)待,而不是相對(duì)于當(dāng)前添加時(shí)間的時(shí)間間隔
*/
int
event_add_nolock_(struct event *ev, const struct timeval *tv,
int tv_is_absolute)
{
struct event_base *base = ev->ev_base;
int res = 0;
int notify = 0;

EVENT_BASE_ASSERT_LOCKED(base);
event_debug_assert_is_setup_(ev);

event_debug((
"event_add: event: %p (fd "EV_SOCK_FMT"), %s%s%s%scall %p",
ev,
EV_SOCK_ARG(ev->ev_fd),
ev->ev_events & EV_READ ? "EV_READ " : " ",
ev->ev_events & EV_WRITE ? "EV_WRITE " : " ",
ev->ev_events & EV_CLOSED ? "EV_CLOSED " : " ",
tv ? "EV_TIMEOUT " : " ",
ev->ev_callback));

// 事件狀態(tài)必須處于合法的某種事件狀態(tài),否則報(bào)錯(cuò)
EVUTIL_ASSERT(!(ev->ev_flags & ~EVLIST_ALL));

// 已經(jīng)處于結(jié)束狀態(tài)的事件再次添加會(huì)報(bào)錯(cuò)
if (ev->ev_flags & EVLIST_FINALIZING) {
/* XXXX debug */
return (-1);
}

/*
* prepare for timeout insertion further below, if we get a
* failure on any step, we should not change any state.
*/
/**
* 為超時(shí)插入做準(zhǔn)備,如果超時(shí)控制不為空,且事件沒(méi)有處于超時(shí)狀態(tài)
* 首先為將要插入的超時(shí)事件準(zhǔn)備插入節(jié)點(diǎn),主要是為了防止后面出現(xiàn)這種情況:
* 事件狀態(tài)改變已經(jīng)完成,但是最小堆申請(qǐng)節(jié)點(diǎn)卻失敗;
* 因此,如果在任何一步出現(xiàn)錯(cuò)誤,都不能改變事件狀態(tài),這是前提條件。
*/

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

/* If the main thread is currently executing a signal event's
* callback, and we are not the main thread, then we want to wait
* until the callback is done before we mess with the event, or else
* we can race on ev_ncalls and ev_pncalls below. */
/**
* 如果主線程當(dāng)前正在執(zhí)行信號(hào)事件的回調(diào)函數(shù),同時(shí)又不在主線程,則
* 需要等待回調(diào)函數(shù)執(zhí)行完畢才能繼續(xù)添加事件,否則可能會(huì)在
* ev_ncalls和ev_pncalls上產(chǎn)生競(jìng)爭(zhēng)。
*/
#ifndef EVENT__DISABLE_THREAD_SUPPORT
if (base->current_event == event_to_event_callback(ev) &&
(ev->ev_events & EV_SIGNAL)
&& !EVBASE_IN_THREAD(base)) {
++base->current_event_waiters;
EVTHREAD_COND_WAIT(base->current_event_cond, base->th_base_lock);
}
#endif

/**
* 如果事件類(lèi)型是IO事件/信號(hào)事件,同時(shí)事件狀態(tài)不是已經(jīng)插入/激活/下一次激活狀態(tài),
* 則根據(jù)事件類(lèi)型將事件添加到不同的映射表或者隊(duì)列中
*/
if ((ev->ev_events & (EV_READ|EV_WRITE|EV_CLOSED|EV_SIGNAL)) &&
!(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE|EVLIST_ACTIVE_LATER))) {
// 如果事件是IO事件,則將事件插入到IO事件與文件描述符的映射表中
if (ev->ev_events & (EV_READ|EV_WRITE|EV_CLOSED))
res = evmap_io_add_(base, ev->ev_fd, ev);
// 如果事件是信號(hào)事件,則將事件插入信號(hào)與文件描述符的映射表中
else if (ev->ev_events & EV_SIGNAL)
res = evmap_signal_add_(base, (int)ev->ev_fd, ev);
// 如果上述添加行為正確,則將事件插入到event_base的事件列表中
if (res != -1)
event_queue_insert_inserted(base, ev);//其實(shí)就是這是事件的已注冊(cè)標(biāo)志
/**
* 如果上述添加行為正確,則設(shè)置通知主線程的標(biāo)志,因?yàn)橐呀?jīng)添加了新事件,
* 防止1)優(yōu)先級(jí)高的事件被優(yōu)先級(jí)低的事件倒掛,2)防止主線程忙等,會(huì)通知主線程有新事件
*/
if (res == 1) {
/* evmap says we need to notify the main thread. */
notify = 1;
res = 0;
}
}

/*
* we should change the timeout state only if the previous event
* addition succeeded.
* 只有當(dāng)前面事件條件成功執(zhí)行之后,才能改變超時(shí)狀態(tài)
*/
if (res != -1 && tv != NULL) {
struct timeval now;
int common_timeout;
#ifdef USE_REINSERT_TIMEOUT
int was_common;
int old_timeout_idx;
#endif

/*
* for persistent timeout events, we remember the
* timeout value and re-add the event.
*
* If tv_is_absolute, this was already set.
*
* 對(duì)于持久化的定時(shí)事件,需要記住超時(shí)時(shí)間,并重新注冊(cè)事件
* 如果tv_is_absolute設(shè)置,則事件超時(shí)時(shí)間就等于輸入時(shí)間參數(shù)
*/
if (ev->ev_closure == EV_CLOSURE_EVENT_PERSIST && !tv_is_absolute)
ev->ev_io_timeout = *tv;

/**
* 如果沒(méi)有使用USE_REINSERT_TIMEOUT,則當(dāng)事件處于超時(shí)狀態(tài)時(shí),需要從隊(duì)列中移除事件
* 因?yàn)橥瑯拥氖录荒苤匦虏迦耄援?dāng)一個(gè)事件已經(jīng)處于超時(shí)狀態(tài)時(shí),為防止執(zhí)行,需要先移除后插入
*/
#ifndef USE_REINSERT_TIMEOUT
if (ev->ev_flags & EVLIST_TIMEOUT) {
event_queue_remove_timeout(base, ev);
}
#endif

/* Check if it is active due to a timeout. Rescheduling
* this timeout before the callback can be executed
* removes it from the active list.
* 檢查事件當(dāng)前狀態(tài)是否已經(jīng)激活,而且是超時(shí)事件的激活狀態(tài),
* 則在回調(diào)函數(shù)執(zhí)行之前,需要重新調(diào)度這個(gè)超時(shí)事件,因此需要把它移出激活隊(duì)列
* */
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;
}
}

// 將此事件的回調(diào)函數(shù)從激活隊(duì)列中移除
event_queue_remove_active(base, event_to_event_callback(ev));
}

// 獲取base中的緩存時(shí)間
gettime(base, &now);

// 檢查base是否使用了公用超時(shí)隊(duì)列機(jī)制
common_timeout = is_common_timeout(tv, base);
#ifdef USE_REINSERT_TIMEOUT
was_common = is_common_timeout(&ev->ev_timeout, base);
old_timeout_idx = COMMON_TIMEOUT_IDX(&ev->ev_timeout);
#endif

/**
* 1)如果設(shè)置絕對(duì)超時(shí)時(shí)間,則設(shè)置時(shí)間超時(shí)時(shí)間為輸入時(shí)間參數(shù)
* 2)如果使用的公用超時(shí)隊(duì)列機(jī)制,則根據(jù)當(dāng)前base中時(shí)間和輸入超時(shí)時(shí)間間隔計(jì)算出時(shí)間超時(shí)時(shí)間,
* 并對(duì)超時(shí)時(shí)間進(jìn)行公用超時(shí)掩碼計(jì)算
* 3)如果是其他情況,則直接根據(jù)base中時(shí)間和輸入超時(shí)時(shí)間間隔計(jì)算事件的超時(shí)時(shí)間
*/
if (tv_is_absolute) {
ev->ev_timeout = *tv;
} else if (common_timeout) {
struct timeval tmp = *tv;
tmp.tv_usec &= MICROSECONDS_MASK;
evutil_timeradd(&now, &tmp, &ev->ev_timeout);
ev->ev_timeout.tv_usec |=
(tv->tv_usec & ~MICROSECONDS_MASK);
} else {
evutil_timeradd(&now, tv, &ev->ev_timeout);
}

event_debug((
"event_add: event %p, timeout in %d seconds %d useconds, call %p",
ev, (int)tv->tv_sec, (int)tv->tv_usec, ev->ev_callback));

// 將事件插入超時(shí)隊(duì)列
#ifdef USE_REINSERT_TIMEOUT
// event_queue_reinsert_timeout會(huì)插入兩個(gè)隊(duì)列:一個(gè)是公用超時(shí)隊(duì)列,一個(gè)超時(shí)隊(duì)列
event_queue_reinsert_timeout(base, ev, was_common, common_timeout, old_timeout_idx);
#else
// 只會(huì)插入超時(shí)隊(duì)列
event_queue_insert_timeout(base, ev);
#endif

/**
* 如果使用了公用超時(shí)隊(duì)列機(jī)制,則需要根據(jù)當(dāng)前事件的超時(shí)時(shí)間將當(dāng)前事件插入具有相同超時(shí)時(shí)間的時(shí)間列表
*/
if (common_timeout) {
// 根據(jù)事件超時(shí)時(shí)間獲取應(yīng)該插入的公用超時(shí)隊(duì)列,注意此處是從隊(duì)尾插入
struct common_timeout_list *ctl =
get_common_timeout_list(base, &ev->ev_timeout);
/**
* 如果當(dāng)前事件是公用超時(shí)隊(duì)列的第一個(gè)事件,則因此需要將此超時(shí)事件插入最小堆
* 解釋?zhuān)汗贸瑫r(shí)隊(duì)列機(jī)制:處于同一個(gè)公用超時(shí)隊(duì)列中的所有事件具有相同的超時(shí)控制,因此只需要將公用超時(shí)隊(duì)列
* 的第一個(gè)事件插入最小堆,當(dāng)超時(shí)觸發(fā)時(shí),可以通過(guò)遍歷公用超時(shí)隊(duì)列獲取同樣的超時(shí)事件。
*/
if (ev == TAILQ_FIRST(&ctl->events)) {
common_timeout_schedule(ctl, &now, ev);
}
} else {
// 如果沒(méi)有使用公用超時(shí)隊(duì)列,則調(diào)整最小堆
struct event* top = NULL;
/* See if the earliest timeout is now earlier than it
* was before: if so, we will need to tell the main
* thread to wake up earlier than it would otherwise.
* We double check the timeout of the top element to
* handle time distortions due to system suspension.
* 查看當(dāng)前事件是否位于最小堆根部,如果是,則需要通知主線程
* 否則,需要查看最小堆根部超時(shí)時(shí)間是否已經(jīng)小于當(dāng)前時(shí)間,即已經(jīng)超時(shí)了,如果是,則需要通知主線程
*/
if (min_heap_elt_is_top_(ev))
notify = 1;
else if ((top = min_heap_top_(&base->timeheap)) != NULL &&
evutil_timercmp(&top->ev_timeout, &now, <))
notify = 1;
}
}

/**
* if we are not in the right thread, we need to wake up the loop
* 如果本線程不是執(zhí)行event_loop的主線程,就通知主線程
* */
if (res != -1 && notify && EVBASE_NEED_NOTIFY(base))
evthread_notify_base(base);

event_debug_note_add_(ev);

return (res);
}

6.3.2 刪除事件

函數(shù)原型為:

static int event_del_(struct event *ev, int blocking)

該函數(shù)將刪除事件 ev,對(duì)于 I/O 事件,從 I/O 的 demultiplexer 上將事件注銷(xiāo);對(duì)于 Signal 事件,將從 Signal 事件鏈表中刪除;對(duì)于定時(shí)事件,將從堆上刪除; 同樣刪除事件的操作則不一定是原子的,比如刪除時(shí)間事件之后,有可能從系統(tǒng) I/O 機(jī) 制中注銷(xiāo)會(huì)失敗。

static int
event_del_(struct event *ev, int blocking)
{
int res;
struct event_base *base = ev->ev_base;

if (EVUTIL_FAILURE_CHECK(!base)) {
event_warnx("%s: event has no event_base set.", __func__);
return -1;
}

EVBASE_ACQUIRE_LOCK(base, th_base_lock);
res = event_del_nolock_(ev, blocking);
EVBASE_RELEASE_LOCK(base, th_base_lock);

return (res);
}
聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 框架
    +關(guān)注

    關(guān)注

    0

    文章

    396

    瀏覽量

    17270
  • 函數(shù)
    +關(guān)注

    關(guān)注

    3

    文章

    4237

    瀏覽量

    61971
  • 線程
    +關(guān)注

    關(guān)注

    0

    文章

    501

    瀏覽量

    19580
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    RA CLASS B認(rèn)證函數(shù)庫(kù)使用簡(jiǎn)介

    RA CLASS B認(rèn)證函數(shù)庫(kù)使用簡(jiǎn)介
    的頭像 發(fā)表于 11-17 08:06 ?670次閱讀
    RA CLASS B認(rèn)證函數(shù)<b class='flag-5'>庫(kù)</b>使用<b class='flag-5'>簡(jiǎn)介</b>

    安裝libevent報(bào)錯(cuò)

    [dudu@localhost libevent-1.3]# makemakeall-recursivemake[1]: 進(jìn)入目錄“/tmp/libevent-1.3”Making all
    發(fā)表于 07-30 08:22

    BSP驅(qū)動(dòng)設(shè)計(jì)方法和HAL庫(kù)框架學(xué)習(xí)

    說(shuō)明:1、本教程重在BSP驅(qū)動(dòng)包設(shè)計(jì)方法和HAL庫(kù)框架學(xué)習(xí),并將HAL庫(kù)里面的各種彎彎繞捋順,從而方便我們的程序設(shè)計(jì)。2、由于是基于HAL庫(kù)的文檔,所以不限制H7系列,其它F1,F(xiàn)2,F(xiàn)3,F(xiàn)4
    發(fā)表于 08-04 08:15

    STM32應(yīng)用的三種框架應(yīng)用代碼

    文章目錄STM32應(yīng)用的三種框架應(yīng)用代碼+設(shè)備寄存器應(yīng)用代碼+標(biāo)準(zhǔn)庫(kù)+設(shè)備寄存器應(yīng)用代碼+OS+標(biāo)準(zhǔn)庫(kù)+設(shè)備寄存器STM32 固件庫(kù)結(jié)構(gòu)STM32 官方標(biāo)準(zhǔn)固件
    發(fā)表于 08-10 06:32

    STM32F407的HAL庫(kù)框架設(shè)計(jì)

    第12章 STM32F407的HAL庫(kù)框架設(shè)計(jì)學(xué)習(xí)通過(guò)本章節(jié),主要是想讓大家對(duì)HAL庫(kù)程序設(shè)計(jì)的基本套路有個(gè)了解,防止踩坑。目錄第12章 STM32F407的HAL庫(kù)
    發(fā)表于 08-10 06:23

    Go 相關(guān)的框架庫(kù)和軟件的精選清單 精選資料分享

    概述這是一個(gè)Go 相關(guān)的框架,庫(kù)和軟件的精選清單,引用自 awesome-go項(xiàng)目,并翻譯補(bǔ)充而來(lái)這是一個(gè)Go 相關(guān)的框架庫(kù)和軟件的精選清單,引用自 awesome-go項(xiàng)目,并翻譯
    發(fā)表于 08-12 07:53

    精選的 Go 框架,庫(kù)和軟件的精選清單 精選資料分享

    來(lái)自:https://learnku.com/articles/41230精選的 Go 框架庫(kù)和軟件的精選清單概述這是一個(gè) Go 相關(guān)的框架,庫(kù)和軟件的精選清單,引用自awesome
    發(fā)表于 08-12 06:32

    怎樣去移植在ARM嵌入式平臺(tái)的libevent

    libevent-2.1.8-stable.tar.gz 下載openssl-1.1.1a.tar.gz 下載openssl 交叉編譯在《arm-linux 交叉編譯wget支持openssl, 使
    發(fā)表于 12-14 07:15

    如何將交叉編譯libevent源碼移植到RK1126平臺(tái)上

    程序中需要起一個(gè)http服務(wù)來(lái)與主控進(jìn)行交互,正好之前海思平臺(tái)用的是libevent現(xiàn)在需要移植到RK平臺(tái) 。把libevent源碼下下來(lái)之后 隨便放一個(gè)目錄然后建一個(gè)build.sh內(nèi)容如下
    發(fā)表于 10-08 16:40

    一個(gè)最簡(jiǎn)單的事件驅(qū)動(dòng)的IO libevent編程例子

    本文演示一個(gè)最簡(jiǎn)單的基于libevent編程的例子。libevent是事件驅(qū)動(dòng)的IO,適用于“好萊塢原則”。
    的頭像 發(fā)表于 03-23 09:54 ?6287次閱讀
    一個(gè)最簡(jiǎn)單的事件驅(qū)動(dòng)的IO <b class='flag-5'>libevent</b>編程例子

    Atmel Studio 6簡(jiǎn)介 大概了解Atmel軟件框架

    Atmel Studio 6簡(jiǎn)介 大概了解Atmel軟件框架
    的頭像 發(fā)表于 07-04 10:49 ?3475次閱讀

    C++的框架庫(kù)和資源資料匯總大全

    關(guān)于 C++ 框架、庫(kù)和資源的一些匯總列表,由 fffaraz發(fā)起和維護(hù)。內(nèi)容包括:標(biāo)準(zhǔn)庫(kù)、Web應(yīng)用框架、人工智能、數(shù)據(jù)庫(kù)、圖片處理、機(jī)器
    發(fā)表于 05-07 18:22 ?6次下載
    C++的<b class='flag-5'>框架</b>、<b class='flag-5'>庫(kù)</b>和資源資料匯總大全

    ADM1266 Linux API和Python庫(kù)簡(jiǎn)介

    ADM1266 Linux API和Python庫(kù)簡(jiǎn)介
    發(fā)表于 05-17 10:50 ?6次下載
    ADM1266 Linux API和Python<b class='flag-5'>庫(kù)</b><b class='flag-5'>簡(jiǎn)介</b>

    深入解析UI框架簡(jiǎn)介以及業(yè)界發(fā)展趨勢(shì)

    作者:yuzhiqiang、sunfei、wanglei,華為軟件架構(gòu)工程師UI 框架簡(jiǎn)介以及業(yè)界發(fā)展趨勢(shì) UI,即用戶(hù)界面,主要包含視覺(jué)(比如圖像、文字、動(dòng)畫(huà)等可視化內(nèi)容)以及交互(比如按鈕點(diǎn)擊
    的頭像 發(fā)表于 08-04 14:25 ?5436次閱讀
    深入解析UI<b class='flag-5'>框架</b><b class='flag-5'>簡(jiǎn)介</b>以及業(yè)界發(fā)展趨勢(shì)

    Libevent網(wǎng)絡(luò)庫(kù)的原理與應(yīng)用

    1. Libevent介紹 Libevent 是一個(gè)用C語(yǔ)言編寫(xiě)的、輕量級(jí)的開(kāi)源高性能事件通知庫(kù),主要有以下幾個(gè)亮點(diǎn): 事件驅(qū)動(dòng)( event-driven),高性能; 輕量級(jí),專(zhuān)注于網(wǎng)絡(luò); 源代碼
    的頭像 發(fā)表于 11-09 10:24 ?402次閱讀