函數(shù)執(zhí)行,程序" />

一区二区三区三上|欧美在线视频五区|国产午夜无码在线观看视频|亚洲国产裸体网站|无码成年人影视|亚洲AV亚洲AV|成人开心激情五月|欧美性爱内射视频|超碰人人干人人上|一区二区无码三区亚洲人区久久精品

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

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

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

Libevent框架庫簡介

科技綠洲 ? 來源:Linux開發(fā)架構(gòu)之路 ? 作者:Linux開發(fā)架構(gòu)之路 ? 2023-11-09 16:43 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

一、Libevent簡介

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

1、跨平臺支持。Libevent支持Linux、UNIX和Windows。

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

3、線程安全。libevent使用libevent_pthreads庫來提供線程安全支持。

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

5、輕量級,專注于網(wǎng)絡(luò),沒有ACE那么臃腫龐大

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

二、Reactor 模式

2.1 Reactor簡介

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

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

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

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

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

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

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

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

2.3 Reactor 模式框架

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

圖片

1)事件源(handle)

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

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

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

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

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

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

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

5)具體的事件處理器

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

2.4 Reactor 事件處理流程

圖片

三、libevent庫的使用

3.1 使用步驟

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

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

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

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

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

3.2 事件處理流程

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

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

圖片

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

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

3)程序調(diào)用 event_base_dispatch()系列函數(shù)進(jìn)入無限循環(huán),等待事件,以 select()函數(shù)為例; 每次循環(huán)前 libevent 會檢查定時事件的最小超時時間 tv,根據(jù) tv 設(shè)置 select()的最大等待時間,以便于后面及時處理超時事件; 當(dāng) select()返回后,首先檢查超時事件,然后檢查 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ù),對外不可見,以達(dá)到信息隱藏的目的;

3) libevent 框架

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

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

epoll.c:對 epoll 的封裝;

select.c:對 select 的封裝;

devpoll.c:對 dev/poll 的封裝;

kqueue.c:對 kqueue 的封裝;

5)定時事件管理

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

6)信號管理

signal.c:對信號事件的處理;

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

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

8)日志

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

9)緩沖區(qū)管理

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

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

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

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

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

五、libevent 的核心

5.1 ibevent 的核心---event

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

struct event {
/**
* ev_callback,event的回調(diào)函數(shù),被ev_base調(diào)用,執(zhí)行事件處理程序,這是一個函數(shù)指針,原型為:
* void (*ev_callback)(int fd, short events, void *arg)
* 其中參數(shù)fd對應(yīng)于ev_fd;events對應(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在小根堆中的索引和超時值,
* libevent使用小根堆來管理定時事件
* 用來管理超時事件
*/
union {
// 公用超時隊(duì)列
TAILQ_ENTRY(event) ev_next_with_common_timeout;
// min_heap最小堆索引
int min_heap_idx;
} ev_timeout_pos;
evutil_socket_t ev_fd; //對于I/O事件,是綁定的文件描述符;對于signal事件,是綁定的信號;

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

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

/**
* 用共用體來同時表現(xiàn)IO事件和信號
* 以下為一些方便調(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 {
// 下一個io事件
LIST_ENTRY (event) ev_io_next; //使用雙向鏈表保存所有注冊的I/O和Signal事件
// 事件超時時間(既可以是相對時間,也可以是絕對時間)
struct timeval ev_timeout;
} ev_io;

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

// 保存事件的超時時間
struct timeval ev_timeout;
};

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

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

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

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. */
// 定時事件
#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.
*/
// 永久事件,激活執(zhí)行后會重新加到隊(duì)列中等待下一次激活,否則激活執(zhí)行后會自動移除
#define EV_PERSIST 0x10
/** Select edge-triggered behavior, if supported by the backend. */
// 邊沿觸發(fā),一般需要后臺方法支持
#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è)置這個選項(xiàng),則event_del不會阻塞,需要使用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)閉;可以使用這個選項(xiàng)來檢測鏈接是否關(guān)閉,而不需要讀取此鏈接所有未決數(shù)據(jù);
#define EV_CLOSED 0x80

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

2)ev_next,ev_active_next 和 ev_signal_next 都是雙向鏈表節(jié)點(diǎn)指針;它們是 libevent 對不同事件類型和在不同的時期,對事件的管理時使用到的字段。 libevent 使用雙向鏈表保存所有注冊的 I/O 和 Signal 事件,ev_next 就是該 I/O 事件在鏈表中的位置;稱此鏈表為“已注冊事件鏈表”; 同樣 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 在小根堆中的索引和超時值,libevent 使用小根堆來管理定時事件。

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

