libevent之event

Philosophy發表於2024-06-26

目錄
  • 使用事件
    • 構造事件物件
      • 事件標誌
      • 關於事件永續性
      • 建立事件作為其自己的回撥引數
      • 僅超時事件
      • 構造訊號事件
      • 處理訊號時的注意事項
        • 建立使用者觸發的事件
      • 設定不帶堆分配的事件
    • 使事件掛起和非掛起
    • 具有優先次序的事件
    • 檢查事件狀態
    • 查詢當前正在執行的事件
    • 配置一次性事件
    • 手動啟用事件
    • 最佳化常見超時
    • 講述一個好的事件,而不是清除的記憶
    • 過時的事件操作函式

使用事件

Libevent 的基本操作單元是事件。每個活動 表示一組條件,包括:

  • 準備讀取或寫入的檔案描述符。
  • 準備讀取或寫入的檔案描述符 (僅限 Edge 觸發的 IO)。
  • 超時即將到期。
  • 出現訊號。
  • 使用者觸發的事件。

事件具有相似的生命週期。將 Libevent 函式呼叫到 設定一個事件並將其與事件庫關聯,它將被初始化。此時,您可以新增,這使它在基礎中處於掛起狀態。當事件處於掛起狀態時,如果條件 這將觸發事件發生(例如,其檔案描述符更改 狀態或其超時過期),事件變為活動狀態,其 (使用者提供)回撥函式執行。如果事件配置為永續性,則該事件仍處於掛起狀態。如果它不是持久的,它就會停止 當其回撥執行時處於掛起狀態。您可以建立掛起事件 non-pending 透過刪除它,您可以將非 pending 事件新增到 再次將其設定為掛起狀態。

構造事件物件

若要建立新事件,請使用 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           0x20

typedef 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() 函式嘗試分配和構造一個新事件 與event_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 them
           nonblocking. */

        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 中。

事件標誌

  • EV_TIMEOUT

    此標誌指示超時後變為活動狀態。
    在構造事件時忽略 EV _ TIMEOUT 標誌: you可以在新增事件時設定超時,也可以不設定當超時時,將“ what”引數設定為回撥函式已經發生了。

  • EV_READ

    此標誌指示當提供的 檔案描述符已準備好讀取。

  • EV_WRITE

    此標誌指示當提供的 檔案描述符已準備好寫入。

  • EV_SIGNAL

    用於實現訊號檢測。請參閱“構造訊號事件” 下面。

  • EV_PERSIST

    指示事件是持久的。請參閱“關於事件 堅持“。

  • EV_ET

    指示事件應由邊緣觸發,如果 底層event_base後端支援邊緣觸發事件。 這會影響EV_READ和EV_WRITE的語義。

從 Libevent 2.0.1-alpha 開始,任何數量的事件都可能處於待處理狀態 同時在相同的條件下。例如,您可能有兩個 如果給定的 FD 準備好讀取,則事件將變為活動狀態。 其回撥的執行順序未定義。

這些標誌在 <event2/event.h> 中定義。從那時起,一切都存在 在 Libevent 1.0 之前,除了 EV_ET,它是在 Libevent 2.0.1-alpha 版本。

關於事件永續性

預設情況下,每當掛起事件變為活動狀態時(因為它的 fd 是 準備讀取或寫入,或者由於其超時過期),它變為 非待定權利之前 執行回撥。因此,如果要使事件掛起 同樣,您可以從內部再次呼叫 event_add() 回撥函式。

但是,如果在事件上設定了EV_PERSIST標誌,則該事件是持久的。這意味著該事件仍處於掛起狀態,即使其 回撥被啟用。如果您想從其內部使其非掛起 callback,你可以呼叫 event_del()。

每當事件的回撥時,持久事件的超時都會重置 執行。因此,如果您有一個帶有 flags EV_READ|EV_PERSIST 和 超時 5 秒後,事件將變為活動狀態:

  • 每當插座準備好讀取時。
  • 每當事件自上次事件以來已經過去了五秒鐘 積極。

建立事件作為其自己的回撥引數

通常,您可能希望建立一個事件,該事件將自身接收為 回撥引數。不能只將指向事件的指標作為引數傳遞 但是,event_new(),因為它還不存在。為了解決這個問題, 您可以使用 event_self_cbarg()。

介面

void *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 100
       times. */
    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 中引入的。

僅超時事件

為方便起見,有一組以 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 中。

構造訊號事件

Libevent 還可以監視 POSIX 樣式的訊號。要構造一個 處理程式,使用:

介面

#define evsignal_new(base, signum, cb, arg) \
    event_new(base, signum, EV_SIGNAL|EV_PERSIST, cb, arg)

這些論點與event_new一樣,只是我們提供了一個訊號 number 而不是檔案描述符。

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_bases---即使訊號不同---也只有一個 event_base將接收訊號。

kqueue 後端沒有此限制。

建立使用者觸發的事件

