Libevent(4)之使用教程(3)配置

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_SIGNALEV_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 之前的版本不支持同時插入多個具有相同文件描述符和相同讀寫標志的事件。換句話說,每個文件描述符每次只能有一個事件等待讀取,也只能有一個事件等待寫入。







Alt

Once Day

也信美人終作土,不堪幽夢太匆匆......

如果這篇文章為您帶來了幫助或啟發,不妨點個贊👍和關注,再加上一個小小的收藏?!

(。???。)感謝您的閱讀與支持~~~

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/90678.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/90678.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/90678.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

若依前后端分離版學習筆記(三)——表結構介紹

前言&#xff1a; 這一節將ruoyi框架中數據庫中的表結構過一遍&#xff0c;查看都有哪些表及其表結構及關聯關系&#xff0c;為后續代碼學習做準備。 一 代碼生成表記錄代碼生成的業務表及相關字段1 代碼生成業務表 CREATE TABLE gen_table (table_id bigint(20) NOT NULL AUTO…

NFS服務安裝與使用

概述 內網需要使用NFS服務掛載到其他服務器&#xff0c;用做數據備份使用。 安裝 # Centos yum install -y nfs-utils # Ubuntu apt install nfs-common配置 # 編輯 vim /etc/exports # 輸入內容 /public/KOL-ESbackup 172.29.1.0/24 192.168.8.63 192.168.8.64 192.168.8.65(r…

使用adb 發送廣播 動態改變app內的值

前言 在開發過程中有時候我們需要做一些調試工作。可以通過adb發送廣播實現。 廣播注冊 注意最后一個參數&#xff0c;Context.RECEIVER_EXPORTED 這是Android 34以后強制要求的&#xff0c;方便外部發送這個廣播。否則會報錯val filter IntentFilter()filter.addAction("…

【Web安全】邏輯漏洞之URL跳轉漏洞:原理、場景與防御

文章目錄前言一、漏洞本質二、攻擊原理正常跳轉流程漏洞觸發流程三、抓包的關鍵時機&#xff1a;跳轉參數生成時四、風險場景1.登錄/注冊后跳轉2.退出登錄跳轉3.分享/廣告鏈接跳轉4.密碼重置鏈接跳轉五、漏洞挖掘&#xff1a;怎么找到這種漏洞&#xff1f;1.找到跳轉參數2.篡改…

新手開發 App,容易陷入哪些誤區?

新手開發 App 時&#xff0c;常因對流程和用戶需求理解不足陷入誤區&#xff0c;不僅拖慢進度&#xff0c;還可能導致產品無人問津。?功能堆砌是最常見的陷阱。不少新手總想 “一步到位”&#xff0c;在初期版本就加入十幾項功能&#xff0c;比如做社區團購 App 時&#xff0c…

Linux學習篇11——Linux軟件包管理利器:RPM與YUM詳解與實戰指南,包含如何配置失效的YUM鏡像地址

引言 本文主要梳理 Linux 系統中的軟件包的概念&#xff0c;同時介紹RPM與YUM兩大核心管理工具的常用指令、區別聯系以及實戰技巧等。本文作為作者學習Linux系統的第11篇文章&#xff0c;依舊旨在總結當前的學習內容&#xff0c;同時鞏固知識以便日后的學習復習回顧。如有說的…

Vue3+ElementPlus實現可拖拽/吸附/搜索/收起展開的浮動菜單組件

在開發后臺管理系統時&#xff0c;我們經常會用到浮動菜單來快速訪問某些功能。本篇文章將分享一個基于 Vue3 ElementPlus 實現的浮動菜單組件&#xff0c;支持拖拽移動、邊緣吸附、二級菜單展開、菜單搜索過濾、視頻彈窗等交互效果&#xff0c;極大提升了用戶操作的便捷性與美…

CSS 盒子模型學習版的理解

文章目錄一、盒子模型構建流程&#xff08;一句話抓關鍵&#xff09;二、核心邏輯提煉三、代碼驗證四、一句話總結流程通過手繪圖示&#xff0c;清晰拆解 Content&#xff08;內容&#xff09;→ Padding&#xff08;內邊距&#xff09;→ Border&#xff08;邊框&#xff09;→…

解決線程安全的幾個方法

線程安全&#xff1a;線程安全問題的發現與解決-CSDN博客 Java中所使用的并發機制依賴于JVM的實現和CPU的指令。 所以了解并掌握深入Java并發編程基礎的前提知識是熟悉JVM的實現了解CPU的指令。 1.volatile簡介 在多線程并發編程中&#xff0c;有兩個重要的關鍵字&#xff1a…

大模型應用班-第2課 DeepSeek使用與提示詞工程課程重點 學習ollama 安裝 用deepseek-r1:1.5b 分析PDF 內容