5)ev_fd,對于 I/O 事件,是綁定的文件描述符;對于 signal 事件,是綁定的信號;

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

struct event_callback {
//下一個回調(diào)事件
TAILQ_ENTRY(event_callback) evcb_active_next;
/**
*
* 回調(diào)事件的狀態(tài)標(biāo)識,具體為:
* #define EVLIST_TIMEOUT 0x01 // event在time堆中,min_heap
* #define EVLIST_INSERTED 0x02 // event在已注冊事件鏈表中,event_base的queue中
* #define EVLIST_SIGNAL 0x04 // 未見使用
* #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)先級,越小優(yōu)先級越高
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)閉時的回調(diào)函數(shù)模式類型 */
// // 常規(guī)事件,使用evcb_callback回調(diào)
// #define EV_CLOSURE_EVENT 0
// /** A signal event. Uses the evcb_callback callback */
// // 信號事件;使用evcb_callback回調(diào)
// #define EV_CLOSURE_EVENT_SIGNAL 1
// /** A persistent non-signal event. Uses the evcb_callback callback */
// // 永久性非信號事件;使用evcb_callback回調(diào)
// #define EV_CLOSURE_EVENT_PERSIST 2
// /** A simple callback. Uses the evcb_selfcb callback. */
// // 簡單回調(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 */
// 允許我們自動適配不同類型的回調(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*,表明可以是任意類型的數(shù)據(jù),在設(shè)置 event 時指定;

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

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

// 事件在time min_heap堆中
#define EVLIST_TIMEOUT 0x01
// 事件在已注冊事件鏈表中
#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í)行時,調(diào)用 ev_callback 的次數(shù),通常為 1;

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

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

5.2 libevent 對 event 的管理

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

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

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

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

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

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

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

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

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

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

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

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

int event_priority_set(struct event *ev, int pri)

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

六、初見事件處理框架

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

6.1 事件處理框架-event_base

回想 Reactor 模式的幾個基本組件,本節(jié)講解的部分對應(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í)際使用后臺方法的句柄,實(shí)際上指向的是靜態(tài)全局?jǐn)?shù)組變量,從靜態(tài)全局變量eventops中選擇
*/
const struct eventop *evsel;
/** Pointer to backend-specific data. */
/**
* 指向后臺特定的數(shù)據(jù),是由evsel->init返回的句柄
* 實(shí)際上是對實(shí)際后臺方法所需數(shù)據(jù)的封裝,void出于兼容性考慮
*/
void *evbase;

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

/** Function pointers used to describe the backend that this event_base
* uses for signals */
// 用于描述當(dāng)前event_base用于信號的后臺方法
const struct eventop *evsigsel;
/** Data to implement the common signal handler code. */
// 用于實(shí)現(xiàn)公用信號句柄的代碼
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上的最大個數(shù)
int event_count_max;
/** Number of total events active in this event_base */
// 當(dāng)前event_base中活躍事件的個數(shù)
int event_count_active;
/** Maximum number of total events active in this event_base */
// 當(dāng)前event_base中活躍事件的最大個數(shù)
int event_count_active_max;

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