有時,建立可以啟用的事件和 當所有高優先順序事件都完成後再執行。任何種類 清理或垃圾收集就是這樣的事件。請參閱“活動 with priority“,用於解釋設定降低優先順序。

使用者觸發的事件可以建立為:

struct event *user = event_new(evbase, -1, 0, user_cb, myhandle);

請注意,不需要 event_add()。然後用以下方式發射

event_active(user, 0, 0);

第 3 個引數對於非訊號事件不顯著。

設定不帶堆分配的事件

出於效能等原因,有些人喜歡分配事件 作為更大結構的一部分。對於事件的每次使用,這 儲存它們:

  • 用於分配小物件的記憶體分配器開銷 堆。
  • 取消引用指向 struct 事件。
  • 如果 事件尚未在快取中。

使用此方法可能會破壞與其他二進位制檔案的相容性 Libevent 的版本,對於事件可能具有不同的大小 結構。

這些都是非常小的成本,對於大多數應用來說並不重要。 你應該堅持使用 event_new(),除非你知道 堆分配會造成顯著的效能損失 您的活動。使用 event_assign() 可能會導致難以診斷的錯誤 如果 Libevent 的未來版本使用更大的事件結構 比你正在構建的那個。

介面

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_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 庫告訴 在執行時,結構事件應該有多大:

介面

size_t event_get_struct_event_size(void);

此函式返回需要留出的位元組數 一個 struct 事件。和以前一樣,只有在以下情況下才應該使用此函式 您知道堆分配實際上是您的一個重大問題 程式,因為它會使您的程式碼更難讀寫。

請注意,event_get_struct_event_size() 將來可能會給你一個sizeof(struct event) 的值。如果發生這種情況,則意味著 struct 事件末尾的任何額外位元組都只是保留的填充位元組 供 Libevent 的未來版本使用。

這是與上面相同的示例,但不是依賴於大小 對於 event_struct.h 的 struct 事件,我們使用 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;
}

<event2/event.h> 中定義的 event_assign() 函式。它已經存在了 自 Libevent 2.0.1-alpha 以來。自 2.0.3-alpha 以來,它返回了一個 int; 以前,它返回 void。event_get_struct_event_size() 函式是在 Libevent 2.0.4-alpha 中引入的。事件結構 本身在 <event2/event_struct.h> 中定義。

使事件掛起和非掛起

一旦你構建了一個事件,它實際上不會做任何事情 直到您透過新增它使其處於掛起狀態。您可以透過以下方式執行此操作 event_add:

介面

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

對非掛起事件呼叫 event_add 會使其在其 配置的底座。該函式在成功時返回 0,在成功時返回 -1 失敗。如果 tv 為 NULL,則新增事件且不超時。 否則,tv 是超時的大小(以秒為單位),並且 微秒。

如果對掛起的事件呼叫 event_add(),它將 讓它保持掛起狀態,並使用提供的超時重新安排它。如果 事件已處於掛起狀態,您使用超時 NULL 重新新增它, event_add() 將不起作用。

注意 不要將電視設定為您希望超時的時間 跑。如果您在 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-阿爾法。

具有優先次序的事件

當多個事件同時觸發時,Libevent 不會 定義有關其回撥時間的任何順序 執行。您可以透過以下方式將某些事件定義為比其他事件更重要 使用優先順序。

如上一節所述,每個event_base都有一個或多個 與之關聯的優先順序值。在將事件新增到 event_base,但是初始化後,您可以設定其優先順序。

介面

int event_priority_set(struct event *event, int priority);

事件的優先順序是介於 0 和 event_base中的優先順序,減去 1。該函式返回 0 on 成功,失敗時為 -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 will
     happen before the read callback.  The read callback won't happen at
     all until the write callback is no longer active. */
}

如果未為事件設定優先順序,則預設值為 事件庫中的佇列數除以 2。

此函式在 <event2/event.h> 中宣告。它從那時起就存在了 Libevent 1.0 版本。

檢查事件狀態

有時,您想判斷是否已新增事件,並檢查 它指的是什麼。

介面

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 函式確定給定事件是否 待處理或活動。如果是,並且任何標誌EV_READ,EV_WRITE, EV_SIGNAL 和 EV_TIMEOUT 設定在 what 引數、函式中 返回事件當前處於掛起或活動狀態的所有標誌 上。如果提供了tv_out,並且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 中的新功能。

查詢當前正在執行的事件

出於除錯或其他目的,可以獲取指向當前 跑步事件。

介面

struct event *event_base_get_running_event(struct event_base *base);

請注意,此函式的行為僅在從以下位置呼叫時定義 在提供的event_base的迴圈中。從另一個執行緒呼叫它不是 支援,並可能導致未定義的行為。

此函式在 <event2/event.h> 中宣告。它是在 Libevent 中引入的 2.1.1-阿爾法。

配置一次性事件