DeepSeek使用與提示詞工程課程重點Homework&#xff1a;ollama 安裝 用deepseek-r1:1.5b 分析PDF 內容python 代碼建構&#xff1a;1.小模型 1.5b 可以在 筆記本上快速執行2.分析結果還不錯3. 重點是提示詞 prompt 的寫法一、DeepSeek模型創新與特點1. DeepSeek-V3模型特點采用…

在FreeBSD系統下使用llama-cpp運行飛槳開源大模型Ernie4.5 0.3B(失敗)

先上結論&#xff0c;截止到目前2025.7.25日&#xff0c;還不能用。也就是Ernie4.5模型無法在llama.cpp 和Ollama上進行推理&#xff0c;原因主要就llama是不支持Ernie4.5異構MoE架構。 不局限于FreeBSD系統&#xff0c;Windows也測試失敗&#xff0c;理論上Ubuntu下也是不行。…

OpenCV圖像梯度、邊緣檢測、輪廓繪制、凸包檢測大合集

一、圖像梯度 在圖像處理中&#xff0c;「梯度&#xff08;Gradient&#xff09;」是一個非常基礎但又極其重要的概念。它是圖像邊緣檢測、特征提取、紋理分析等眾多任務的核心。梯度的本質是在空間上描述像素灰度值變化的快慢和方向。 但我們如何在圖像中計算梯度&#xff1f;…

GitHub 趨勢日報 (2025年07月25日)

&#x1f4ca; 由 TrendForge 系統生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日報中的項目描述已自動翻譯為中文 &#x1f4c8; 今日獲星趨勢圖 今日獲星趨勢圖1814Resume-Matcher985neko714Qwen3-Coder622OpenBB542BillionMail486hrms219hyper…

編程語言Java——核心技術篇(五)IO流:數據洪流中的航道設計

&#x1f31f; 你好&#xff0c;我是 勵志成為糕手 &#xff01; &#x1f30c; 在代碼的宇宙中&#xff0c;我是那個追逐優雅與性能的星際旅人。 ? 每一行代碼都是我種下的星光&#xff0c;在邏輯的土壤里生長成璀璨的銀河&#xff1b; &#x1f6e0;? 每一個算法都是我繪制…

基于FPGA的16QAM軟解調+卷積編碼Viterbi譯碼通信系統,包含幀同步,信道,誤碼統計,可設置SNR

目錄 1.引言 2.算法仿真效果 3.算法涉及理論知識概要 3.1 16QAM調制軟解調原理 3.2 幀同步 3.3 卷積編碼&#xff0c;維特比譯碼 4.Verilog程序接口 5.參考文獻 6.完整算法代碼文件獲得 1.引言 基于FPGA的16QAM軟解調卷積編碼Viterbi譯碼通信系統開發,包含幀同步,高斯…

Python數據分析基礎(二)

一、Numpy 常用函數分類概覽函數類別常用函數基本數學函數np.sum(x)、np.sqrt(x)、np.exp(x)、np.log(x)、np.sin(x)、np.abs(x)、np.power(a, b)、np.round(x, n) 等統計函數np.mean(x)、np.median(x)、np.std(x)、np.var(x)、np.min(x)、np.max(x)、np.percentile(x, q) 等比…

Colab中如何臨時使用udocker(以MinIO為例)

本文主要是想記錄一下自己在Colab中用udocker啟動一個MinIO的容器的過程。 1. 命令行配置環境 由于目前沒有用到GPU&#xff0c;所以我選擇的是CPU的環境。(內存12G)然后就可以在命令行里安裝udocker了&#xff0c;并配置minio的環境 # 由于minio需要做兩個端口映射&#xff0c…

rt-thread 5.2.1 基于at-start-f437開發過程記錄

基于rt-thread 5.2.1 bsp/at/at32f437-start進行開發&#xff0c;記錄詳細過程&#xff0c;包括中間遇到的各種坑。 at32f437-start原理圖 自己設計的電路板主要換了一塊小封裝的同系列芯片, 目標是移植opENer。 1. 開發環境 env長時間不用&#xff0c;有點忘了。這次新下載…

EMCCD相機與電可調變焦透鏡的同步控制系統設計與實現

EMCCD相機與電可調變焦透鏡的同步控制系統設計與實現 前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家&#xff0c;覺得好請收藏。點擊跳轉到網站。 摘要 本文詳細介紹了基于Python的EMCCD相機&#xff0…

前綴和-560.和為k的子數組-力扣(LeetCode)

一、題目解析1.子數組是數組中元素的連續非空序列2.nums[i]范圍為[-1000,1000]&#xff0c;存在負數3.由于2的題目條件&#xff0c;該題不能用雙指針算法&#xff0c;不具備單調性 二、算法原理解法1&#xff1a;暴力解法->枚舉 O(N^2)固定一個值&#xff0c;向后枚舉數組和…