Libevent(4)之使用教程(3)配置事件
Author: Once Day Date: 2025年7月27日
一位熱衷于Linux學習和開發的菜鳥,試圖譜寫一場冒險之旅,也許終點只是一場白日夢…
漫漫長路,有人對你微笑過嘛…
本文檔翻譯于:Fast portable non-blocking network programming with Libevent
全系列文章可參考專欄: 十年代碼訓練_Once-Day的博客-CSDN博客
參考文章:
- 詳解libevent網絡庫(一)—框架的搭建_libevent詳解-CSDN博客
- 深度思考高性能網絡庫Libevent,從13個維度來解析Libevent到底是怎么回事 - 知乎
- 深入淺出理解libevent——2萬字總結_libev 堆-CSDN博客
- Fast portable non-blocking network programming with Libevent
- libevent
- C++網絡庫:Libevent網絡庫的原理及使用方法 - 知乎
- 深入理解libevent事件庫的原理與實踐技巧-騰訊云開發者社區-騰訊云
文章目錄
- Libevent(4)之使用教程(3)配置事件
- 6. 配置事件
- 6.1 構造事件對象
- 6.2 事件標志
- 6.3 關于事件持久化
- 6.4 將事件自身作為回調參數創建事件
- 6.5 超時時間事件
- 6.6 構建信號事件
- 6.7 使事件處于待處理與非待處理狀態
- 6.8 帶優先級的事件
- 6.9 檢查事件狀態
- 6.10 獲取當前運行的事件
- 6.11 配置一次性事件
- 6.12 手動激活事件
- 6.13 優化常見超時設置
- 6.14 區分已初始化事件與清零內存
- 6.15 過時的事件操作函數
6. 配置事件
Libevent 的基本操作單元是事件。每個事件都代表一組條件,包括:
- 文件描述符準備好讀取或寫入。
- 文件描述符變為可讀取或可寫入狀態(僅適用于邊緣觸發 IO)。
- 超時時間到期。
- 信號發生。
- 用戶觸發的事件。
事件具有相似的生命周期。當調用 Libevent 函數設置一個事件并將其與事件基座(event base)關聯后,該事件即完成初始化。此時,可以通過 “添加” 操作使其在基座中處于待處理(pending)狀態。當事件處于待處理狀態時,若觸發事件的條件滿足(例如,其文件描述符狀態改變或超時時間到期),事件會變為活躍(active)狀態,并執行其(用戶提供的)回調函數。如果事件配置為持久化(persistent),它會保持待處理狀態;若為非持久化,則在回調函數執行后不再處于待處理狀態。可以通過 “刪除” 操作使待處理事件變為非待處理狀態,也可以重新添加非待處理事件使其再次進入待處理狀態。
6.1 構造事件對象
要創建一個新事件,請使用 event_new () 接口。
#define EV_TIMEOUT 0x01
#define EV_READ 0x02
#define EV_WRITE 0x04
#define EV_SIGNAL 0x08
#define EV_PERSIST 0x10
#define EV_ET 0x20typedef void (*event_callback_fn)(evutil_socket_t, short, void *);struct event *event_new(struct event_base *base, evutil_socket_t fd,short what, event_callback_fn cb,void *arg);void event_free(struct event *event);
event_new () 函數會嘗試為事件基座(base)分配并構建一個新事件。what 參數是一組上述列出的標志(其語義將在下文描述)。如果 fd 為非負值,則它是我們將觀察讀寫事件的文件描述符。當事件處于活躍狀態時,Libevent 會調用提供的 cb 函數,并傳入以下參數:文件描述符 fd、觸發事件的所有標志位組合,以及構建該函數時傳入的 arg 值。
若發生內部錯誤或參數無效,event_new () 將返回 NULL。
所有新創建的事件均處于已初始化且非待處理狀態。要使事件進入待處理狀態,請調用 event_add ()(下文將詳細說明)。
要釋放一個事件,請調用 event_free ()。對處于待處理或活躍狀態的事件調用 event_free () 是安全的:這會先使事件變為非待處理和非活躍狀態,再進行釋放。
#include <event2/event.h>void cb_func(evutil_socket_t fd, short what, void *arg)
{const char *data = arg;printf("Got an event on socket %d:%s%s%s%s [%s]",(int) fd,(what&EV_TIMEOUT) ? " timeout" : "",(what&EV_READ) ? " read" : "",(what&EV_WRITE) ? " write" : "",(what&EV_SIGNAL) ? " signal" : "",data);
}void main_loop(evutil_socket_t fd1, evutil_socket_t fd2)
{struct event *ev1, *ev2;struct timeval five_seconds = {5,0};struct event_base *base = event_base_new();/* The caller has already set up fd1, fd2 somehow, and make themnonblocking. */ev1 = event_new(base, fd1, EV_TIMEOUT|EV_READ|EV_PERSIST, cb_func,(char*)"Reading event");ev2 = event_new(base, fd2, EV_WRITE|EV_PERSIST, cb_func,(char*)"Writing event");event_add(ev1, &five_seconds);event_add(ev2, NULL);event_base_dispatch(base);
}
上述函數定義于 <event2/event.h> 中,首次出現于 Libevent 2.0.1-alpha 版本。event_callback_fn 類型作為 typedef 首次出現于 Libevent 2.0.4-alpha 版本。
6.2 事件標志
EV_TIMEOUT:該標志表示事件在超時期滿后變為活躍狀態。構建事件時,EV_TIMEOUT 標志會被忽略:你可以在添加事件時設置超時時間,也可以不設置。當超時發生時,該標志會出現在回調函數的 ‘what’ 參數中。
EV_READ:該標志表示當指定的文件描述符準備好讀取時,事件變為活躍狀態。
EV_WRITE:該標志表示當指定的文件描述符準備好寫入時,事件變為活躍狀態。
EV_SIGNAL:用于實現信號檢測。詳見下文 “構建信號事件”。
EV_PERSIST:表示事件是持久化的。詳見下文 “關于事件持久化”。
EV_ET:表示如果底層事件基座(event_base)后端支持邊緣觸發事件,則該事件應采用邊緣觸發模式。這會影響 EV_READ 和 EV_WRITE 的語義。
自 Libevent 2.0.1-alpha 版本起,多個事件可以同時針對相同條件處于待處理狀態。例如,你可以有兩個事件在同一文件描述符準備好讀取時變為活躍狀態,其回調函數的執行順序是未定義的。
這些標志定義于 <event2/event.h> 中。除 EV_ET 于 Libevent 2.0.1-alpha 版本引入外,其余標志在 Libevent 1.0 版本之前就已存在。
6.3 關于事件持久化
默認情況下,當一個待處理事件變為活躍狀態時(例如,其文件描述符準備好讀寫或超時期滿),會在執行回調函數之前變為非待處理狀態。因此,若想讓事件再次進入待處理狀態,可在回調函數內部再次調用 event_add ()。
然而,若事件設置了 EV_PERSIST 標志,則該事件是持久化的,這意味著即使其回調函數被激活,事件仍會保持待處理狀態。若想在回調函數內部使它變為非待處理狀態,可調用 event_del ()。
持久化事件的超時時間會在其回調函數每次執行時重置。因此,對于一個帶有 EV_READ|EV_PERSIST 標志且超時時間為 5 秒的事件,在以下情況會變為活躍狀態:
- 每當套接字準備好讀取時。
- 每當距離事件上一次變為活躍狀態已過去 5 秒時。
6.4 將事件自身作為回調參數創建事件
通常,你可能希望創建一個能在回調參數中接收自身的事件。不過,你不能直接將事件指針作為參數傳遞給 event_new (),因為此時事件尚未存在。要解決這個問題,可以使用 event_self_cbarg () 函數。
event_self_cbarg () 函數會返回一個 “魔術” 指針,當該指針作為事件回調參數傳遞時,會告知 event_new () 創建一個能在回調中接收自身的事件。
#include <event2/event.h>static int n_calls = 0;void cb_func(evutil_socket_t fd, short what, void *arg)
{struct event *me = arg;printf("cb_func called %d times so far.\n", ++n_calls);if (n_calls > 100)event_del(me);
}void run(struct event_base *base)
{struct timeval one_sec = { 1, 0 };struct event *ev;/* We're going to set up a repeating timer to get called called 100times. */ev = event_new(base, -1, EV_PERSIST, cb_func, event_self_cbarg());event_add(ev, &one_sec);event_base_dispatch(base);
}
該函數還可與 event_new ()、evtimer_new ()、evsignal_new ()、event_assign ()、evtimer_assign () 以及 evsignal_assign () 配合使用。但對于非事件對象,它無法作為有效的回調參數。
event_self_cbarg () 函數在 Libevent 2.1.1-alpha 版本中首次引入。
6.5 超時時間事件
為方便使用,有一組以 evtimer_
開頭的宏,可替代 event_*
系列調用,用于分配和操作純超時事件。使用這些宏除了能提高代碼的清晰度外,沒有其他額外優勢。
#define evtimer_new(base, callback, arg) \event_new((base), -1, 0, (callback), (arg))
#define evtimer_add(ev, tv) \event_add((ev),(tv))
#define evtimer_del(ev) \event_del(ev)
#define evtimer_pending(ev, tv_out) \event_pending((ev), EV_TIMEOUT, (tv_out))
這些宏從 Libevent 0.6 版本起就已存在,不過 evtimer_new()
是在 Libevent 2.0.1-alpha 版本中才首次出現的。
6.6 構建信號事件
Libevent 也可以監聽 POSIX 風格的信號。要為信號構建處理器,請使用以下方式:
#define evsignal_new(base, signum, cb, arg) \event_new(base, signum, EV_SIGNAL|EV_PERSIST, cb, arg)
其參數與 event_new 類似,不同之處在于這里提供的是信號編號而非文件描述符。
struct event *hup_event;
struct event_base *base = event_base_new();/* call sighup_function on a HUP signal */
hup_event = evsignal_new(base, SIGHUP, sighup_function, NULL);
注意,信號回調會在信號發生后的事件循環中執行,因此在回調中調用那些不允許在常規 POSIX 信號處理器中調用的函數是安全的。
警告:不要為信號事件設置超時。這可能不受支持。[待修正:情況是否屬實?]
此外,還有一組便捷宏可用于處理信號事件。
#define evsignal_add(ev, tv) \event_add((ev),(tv))
#define evsignal_del(ev) \event_del(ev)
#define evsignal_pending(ev, what, tv_out) \event_pending((ev), (what), (tv_out))
evsignal_* 系列宏從 Libevent 2.0.1-alpha 版本開始出現。早期版本中它們被命名為 signal_add ()、signal_del () 等。
處理信號時的注意事項:在當前版本的 Libevent 中,對于大多數后端,同一進程中同一時間只能有一個 event_base 監聽信號。如果同時向兩個 event_base 添加信號事件 —— 即便信號不同 —— 也只有一個 event_base 會接收信號。kqueue 后端沒有這一限制。
不使用堆分配創建事件:出于性能等方面的原因,有些人希望將事件作為更大結構的一部分進行分配。這種方式在每次使用事件時,可節省:
- 在堆上分配小對象帶來的內存分配器開銷。
- 解引用 struct event 指針的時間開銷。
- 若事件不在緩存中時可能產生的額外緩存未命中時間開銷。
但這種方法可能會破壞與其他 Libevent 版本的二進制兼容性,因為不同版本中 event 結構的大小可能不同。
這些開銷通常非常小,對大多數應用程序而言無關緊要。除非確定堆分配事件會帶來顯著的性能損失,否則應堅持使用 event_new ()。如果未來版本的 Libevent 使用的 event 結構比當前構建時的更大,使用 event_assign () 可能會導致難以診斷的錯誤。
int event_assign(struct event *event, struct event_base *base,evutil_socket_t fd, short what,void (*callback)(evutil_socket_t, short, void *), void *arg);
event_assign () 的所有參數與 event_new () 相同,不同之處在于 event 參數必須指向一個未初始化的事件。成功時返回 0,發生內部錯誤或參數無效時返回 -1。
#include <event2/event.h>
/* Watch out! Including event_struct.h means that your code will not* be binary-compatible with future versions of Libevent. */
#include <event2/event_struct.h>
#include <stdlib.h>struct event_pair {evutil_socket_t fd;struct event read_event;struct event write_event;
};
void readcb(evutil_socket_t, short, void *);
void writecb(evutil_socket_t, short, void *);
struct event_pair *event_pair_new(struct event_base *base, evutil_socket_t fd)
{struct event_pair *p = malloc(sizeof(struct event_pair));if (!p) return NULL;p->fd = fd;event_assign(&p->read_event, base, fd, EV_READ|EV_PERSIST, readcb, p);event_assign(&p->write_event, base, fd, EV_WRITE|EV_PERSIST, writecb, p);return p;
}
也可以使用 event_assign () 初始化棧分配或靜態分配的事件。
警告:切勿對已在 event base 中處于待處理狀態的事件調用 event_assign ()。這樣做可能會導致極難診斷的錯誤。如果事件已初始化且處于待處理狀態,需先調用 event_del (),再調用 event_assign ()。
有一些便捷宏可用于為僅超時事件或信號事件調用 event_assign ():
#define evtimer_assign(event, base, callback, arg) \event_assign(event, base, -1, 0, callback, arg)
#define evsignal_assign(event, base, signum, callback, arg) \event_assign(event, base, signum, EV_SIGNAL|EV_PERSIST, callback, arg)
如果需要使用 event_assign () 并保持與未來 Libevent 版本的二進制兼容性,可以讓 Libevent 庫在運行時告知 struct event 應有的大小:
size_t event_get_struct_event_size(void);
該函數返回需要為 struct event 預留的字節數。同樣,只有當確定堆分配確實是程序中的顯著問題時,才應使用此函數,因為它會使代碼的讀寫難度大幅增加。
注意,未來 event_get_struct_event_size () 返回的值可能小于 sizeof (struct event)。出現這種情況時,意味著 struct event 末尾的額外字節只是為未來 Libevent 版本預留的填充字節。
下面是與上文類似的示例,但不依賴 event_struct.h 中定義的 struct event 大小,而是使用 event_get_struct_size () 在運行時獲取正確的大小。
#include <event2/event.h>
#include <stdlib.h>/* When we allocate an event_pair in memory, we'll actually allocate* more space at the end of the structure. We define some macros* to make accessing those events less error-prone. */
struct event_pair {evutil_socket_t fd;
};/* Macro: yield the struct event 'offset' bytes from the start of 'p' */
#define EVENT_AT_OFFSET(p, offset) \((struct event*) ( ((char*)(p)) + (offset) ))
/* Macro: yield the read event of an event_pair */
#define READEV_PTR(pair) \EVENT_AT_OFFSET((pair), sizeof(struct event_pair))
/* Macro: yield the write event of an event_pair */
#define WRITEEV_PTR(pair) \EVENT_AT_OFFSET((pair), \sizeof(struct event_pair)+event_get_struct_event_size())/* Macro: yield the actual size to allocate for an event_pair */
#define EVENT_PAIR_SIZE() \(sizeof(struct event_pair)+2*event_get_struct_event_size())void readcb(evutil_socket_t, short, void *);
void writecb(evutil_socket_t, short, void *);
struct event_pair *event_pair_new(struct event_base *base, evutil_socket_t fd)
{struct event_pair *p = malloc(EVENT_PAIR_SIZE());if (!p) return NULL;p->fd = fd;event_assign(READEV_PTR(p), base, fd, EV_READ|EV_PERSIST, readcb, p);event_assign(WRITEEV_PTR(p), base, fd, EV_WRITE|EV_PERSIST, writecb, p);return p;
}
event_assign () 函數定義于 <event2/event.h> 中,從 Libevent 2.0.1-alpha 版本開始存在。自 2.0.3-alpha 版本起,它返回 int 類型;在此之前,返回 void 類型。event_get_struct_event_size () 函數在 Libevent 2.0.4-alpha 版本中引入。event 結構本身定義于 <event2/event_struct.h> 中。
6.7 使事件處于待處理與非待處理狀態
創建事件后,需通過添加操作使其進入待處理狀態才會實際生效,這一操作可通過 event_add
完成:
int event_add(struct event *ev, const struct timeval *tv);
對非待處理狀態的事件調用 event_add
,會使其在已配置的事件基座(base)中變為待處理狀態。函數成功時返回 0,失敗時返回 -1。若 tv
為 NULL,則添加的事件無超時設置;否則,tv
表示超時時間(單位為秒和微秒)。
若對已處于待處理狀態的事件調用 event_add
,事件會保持待處理狀態,并按提供的超時時間重新調度。若事件已處于待處理狀態,且重新添加時 timeout
設為 NULL,則 event_add
不會產生任何效果。
注意:不要將 tv
設置為希望超時觸發的具體時間。例如,在 2010 年 1 月 1 日執行 tv->tv_sec = time(NULL)+10;
,超時會等待 40 年而非 10 秒(此處為示例,實際時間差需根據具體場景計算)。
int event_del(struct event *ev);
對已初始化的事件調用 event_del
,會使其變為非待處理和非活躍狀態。若事件原本就不處于待處理或活躍狀態,則該操作無效果。成功時返回 0,失敗時返回 -1。
注意:若在事件變為活躍狀態后、其回調函數有機會執行前刪除該事件,回調函數將不會被執行。
int event_remove_timer(struct event *ev);
最后,可完全移除待處理事件的超時設置,而不刪除其 IO 或信號組件。若事件原本就沒有待處理的超時設置,event_remove_timer()
無效果;若事件僅包含超時設置(無 IO 或信號組件),event_remove_timer()
的效果與 event_del()
相同。成功時返回 0,失敗時返回 -1。
上述函數定義于 <event2/event.h>
中:event_add()
和 event_del()
從 Libevent 0.1 版本起就已存在;event_remove_timer()
則在 2.1.2-alpha 版本中新增。
6.8 帶優先級的事件
當多個事件同時觸發時,Libevent 并未定義它們的回調函數執行順序。不過,你可以通過設置優先級來指定某些事件比其他事件更重要。正如前面章節所討論的,每個 event_base 都關聯有一個或多個優先級值。在將事件添加到 event_base 之前(但在事件初始化之后),你可以為其設置優先級。
int event_priority_set(struct event *event, int priority);
事件的優先級是一個介于 0 到(event_base 中的優先級數量 - 1)之間的數字。該函數成功時返回 0,失敗時返回 -1。
當多個不同優先級的事件變為活躍狀態時,低優先級事件不會被執行。相反,Libevent 會先執行高優先級事件,然后再次檢查事件。只有當沒有高優先級事件處于活躍狀態時,低優先級事件才會被執行。
#include <event2/event.h>void read_cb(evutil_socket_t, short, void *);
void write_cb(evutil_socket_t, short, void *);void main_loop(evutil_socket_t fd)
{struct event *important, *unimportant;struct event_base *base;base = event_base_new();event_base_priority_init(base, 2);/* Now base has priority 0, and priority 1 */important = event_new(base, fd, EV_WRITE|EV_PERSIST, write_cb, NULL);unimportant = event_new(base, fd, EV_READ|EV_PERSIST, read_cb, NULL);event_priority_set(important, 0);event_priority_set(unimportant, 1);/* Now, whenever the fd is ready for writing, the write callback willhappen before the read callback. The read callback won't happen atall until the write callback is no longer active. */
}
如果不為事件設置優先級,其默認優先級為 event_base 中隊列數量的一半。
該函數聲明于 <event2/event.h>
中,從 Libevent 1.0 版本起就已存在。
6.9 檢查事件狀態
有時你需要判斷一個事件是否已被添加,以及查看它所關聯的對象。
int event_pending(const struct event *ev, short what, struct timeval *tv_out);#define event_get_signal(ev) /* ... */
evutil_socket_t event_get_fd(const struct event *ev);
struct event_base *event_get_base(const struct event *ev);
short event_get_events(const struct event *ev);
event_callback_fn event_get_callback(const struct event *ev);
void *event_get_callback_arg(const struct event *ev);
int event_get_priority(const struct event *ev);void event_get_assignment(const struct event *event,struct event_base **base_out,evutil_socket_t *fd_out,short *events_out,event_callback_fn *callback_out,void **arg_out);
event_pending
函數用于判斷指定事件是否處于待處理(pending)或活躍(active)狀態。如果是,且 what
參數中設置了 EV_READ、EV_WRITE、EV_SIGNAL、EV_TIMEOUT 中的任一標志,該函數會返回事件當前在待處理或活躍狀態下所關聯的所有標志。如果提供了 tv_out
,且 what
中設置了 EV_TIMEOUT,同時事件當前因超時而處于待處理或活躍狀態,那么 tv_out
會被設置為該事件超時到期的時間。
event_get_fd()
和 event_get_signal()
函數分別返回事件所配置的文件描述符和信號編號。event_get_base()
函數返回事件所關聯的 event_base。event_get_events()
函數返回事件的標志(如 EV_READ、EV_WRITE 等)。event_get_callback()
和 event_get_callback_arg()
函數分別返回回調函數和回調參數指針。event_get_priority()
函數返回事件當前被分配的優先級。
event_get_assignment()
函數會將事件的所有已分配字段復制到提供的指針中。如果某個指針為 NULL,則會忽略該字段。
#include <event2/event.h>
#include <stdio.h>/* Change the callback and callback_arg of 'ev', which must not be* pending. */
int replace_callback(struct event *ev, event_callback_fn new_callback,void *new_callback_arg)
{struct event_base *base;evutil_socket_t fd;short events;int pending;pending = event_pending(ev, EV_READ|EV_WRITE|EV_SIGNAL|EV_TIMEOUT,NULL);if (pending) {/* We want to catch this here so that we do not re-assign a* pending event. That would be very very bad. */fprintf(stderr,"Error! replace_callback called on a pending event!\n");return -1;}event_get_assignment(ev, &base, &fd, &events,NULL /* ignore old callback */ ,NULL /* ignore old callback argument */);event_assign(ev, base, fd, events, new_callback, new_callback_arg);return 0;
}
這些函數均聲明于 <event2/event.h>
中。event_pending()
從 Libevent 0.1 版本起就已存在;Libevent 2.0.1-alpha 版本引入了 event_get_fd()
和 event_get_signal()
;Libevent 2.0.2-alpha 版本引入了 event_get_base()
;Libevent 2.1.2-alpha 版本新增了 event_get_priority()
;其他函數則在 Libevent 2.0.4-alpha 版本中首次出現。
6.10 獲取當前運行的事件
出于調試或其他目的,你可以獲取指向當前正在運行的事件的指針。
struct event *event_base_get_running_event(struct event_base *base);
需要注意的是,只有在提供的 event_base 的事件循環中調用該函數時,其行為才是確定的。在其他線程中調用該函數是不被支持的,可能會導致未定義行為。
該函數聲明于 <event2/event.h>
中,在 Libevent 2.1.1-alpha 版本中引入。
6.11 配置一次性事件
如果你不需要多次添加或刪除某個事件,并且該事件無需持久化,那么可以使用 event_base_once()
。
int event_base_once(struct event_base *, evutil_socket_t, short,void (*)(evutil_socket_t, short, void *), void *, const struct timeval *);
此函數的接口與 event_new()
相同,不過它不支持 EV_SIGNAL
或 EV_PERSIST
標志。被調度的事件會以默認優先級插入并執行。當回調函數最終執行完畢后,Libevent 會自行釋放內部的事件結構。成功時返回 0,失敗時返回 -1。
通過 event_base_once()
插入的事件無法被刪除或手動激活:如果你希望能夠取消某個事件,應使用常規的 event_new()
或 event_assign()
接口來創建它。
此外需要注意,在 Libevent 2.0 及之前的版本中,如果事件從未被觸發,用于存儲它的內部內存將永遠不會被釋放。從 Libevent 2.1.2-alpha 版本開始,當釋放 event_base
時,這些未激活的事件也會被釋放,但仍需留意:如果它們的回調參數關聯了某些存儲資源,除非你的程序主動跟蹤并釋放這些資源,否則它們不會自動被釋放。
6.12 手動激活事件
極少數情況下,你可能希望在事件條件未觸發時強制使其變為活躍狀態。
void event_active(struct event *ev, int what, short ncalls);
event_active()
函數可使事件 ev
以指定標志 what
(由 EV_READ、EV_WRITE 和 EV_TIMEOUT 組合而成)變為活躍狀態。該事件無需事先處于待處理狀態,激活操作也不會使其進入待處理狀態。
警告:對同一事件遞歸調用 event_active()
可能導致資源耗盡。以下代碼片段展示了錯誤使用 event_active()
的情況:
struct event *ev;static void cb(int sock, short which, void *arg) {/* Whoops: Calling event_active on the same event unconditionallyfrom within its callback means that no other events might not getrun! */event_active(ev, EV_WRITE, 0);
}int main(int argc, char **argv) {struct event_base *base = event_base_new();ev = event_new(base, -1, EV_PERSIST | EV_READ, cb, NULL);event_add(ev, NULL);event_active(ev, EV_WRITE, 0);event_base_loop(base, 0);return 0;
}
這種實現會導致事件循環僅執行一次,隨后無限調用回調函數 cb
,形成死循環。
// Example: Alternative solution to the above problem using timersstruct event *ev;
struct timeval tv;static void cb(int sock, short which, void *arg) {if (!evtimer_pending(ev, NULL)) {event_del(ev);evtimer_add(ev, &tv);}
}int main(int argc, char **argv) {struct event_base *base = event_base_new();tv.tv_sec = 0;tv.tv_usec = 0;ev = evtimer_new(base, cb, NULL);evtimer_add(ev, &tv);event_base_loop(base, 0);return 0;
}// Example: Alternative solution to the above problem using event_config_set_max_dispatch_interval()
struct event *ev;static void cb(int sock, short which, void *arg) {event_active(ev, EV_WRITE, 0);
}int main(int argc, char **argv) {struct event_config *cfg = event_config_new();/* Run at most 16 callbacks before checking for other events. */event_config_set_max_dispatch_interval(cfg, NULL, 16, 0);struct event_base *base = event_base_new_with_config(cfg);ev = event_new(base, -1, EV_PERSIST | EV_READ, cb, NULL);event_add(ev, NULL);event_active(ev, EV_WRITE, 0);event_base_loop(base, 0);return 0;
}
event_active()
函數定義于 <event2/event.h>
中,自 Libevent 0.3 版本起就已存在。
6.13 優化常見超時設置
當前版本的 Libevent 采用二叉堆算法來跟蹤待處理事件的超時設置。對于添加和刪除每個事件的超時設置,二叉堆的性能為 O (lg n) 級別。如果添加的事件超時值是隨機分布的,這種算法是最優的;但如果有大量事件具有相同的超時值,它就不是最優選擇了。
例如,假設有一萬個事件,每個事件在添加后 5 秒觸發超時。在這種情況下,使用雙向鏈表隊列實現可以使每個超時操作的性能達到 O(1)。
當然,你不會希望對所有超時值都使用隊列,因為隊列僅在超時值固定時更快。如果某些超時值大致呈隨機分布,那么將這些超時值添加到隊列中會花費 O(n) 的時間,這比二叉堆要慢得多。
Libevent 允許你將部分超時設置放入隊列,其余的放入二叉堆,以此解決上述問題。具體做法是:向 Libevent 請求一個特殊的 “通用超時” timeval,然后使用該 timeval 來添加具有相同超時設置的事件。如果有大量事件使用單一的通用超時設置,這種優化可以提升超時處理的性能。
const struct timeval *event_base_init_common_timeout(struct event_base *base, const struct timeval *duration);
該函數接收 event_base 和要初始化的通用超時持續時間作為參數,返回一個指向特殊 struct timeval 的指針。使用這個特殊的 timeval 添加事件時,事件會被放入 O(1) 級別的隊列,而非 O(lgn) 級別的堆。你可以在代碼中自由復制或賦值這個特殊的 timeval,但它僅對用于構建它的特定 base 有效。不要依賴其實際內容:Libevent 會利用這些內容來確定使用哪個隊列。
#include <event2/event.h>
#include <string.h>/* We're going to create a very large number of events on a given base,* nearly all of which have a ten-second timeout. If initialize_timeout* is called, we'll tell Libevent to add the ten-second ones to an O(1)* queue. */
struct timeval ten_seconds = { 10, 0 };void initialize_timeout(struct event_base *base)
{struct timeval tv_in = { 10, 0 };const struct timeval *tv_out;tv_out = event_base_init_common_timeout(base, &tv_in);memcpy(&ten_seconds, tv_out, sizeof(struct timeval));
}int my_event_add(struct event *ev, const struct timeval *tv)
{/* Note that ev must have the same event_base that we passed toinitialize_timeout */if (tv && tv->tv_sec == 10 && tv->tv_usec == 0)return event_add(ev, &ten_seconds);elsereturn event_add(ev, tv);
}
與所有優化函數一樣,除非確定它對性能有顯著影響,否則應避免使用通用超時功能。
此功能在 Libevent 2.0.4-alpha 版本中引入。
6.14 區分已初始化事件與清零內存
Libevent 提供了一些函數,可用于區分已初始化的事件和被清零的內存(例如,通過 calloc() 分配或用 memset()、bzero() 清零的內存)。
int event_initialized(const struct event *ev);#define evsignal_initialized(ev) event_initialized(ev)
#define evtimer_initialized(ev) event_initialized(ev)
這些函數無法可靠地區分已初始化事件和未初始化內存塊。只有當確定相關內存要么已清零,要么已初始化為事件時,才應使用它們。通常,除非有非常特定的應用場景,否則無需使用這些函數。event_new () 返回的事件始終是已初始化的。
#include <event2/event.h>
#include <stdlib.h>struct reader {evutil_socket_t fd;
};#define READER_ACTUAL_SIZE() \(sizeof(struct reader) + \event_get_struct_event_size())#define READER_EVENT_PTR(r) \((struct event *) (((char*)(r))+sizeof(struct reader)))struct reader *allocate_reader(evutil_socket_t fd)
{struct reader *r = calloc(1, READER_ACTUAL_SIZE());if (r)r->fd = fd;return r;
}void readcb(evutil_socket_t, short, void *);
int add_reader(struct reader *r, struct event_base *b)
{struct event *ev = READER_EVENT_PTR(r);if (!event_initialized(ev))event_assign(ev, b, r->fd, EV_READ, readcb, r);return event_add(ev, NULL);
}
event_initialized () 函數從 Libevent 0.3 版本起就已存在。
6.15 過時的事件操作函數
Libevent 2.0 之前的版本沒有 event_assign () 或 event_new (),而是使用 event_set () 函數將事件與 “當前” 基座(base)關聯。如果有多個基座,需要記得在之后調用 event_base_set (),以確保事件關聯到實際想要使用的基座。
void event_set(struct event *event, evutil_socket_t fd, short what,void(*callback)(evutil_socket_t, short, void *), void *arg);
int event_base_set(struct event_base *base, struct event *event);
event_set () 函數類似于 event_assign (),不同之處在于它使用當前基座。event_base_set () 函數用于更改事件所關聯的基座。
event_set () 有一些變體,方便處理定時器和信號:evtimer_set () 大致對應于 evtimer_assign (),evsignal_set () 大致對應于 evsignal_assign ()。
Libevent 2.0 之前的版本中,基于信號的 event_set() 變體使用 signal_
作為前綴,而非 evsignal_
(即有 signal_set()、signal_add()、signal_del()、signal_pending() 和 signal_initialized())。真正古老的 Libevent 版本(0.6 之前)使用 timeout_
而非 evtimer_
,因此,如果進行代碼考古,可能會看到 timeout_add()、timeout_del()、timeout_initialized()、timeout_set()、timeout_pending () 等函數。
Libevent 2.0 之前的版本沒有 event_get_fd () 和 event_get_signal () 函數,而是使用兩個宏:EVENT_FD () 和 EVENT_SIGNAL ()。這些宏直接訪問事件結構的內容,因此破壞了不同版本間的二進制兼容性;在 2.0 及之后的版本中,它們只是 event_get_fd () 和 event_get_signal () 的別名。
由于 Libevent 2.0 之前的版本不支持鎖定,從運行基座的線程外部調用任何更改事件相對于基座狀態的函數都是不安全的,包括 event_add ()、event_del ()、event_active () 和 event_base_once ()。
還有一個 event_once () 函數,作用類似于 event_base_once (),但使用當前基座。
在 Libevent 2.0 之前,EV_PERSIST 標志與超時的交互不夠合理。當時,EV_PERSIST 標志不會在事件激活時重置超時,這與現在的行為不同。
Libevent 2.0 之前的版本不支持同時插入多個具有相同文件描述符和相同讀寫標志的事件。換句話說,每個文件描述符每次只能有一個事件等待讀取,也只能有一個事件等待寫入。
Once Day
也信美人終作土,不堪幽夢太匆匆......
如果這篇文章為您帶來了幫助或啟發,不妨點個贊👍和關注,再加上一個小小的收藏?!
(。???。)感謝您的閱讀與支持~~~