如果您不需要多次新增事件,或者刪除一次事件 已新增,不一定要持久,可以使用 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被釋放時,這些事件被釋放,即使 他們尚未啟用,但仍要注意:如果有一些儲存空間 與其回撥引數相關聯,則不會釋放該儲存 除非你的程式已經做了一些事情來跟蹤和釋出它。

手動啟用事件

在極少數情況下,您可能希望使事件處於活動狀態,即使其 條件尚未觸發。

介面

void event_active(struct event *ev, int what, short ncalls);

此函式使事件 ev 變得活動,並帶有標誌 what(EV_READ、EV_WRITE 和 EV_TIMEOUT 的組合)。該事件確實如此 不需要以前處於掛起狀態,啟用它也不需要 使其處於待定狀態。

警告:在同一事件上遞迴呼叫 event_active() 可能會導致 資源枯竭。以下程式碼片段是操作示例 event_active可能使用不當。

壞例子:用 event_active() 做一個無限迴圈

struct event *ev;

static void cb(int sock, short which, void *arg) {
        /* Whoops: Calling event_active on the same event unconditionally
           from within its callback means that no other events might not get
           run! */

        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”永遠。

示例:使用計時器解決上述問題的替代方法

struct 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;
}

示例:使用 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;
}

此函式在 <event2/event.h> 中定義。它已經存在了 從 Libevent 0.3 開始。

最佳化常見超時

當前版本的 Libevent 使用二進位制堆演算法來跟蹤 掛起事件的超時。二進位制堆提供順序效能 O(lg n) 用於新增和刪除每個事件超時。如果出現以下情況,這是最佳的 您正在新增具有一組隨機分佈的超時值的事件, 但如果有大量事件具有相同的超時,則不會。

例如,假設您有一萬個事件,每個事件都應該 在新增 5 秒後觸發其超時。在某種情況下 像這樣,你可以透過使用 雙鏈佇列實現。

當然,您不希望在全部超時期間使用佇列 值,因為佇列僅對於常量超時值更快。如果 一些超時或多或少是隨機分佈的,然後新增 佇列的其中一個超時將花費 O(n) 時間,即 比二進位制堆差得多。

Libevent 允許您透過將一些超時放在佇列中來解決這個問題, 以及二進位制堆中的其他人。為此,您向 Libevent 請求 特殊的“通用超時”timeval,然後用於新增事件 有那個時間。如果您有非常多的事件 使用此最佳化的單個公共超時應該會有所改善 超時效能。

介面

const struct timeval *event_base_init_common_timeout(
    struct event_base *base, const struct timeval *duration);

此函式將event_base和持續時間作為其引數 要初始化的常見超時。它返回指向特殊 struct timeval,可用於指示事件應為 新增到 O(1) 佇列而不是 O(lg n) 堆中。這個特別的 TimeVal 可以在程式碼中自由複製或分配。它只會 使用您用來構建它的特定基礎。不要依賴 它的實際內容: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 to
       initialize_timeout */
    if (tv && tv->tv_sec == 10 && tv->tv_usec == 0)
        return event_add(ev, &ten_seconds);
    else
        return event_add(ev, tv);
}

與所有最佳化函式一樣,應避免使用 common_timeout功能,除非您非常確定它很重要 給你的。

此功能是在 Libevent 2.0.4-alpha 中引入的。

講述一個好的事件,而不是清除的記憶

Libevent 提供了可用於區分 透過將其設定為 0 清除的記憶體中的初始化事件 (例如,透過使用 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 開始就存在了。

過時的事件操作函式

Libevent 的 2.0 之前版本沒有 event_assign() 或 event_new()。相反,您有關聯事件的 event_set() 與“當前”基礎。如果您有多個鹼基,則需要 記得在之後呼叫 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 之前的版本使用“signal_”作為 event_set() 等的基於訊號的變體,而不是“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() 等。

代替 event_get_fd() 和 event_get_signal() 函式,較舊 Libevent 版本(2.0 之前)使用兩個宏,分別稱為 EVENT_FD() 和 EVENT_SIGNAL()。這些宏檢查了事件結構的內容 直接,從而阻止了版本之間的二進位制相容性;在 2.0 和 後來它們只是 event_get_fd() 和 event_get_signal() 的別名。

由於 2.0 之前的 Libevent 版本沒有 鎖定支援,呼叫任何 從執行緒外部更改事件相對於基的狀態 執行基地。這些包括 event_add()、event_del()、 event_active() 和 event_base_once()。

還有一個 event_once() 函式,它扮演著 event_base_once(),但使用了當前基數。

EV_PERSIST標誌之前沒有與超時進行合理的互操作 Libevent 2.0 版。而是在事件發生時重置超時 啟用後,EV_PERSIST標誌對超時沒有任何作用。

2.0 之前的 Libevent 版本不支援多個事件 使用相同的 fd 和相同的 READ/WRITE 同時插入。 換言之,一次只能有一個事件等待繼續閱讀 每個 FD,一次只能有一個事件等待寫入 每個 FD。

原文件地址:https://libevent.org/libevent-book/Ref4_event.html

相關文章