/** The currently running priority of events */
// 當(dāng)前運(yùn)行事件的優(yōu)先級
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的個數(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.
* 存儲激活事件的event_callbacks的隊(duì)列,這些event_callbacks都需要調(diào)用;
* 數(shù)字越小優(yōu)先級越高
*/
struct evcallback_list *activequeues;
/** The length of the activequeues array 活躍隊(duì)列的長度*/
int nactivequeues;
/** A list of event_callbacks that should become active the next time
* we process events, but not this time. */
// 下一次會變成激活狀態(tài)的回調(diào)函數(shù)的列表,但是當(dāng)前這次不會調(diào)用
struct evcallback_list active_later_queue;

/* 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 */
// 公用超時隊(duì)列中的項(xiàng)目個數(shù)
int n_common_timeouts;
/** The total size of common_timeout_queues. */
// 公用超時隊(duì)列的總個數(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. */
// 信號數(shù)字和事件之間映射表
struct event_signal_map sigmap;

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

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

// monotonic格式的時間
struct evutil_monotonic_timer monotonic_timer;

/** Difference between internal time (maybe from clock_gettime) and
* gettimeofday. */
// 內(nèi)部時間(可以從clock_gettime獲?。┖蚲ettimeofday之間的差異
struct timeval tv_clock_diff;
/** Second in which we last updated tv_clock_diff, in monotonic time. */
// 更新內(nèi)部時間的間隔秒數(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,
// 不使用緩存的時間,每次回調(diào)都會獲取系統(tǒng)時間
// EVENT_BASE_FLAG_NO_CACHE_TIME = 0x08,
// 如果使用epoll方法,則使用epoll內(nèi)部的changelist
// EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST = 0x10,
// 使用更精確的時間,但是可能性能會降低
// EVENT_BASE_FLAG_PRECISE_TIMER = 0x20

enum event_base_config_flag flags;

// 最大調(diào)度時間間隔
struct timeval max_dispatch_time;
// 最大調(diào)度的回調(diào)函數(shù)個數(shù)
int max_dispatch_callbacks;
// 優(yōu)先級設(shè)置之后,對于活躍隊(duì)列中子隊(duì)列個數(shù)的限制
// 但是當(dāng)子隊(duì)列個數(shù)超過這個限制之后,會以實(shí)際的回調(diào)函數(shù)個數(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. */
// 一端讀、一端寫,用來觸發(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)聽集合中的對象
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)生器的種子。某些后臺方法會使用這個種子來公平的選擇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 這兩個字段的設(shè)置可能會讓人有些迷惑,這里你可以把 evsel 和 evbase 看作是類和靜態(tài)函數(shù)的關(guān)系,比如添加事件時的調(diào)用行為:evsel->add(evbase, ev),實(shí)際執(zhí) 行操作的是 evbase;這相當(dāng)于 class::add(instance, ev),instance 就是 class 的一個對象實(shí)例。 evsel指向了全局變量static const struct eventop *eventops[]中的一個;libevent將系統(tǒng)提供的I/O demultiplex機(jī)制統(tǒng)一封裝成了eventop結(jié)構(gòu);因此 eventops[]包含了select、poll、kequeue和epoll等等其中的若干個全局實(shí)例對象。 evbase實(shí)際上是一個eventop實(shí)例對象; 先來看看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. 后臺方法名字,即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)前后臺方法;他應(yīng)該創(chuàng)建新的數(shù)據(jù)結(jié)構(gòu),
* 隱藏了后臺方法運(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.
*/
/**
* 使給定的文件描述符或者信號變得可讀或者可寫?!痚vents’將是我們嘗試添加的
* 事件類型:一個或者更多的EV_READ,EV_WRITE,EV_SIGNAL,EV_ET?!痮ld’是這些事件
* 先前的事件類型;’fdinfo’將是fd在evmap中的輔助結(jié)構(gòu)體信息,它的大小由下面的
* fdinfo_len給出。fd第一次添加時將設(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)的核心代碼。他必須察覺哪些添加的事件已經(jīng)準(zhǔn)備好,然后觸發(fā)每個
* 活躍事件都被調(diào)用(通常是通過event_io_active或者類似這樣)。成功返回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)識位
int need_reinit;
/** Bit-array of supported event_method_features that this backend can
* provide. */

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

// 后臺方法可以處理各種文件描述符,而不僅僅是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.
**/
// 要求后臺方法允許使用EV_CLOSED特征檢測鏈接是否中斷,而不需要讀取
// 所有未決數(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)該為每個文件描述符保留的額外信息長度,額外信息可能包括一個或者多個
* 活躍事件。這個信息是存儲在每個文件描述符的evmap中,然后通過參數(shù)傳遞
* 到上面的add和del函數(shù)中。
*/
size_t fdinfo_len;
};

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

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

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

4)sigmap 是由來管理信號的結(jié)構(gòu)體,將在后面信號處理時專門講解;

5)timeheap 是管理定時事件的小根堆,將在后面定時事件處理時專門講解;

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

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

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

6.3 接口函數(shù)

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

函數(shù)原型:

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

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

tv:超時時間;

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

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í)際時調(diào)用內(nèi)部實(shí)現(xiàn)函數(shù)event_add_nolock實(shí)現(xiàn)的,下文會分析
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的等待列表中。
* 真正的將信號事件注冊在event_base上,也就是進(jìn)行了信號的內(nèi)部事件注冊
*
* 添加事件的實(shí)現(xiàn)函數(shù);就像event_add一樣,異常:
* 1)它需要使用者加鎖;2)如果tv_is_absolute設(shè)置了,則將tv作為絕對時間對待,而不是相對于當(dāng)前添加時間的時間間隔
*/
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),否則報錯
EVUTIL_ASSERT(!(ev->ev_flags & ~EVLIST_ALL));

// 已經(jīng)處于結(jié)束狀態(tài)的事件再次添加會報錯
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.
*/
/**
* 為超時插入做準(zhǔn)備,如果超時控制不為空,且事件沒有處于超時狀態(tài)
* 首先為將要插入的超時事件準(zhǔn)備插入節(jié)點(diǎn),主要是為了防止后面出現(xiàn)這種情況:
* 事件狀態(tài)改變已經(jīng)完成,但是最小堆申請節(jié)點(diǎn)卻失??;
* 因此,如果在任何一步出現(xiàn)錯誤,都不能改變事件狀態(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í)行信號事件的回調(diào)函數(shù),同時又不在主線程,則
* 需要等待回調(diào)函數(shù)執(zhí)行完畢才能繼續(xù)添加事件,否則可能會在
* ev_ncalls和ev_pncalls上產(chǎn)生競爭。
*/
#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

/**
* 如果事件類型是IO事件/信號事件,同時事件狀態(tài)不是已經(jīng)插入/激活/下一次激活狀態(tài),
* 則根據(jù)事件類型將事件添加到不同的映射表或者隊(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);
// 如果事件是信號事件,則將事件插入信號與文件描述符的映射表中
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í)就是這是事件的已注冊標(biāo)志
/**
* 如果上述添加行為正確,則設(shè)置通知主線程的標(biāo)志,因?yàn)橐呀?jīng)添加了新事件,
* 防止1)優(yōu)先級高的事件被優(yōu)先級低的事件倒掛,2)防止主線程忙等,會通知主線程有新事件
*/
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í)行之后,才能改變超時狀態(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.
*
* 對于持久化的定時事件,需要記住超時時間,并重新注冊事件
* 如果tv_is_absolute設(shè)置,則事件超時時間就等于輸入時間參數(shù)
*/
if (ev->ev_closure == EV_CLOSURE_EVENT_PERSIST && !tv_is_absolute)
ev->ev_io_timeout = *tv;

/**
* 如果沒有使用USE_REINSERT_TIMEOUT,則當(dāng)事件處于超時狀態(tài)時,需要從隊(duì)列中移除事件
* 因?yàn)橥瑯拥氖录荒苤匦虏迦?,所以?dāng)一個事件已經(jīng)處于超時狀態(tài)時,為防止執(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)激活,而且是超時事件的激活狀態(tài),
* 則在回調(diào)函數(shù)執(zhí)行之前,需要重新調(diào)度這個超時事件,因此需要把它移出激活隊(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中的緩存時間
gettime(base, &now);

// 檢查base是否使用了公用超時隊(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è)置絕對超時時間,則設(shè)置時間超時時間為輸入時間參數(shù)
* 2)如果使用的公用超時隊(duì)列機(jī)制,則根據(jù)當(dāng)前base中時間和輸入超時時間間隔計(jì)算出時間超時時間,
* 并對超時時間進(jìn)行公用超時掩碼計(jì)算
* 3)如果是其他情況,則直接根據(jù)base中時間和輸入超時時間間隔計(jì)算事件的超時時間
*/
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));

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

/**
* 如果使用了公用超時隊(duì)列機(jī)制,則需要根據(jù)當(dāng)前事件的超時時間將當(dāng)前事件插入具有相同超時時間的時間列表
*/
if (common_timeout) {
// 根據(jù)事件超時時間獲取應(yīng)該插入的公用超時隊(duì)列,注意此處是從隊(duì)尾插入
struct common_timeout_list *ctl =
get_common_timeout_list(base, &ev->ev_timeout);
/**
* 如果當(dāng)前事件是公用超時隊(duì)列的第一個事件,則因此需要將此超時事件插入最小堆
* 解釋:公用超時隊(duì)列機(jī)制:處于同一個公用超時隊(duì)列中的所有事件具有相同的超時控制,因此只需要將公用超時隊(duì)列
* 的第一個事件插入最小堆,當(dāng)超時觸發(fā)時,可以通過遍歷公用超時隊(duì)列獲取同樣的超時事件。
*/
if (ev == TAILQ_FIRST(&ctl->events)) {
common_timeout_schedule(ctl, &now, ev);
}
} else {
// 如果沒有使用公用超時隊(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)前事件是否位于最小堆根部,如果是,則需要通知主線程
* 否則,需要查看最小堆根部超時時間是否已經(jīng)小于當(dāng)前時間,即已經(jīng)超時了,如果是,則需要通知主線程
*/
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,對于 I/O 事件,從 I/O 的 demultiplexer 上將事件注銷;對于 Signal 事件,將從 Signal 事件鏈表中刪除;對于定時事件,將從堆上刪除; 同樣刪除事件的操作則不一定是原子的,比如刪除時間事件之后,有可能從系統(tǒng) I/O 機(jī) 制中注銷會失敗。

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

    關(guān)注

    0

    文章

    404

    瀏覽量

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

    關(guān)注

    3

    文章

    4381

    瀏覽量

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

    關(guān)注

    0

    文章

    508

    瀏覽量

    20219
收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評論

    相關(guān)推薦
    熱點(diǎn)推薦

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

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

    安裝libevent報錯

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

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

    說明:1、本教程重在BSP驅(qū)動包設(shè)計(jì)方法和HAL框架學(xué)習(xí),并將HAL庫里面的各種彎彎繞捋順,從而方便我們的程序設(shè)計(jì)。2、由于是基于HAL的文檔,所以不限制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)+設(shè)備寄存器應(yīng)用代碼+OS+標(biāo)準(zhǔn)+設(shè)備寄存器STM32 固件結(jié)構(gòu)STM32 官方標(biāo)準(zhǔn)固件
    發(fā)表于 08-10 06:32

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

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

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

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

    精選的 Go 框架,和軟件的精選清單 精選資料分享

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

    怎樣去移植在ARM嵌入式平臺的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平臺上

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

    一個最簡單的事件驅(qū)動的IO libevent編程例子

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

    Atmel Studio 6簡介 大概了解Atmel軟件框架

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

    C++的框架、和資源資料匯總大全

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

    ADM1266 Linux API和Python簡介

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

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

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

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

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