00. 目錄
聲明: 該博客來源於傳智播客C++學院相關培訓參考手冊
01. 事件概述
Libevents的基本操作單元是event
,每一個event
代表了一些條件的集合,這些條件包括:
- 文件描述符已經准備好讀或寫
- 文件描述符正在變為就緒,准備好讀或寫(僅限於邊沿觸發)
- 超時事件
- 信號發生
- 用戶觸發事件
所有事件具有相似的生命周期。調用libevent函數設置事件並且關聯到event_base之后,事件進入“已初始化(initialized)”狀態。此時可以將事件添加到event_base中,這使之進入“未決(pending)”狀態。在未決狀態下,如果觸發事件的條件發生(比如說,文件描述符的狀態改變,或者超時時間到達),則事件進入“激活(active)”狀態,(用戶提供的)事件回調函數將被執行。如果配置為“持久的(persistent)”,當執行回調時,事件將保持為未決狀態;否則,執行完回調后,事件不再是未決的。刪除操作可以讓未決事件成為非未決(已初始化)的;添加操作可以讓非未決事件再次成為未決的。
02. 創建事件
event_new函數
/**
Allocate and asssign a new event structure, ready to be added.
The function event_new() returns a new event that can be used in
future calls to event_add() and event_del(). The fd and events
arguments determine which conditions will trigger the event; the
callback and callback_arg arguments tell Libevent what to do when the
event becomes active.
If events contains one of EV_READ, EV_WRITE, or EV_READ|EV_WRITE, then
fd is a file descriptor or socket that should get monitored for
readiness to read, readiness to write, or readiness for either operation
(respectively). If events contains EV_SIGNAL, then fd is a signal
number to wait for. If events contains none of those flags, then the
event can be triggered only by a timeout or by manual activation with
event_active(): In this case, fd must be -1.
The EV_PERSIST flag can also be passed in the events argument: it makes
event_add() persistent until event_del() is called.
The EV_ET flag is compatible with EV_READ and EV_WRITE, and supported
only by certain backends. It tells Libevent to use edge-triggered
events.
The EV_TIMEOUT flag has no effect here.
It is okay to have multiple events all listening on the same fds; but
they must either all be edge-triggered, or all not be edge triggerd.
When the event becomes active, the event loop will run the provided
callbuck function, with three arguments. The first will be the provided
fd value. The second will be a bitfield of the events that triggered:
EV_READ, EV_WRITE, or EV_SIGNAL. Here the EV_TIMEOUT flag indicates
that a timeout occurred, and EV_ET indicates that an edge-triggered
event occurred. The third event will be the callback_arg pointer that
you provide.
@param base the event base to which the event should be attached.
@param fd the file descriptor or signal to be monitored, or -1.
@param events desired events to monitor: bitfield of EV_READ, EV_WRITE,
EV_SIGNAL, EV_PERSIST, EV_ET.
@param callback callback function to be invoked when the event occurs
@param callback_arg an argument to be passed to the callback function
@return a newly allocated struct event that must later be freed with
event_free().
@see event_free(), event_add(), event_del(), event_assign()
*/
struct event *event_new(struct event_base *base, evutil_socket_t fd, short what, event_callback_fn cb, void *arg);
功能:
分配和構造一個用於base的新的事件。
參數:
base event_base指針
fd 被監視的文件描述符或者信號
what 標志
cb 事件處理函數
arg 回調函數的參數
返回值:
成功 struct event指針
失敗 NULL
event_new()試圖分配和構造一個用於base的新的事件。what參數是上述標志的集合。
如果fd非負,則它是將被觀察其讀寫事件的文件。
事件被激活時,libevent將調用cb函數,cb函數傳遞這些參數:文件描述符fd,表示所有被觸發事件的位字段what,以及構造事件時的arg參數。
發生內部錯誤,或者傳入無效參數時,event_new()將返回NULL。
所有新創建的事件都處於已初始化和非未決狀態,調用event_add()可以使其成為未決的。
要釋放事件,調用event_free()。對未決或者激活狀態的事件調用event_free()是安全的:在釋放事件之前,函數將會使事件成為非激活和非未決的。
回調函數的類型
/**
A callback function for an event.
It receives three arguments:
@param fd An fd or signal
@param events One or more EV_* flags
@param arg A user-supplied argument.
@see event_new()
*/
typedef void (*event_callback_fn)(evutil_socket_t, short, void *);
event_free函數
/**
Deallocate a struct event * returned by event_new().
If the event is pending or active, first make it non-pending and
non-active.
*/
void event_free(struct event *base);
功能:
釋放事件。
參數:
base struct event指針
返回值:
無
官方參考代碼
#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類型首次在2.0.4-alpha版本中作為typedef出現。
03. 事件的標志
/**
* @name event flags
*
* Flags to pass to event_new(), event_assign(), event_pending(), and
* anything else with an argument of the form "short events"
*/
/**@{*/
/** 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.
*/
#define EV_PERSIST 0x10
/** Select edge-triggered behavior, if supported by the backend. */
#define EV_ET 0x20
-
EV_TIMEOUT
這個標志表示某超時時間流逝后事件成為激活的。構造事件的時候,EV_TIMEOUT標志是被忽略的:可以在添加事件的時候設置超時,也可以不設置。超時發生時,回調函數的what參數將帶有這個標志。
-
EV_READ
表示指定的文件描述符已經就緒,可以讀取的時候,事件將成為激活的。
-
EV_WRITE
表示指定的文件描述符已經就緒,可以寫入的時候,事件將成為激活的。
-
EV_SIGNAL
用於實現信號檢測,請看下面的“構造信號事件”節。
-
EV_PERSIST
表示事件是“持久的”,請看下面的“關於事件持久性”節。
-
EV_ET
表示如果底層的event_base后端支持邊沿觸發事件,則事件應該是邊沿觸發的。這個標志影響EV_READ和EV_WRITE的語義。
從2.0.1-alpha版本開始,可以有任意多個事件因為同樣的條件而未決。比如說,可以有兩個事件因為某個給定的fd已經就緒,可以讀取而成為激活的。這種情況下,多個事件回調被執行的次序是不確定的。
這些標志定義在<event2/event.h>中。除了EV_ET在2.0.1-alpha版本中引入外,所有標志從1.0版本開始就存在了。
04. 事件持久性
默認情況下,每當未決事件成為激活的(因為fd已經准備好讀取或者寫入,或者因為超時),事件將在其回調被執行前成為非未決的。如果想讓事件再次成為未決的,可以在回調函數中再次對其調用event_add()。
然而,如果設置了EV_PERSIST標志,事件就是持久的。這意味着即使其回調被激活,事件還是會保持為未決狀態。如果想在回調中讓事件成為非未決的,可以對其調用event_del()。
每次執行事件回調的時候,持久事件的超時值會被復位。因此,如果具有EV_READ|EV_PERSIST標志,以及5秒的超時值,則事件將在以下情況下成為激活的:
-
套接字已經准備好被讀取的時候
-
從最后一次成為激活的開始,已經逝去5秒
05. 超時事件
為使用方便,libevent提供了一些以evtimer_開頭的宏,用於替代*event_**調用來操作純超時事件。使用這些宏能改進代碼的清晰性。
/**
@name evtimer_* macros
Aliases for working with one-shot timer events */
/**@{*/
#define evtimer_assign(ev, b, cb, arg) \
event_assign((ev), (b), -1, 0, (cb), (arg))
#define evtimer_new(b, cb, arg) event_new((b), -1, 0, (cb), (arg))
#define evtimer_add(ev, tv) event_add((ev), (tv))
#define evtimer_del(ev) event_del(ev)
#define evtimer_pending(ev, tv) event_pending((ev), EV_TIMEOUT, (tv))
#define evtimer_initialized(ev) event_initialized(ev)
/**@}*/
除了evtimer_new()首次出現在2.0.1-alpha版本中之外,這些宏從0.6版本就存在了。
06. 信號事件
libevent也可以監測POSIX風格的信號。要構造信號處理器。
#define evsignal_new(b, x, cb, arg) \
event_new((b), (x), 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風格信號處理器中使用的函數。
警告:不要在信號事件上設置超時,這可能是不被支持的。[待修正:真是這樣的嗎?]
libevent也提供了一組方便使用的宏用於處理信號事件:
/**
@name evsignal_* macros
Aliases for working with signal events
*/
/**@{*/
#define evsignal_add(ev, tv) event_add((ev), (tv))
#define evsignal_assign(ev, b, x, cb, arg) \
event_assign((ev), (b), (x), EV_SIGNAL|EV_PERSIST, cb, (arg))
#define evsignal_new(b, x, cb, arg) \
event_new((b), (x), EV_SIGNAL|EV_PERSIST, (cb), (arg))
#define evsignal_del(ev) event_del(ev)
#define evsignal_pending(ev, tv) event_pending((ev), EV_SIGNAL, (tv))
#define evsignal_initialized(ev) event_initialized(ev)
/**@}*/
evsignal_**宏從2.0.1-alpha版本開始存在。先前版本中這些宏叫做signal_add()、signal_del*()等等。
關於信號的警告
在當前版本的libevent和大多數后端中,每個進程任何時刻只能有一個event_base可以監聽信號。如果同時向兩個event_base添加信號事件,即使是不同的信號,也只有一個event_base可以取得信號。
kqueue后端沒有這個限制。
07. 設置不使用堆分配的事件
出於性能考慮或者其他原因,有時需要將事件作為一個大結構體的一部分。對於每個事件的使用,這可以節省:
-
內存分配器在堆上分配小對象的開銷.
-
對event結構體指針取值的時間開銷.
-
如果事件不在緩存中,因為可能的額外緩存丟失而導致的時間開銷.
對於大多數應用來說,這些開銷是非常小的。所以,你應該堅持使用event_new()函數除非確定在堆上分配事件導致了嚴重的性能問題。如果將來版本中的event結構體更大,不使用event_new()可能會導致難以診斷的錯誤。
不在堆上分配event具有破壞與其他版本libevent二進制兼容性的風險:其他版本中的event結構體大小可能不同。
/**
Prepare a new, already-allocated event structure to be added.
The function event_assign() prepares the event structure ev to be used
in future calls to event_add() and event_del(). Unlike event_new(), it
doesn't allocate memory itself: it requires that you have already
allocated a struct event, probably on the heap. Doing this will
typically make your code depend on the size of the event structure, and
thereby create incompatibility with future versions of Libevent.
The easiest way to avoid this problem is just to use event_new() and
event_free() instead.
A slightly harder way to future-proof your code is to use
event_get_struct_event_size() to determine the required size of an event
at runtime.
Note that it is NOT safe to call this function on an event that is
active or pending. Doing so WILL corrupt internal data structures in
Libevent, and lead to strange, hard-to-diagnose bugs. You _can_ use
event_assign to change an existing event, but only if it is not active
or pending!
The arguments for this function, and the behavior of the events that it
makes, are as for event_new().
@param ev an event struct to be modified
@param base the event base to which ev should be attached.
@param fd the file descriptor to be monitored
@param events desired events to monitor; can be EV_READ and/or EV_WRITE
@param callback callback function to be invoked when the event occurs
@param callback_arg an argument to be passed to the callback function
@return 0 if success, or -1 on invalid arguments.
@see event_new(), event_add(), event_del(), event_base_once(),
event_get_struct_event_size()
*/
int event_assign(struct event *, struct event_base *, evutil_socket_t, short, event_callback_fn, void *);
除了event參數必須指向一個未初始化的事件之外,event_assign()的參數與event_new()的參數相同。成功時函數返回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->read_event, base, fd, EV_WRITE|EV_PERSIST, writecb, p);
return p;
}
也可以用event_assign()初始化棧上分配的,或者靜態分配的事件。
警告
不要對已經在event_base中未決的事件調用event_assign(),這可能會導致難以診斷的錯誤。如果已經初始化和成為未決的,調用event_assign()之前需要調用event_del()。
libevent提供了方便的宏將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在運行時應該有多大:
/**
Return the size of struct event that the Libevent library was compiled
with.
This will be NO GREATER than sizeof(struct event) if you're running with
the same version of Libevent that your application was built with, but
otherwise might not.
Note that it might be SMALLER than sizeof(struct event) if some future
version of Libevent adds extra padding to the end of struct event.
We might do this to help ensure ABI-compatibility between different
versions of Libevent.
*/
size_t event_get_struct_event_size(void);
這個函數返回需要為event結構體保留的字節數。再次強調,只有在確信堆分配是一個嚴重的性能問題時才應該使用這個函數,因為這個函數讓代碼難以閱讀和編寫。
注意,將來版本的event_get_struct_event_size()的返回值可能比sizeof(struct event)小,這表示event結構體末尾的額外字節僅僅是保留用於將來版本libevent的填充字節。
下面這個例子跟上面的那個相同,但是不依賴於event_struct.h中的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>中,從2.0.1-alpha版本開始就存在了。從2.0.3-alpha版本開始,函數返回int,在這之前函數返回void。event_get_struct_event_size()在2.0.4-alpha版本中引入。event結構體定義在<event2/event_struct.h>中。
08. 事件的未決和非未決
構造事件之后,在將其添加到event_base之前實際上是不能對其做任何操作的。使用event_add()將事件添加到event_base。
event_add函數
/**
Add an event to the set of pending events.
The function event_add() schedules the execution of the ev event when the
event specified in event_assign()/event_new() occurs, or when the time
specified in timeout has elapesed. If atimeout is NULL, no timeout
occurs and the function will only be
called if a matching event occurs. The event in the
ev argument must be already initialized by event_assign() or event_new()
and may not be used
in calls to event_assign() until it is no longer pending.
If the event in the ev argument already has a scheduled timeout, calling
event_add() replaces the old timeout with the new one, or clears the old
timeout if the timeout argument is NULL.
@param ev an event struct initialized via event_set()
@param timeout the maximum amount of time to wait for the event, or NULL
to wait forever
@return 0 if successful, or -1 if an error occurred
@see event_del(), event_assign(), event_new()
*/
int event_add(struct event *ev, const struct timeval *timeout);
在非未決的事件上調用event_add()將使其在配置的event_base中成為未決的。成功時函數返回0,失敗時返回-1。
如果tv為NULL,添加的事件不會超時。否則,tv以秒和微秒指定超時值。
如果對已經未決的事件調用event_add(),事件將保持未決狀態,並在指定的超時時間被重新調度。
注意:不要設置tv為希望超時事件執行的時間。如果在2010年1月1日設置“tv->tv_sec=time(NULL)+10;”,超時事件將會等待40年,而不是10秒
event_del函數
/**
Remove an event from the set of monitored events.
The function event_del() will cancel the event in the argument ev. If the
event has already executed or has never been added the call will have no
effect.
@param ev an event struct to be removed from the working set
@return 0 if successful, or -1 if an error occurred
@see event_add()
*/
int event_del(struct event *);
對已經初始化的事件調用event_del()將使其成為非未決和非激活的。如果事件不是未決的或者激活的,調用將沒有效果。成功時函數返回0,失敗時返回-1。
注意:如果在事件激活后,在回調被執行前刪除事件,回調將不會執行。
這些函數定義在<event2/event.h>中,從0.1版本就存在了。
09. 事件的優先級
多個事件同時觸發時,libevent沒有定義各個回調的執行次序。可以使用優先級來定義某些事件比其他事件更重要。
在前一章討論過,每個event_base有與之相關的一個或者多個優先級。在初始化事件之后,但是在添加到event_base之前,可以為其設置優先級。
event_priority_set函數
/**
Assign a priority to an event.
@param ev an event struct
@param priority the new priority to be assigned
@return 0 if successful, or -1 if an error occurred
@see event_priority_init()
*/
int event_priority_set(struct event *ev, 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 will
happen before the read callback. The read callback won't happen at
all until the write callback is no longer active. */
}
如果不為事件設置優先級,則默認的優先級將會是event_base的優先級數目除以2。
這個函數聲明在<event2/event.h>中,從1.0版本就存在了。
10. 檢查事件狀態
有時候需要了解事件是否已經添加,檢查事件代表什么。
/**
Checks if a specific event is pending or scheduled.
@param ev an event struct previously passed to event_add()
@param events the requested event type; any of EV_TIMEOUT|EV_READ|
EV_WRITE|EV_SIGNAL
@param tv if this field is not NULL, and the event has a timeout,
this field is set to hold the time at which the timeout will
expire.
@return true if the event is pending on any of the events in 'what', (that
is to say, it has been added), or 0 if the event is not added.
*/
int event_pending(const struct event *ev, short events, struct timeval *tv);
/**
Get the signal number assigned to a signal event
*/
#define event_get_signal(ev) ((int)event_get_fd(ev))
/**
Get the socket or signal assigned to an event, or -1 if the event has
no socket.
*/
evutil_socket_t event_get_fd(const struct event *ev);
/**
Get the event_base associated with an event.
*/
struct event_base *event_get_base(const struct event *ev);
/**
Return the events (EV_READ, EV_WRITE, etc) assigned to an event.
*/
short event_get_events(const struct event *ev);
/**
Return the callback assigned to an event.
*/
event_callback_fn event_get_callback(const struct event *ev);
/**
Return the callback argument assigned to an event.
*/
void *event_get_callback_arg(const struct event *ev);
/**
Extract _all_ of arguments given to construct a given event. The
event_base is copied into *base_out, the fd is copied into *fd_out, and so
on.
If any of the "_out" arguments is NULL, it will be ignored.
*/
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()函數確定給定的事件是否是未決的或者激活的。如果是,而且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_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()函數從0.1版就存在了。2.0.1-alpha版引入了event_get_fd()和event_get_signal()。2.0.2-alpha引入了event_get_base()。其他的函數在2.0.4-alpha版中引入。
11. 一次觸發事件
如果不需要多次添加一個事件,或者要在添加后立即刪除事件,而事件又不需要是持久的,則可以使用event_base_once()。
/**
Schedule a one-time event
The function event_base_once() is similar to event_set(). However, it
schedules a callback to be called exactly once, and does not require the
caller to prepare an event structure.
Note that in Libevent 2.0 and earlier, if the event is never triggered,
the internal memory used to hold it will never be freed. This may be
fixed in a later version of Libevent.
@param base an event_base
@param fd a file descriptor to monitor, or -1 for no fd.
@param events event(s) to monitor; can be any of EV_READ |
EV_WRITE, or EV_TIMEOUT
@param callback callback function to be invoked when the event occurs
@param arg an argument to be passed to the callback function
@param timeout the maximum amount of time to wait for the event. NULL
makes an EV_READ/EV_WRITE event make forever; NULL makes an
EV_TIMEOUT event succees immediately.
@return 0 if successful, or -1 if an error occurred
*/
int event_base_once(struct event_base *, evutil_socket_t, short, event_callback_fn, void *, const struct timeval *);
除了不支持EV_SIGNAL或者EV_PERSIST之外,這個函數的接口與event_new()相同。安排的事件將以默認的優先級加入到event_base並執行。回調被執行后,libevent內部將會釋放event結構。成功時函數返回0,失敗時返回-1。
不能刪除或者手動激活使用event_base_once()插入的事件:如果希望能夠取消事件,應該使用event_new()或者event_assign()。
12. 手動激活事件
極少數情況下,需要在事件的條件沒有觸發的時候讓事件成為激活的。
/**
Make an event active.
You can use this function on a pending or a non-pending event to make it
active, so that its callback will be run by event_base_dispatch() or
event_base_loop().
One common use in multithreaded programs is to wake the thread running
event_base_loop() from another thread.
@param ev an event to make active.
@param res a set of flags to pass to the event's callback.
@param ncalls an obsolete argument: this is ignored.
**/
void event_active(struct event *ev, int res, short ncalls);
這個函數讓事件ev帶有標志what(EV_READ、EV_WRITE和EV_TIMEOUT的組合)成為激活的。事件不需要已經處於未決狀態,激活事件也不會讓它成為未決的。
這個函數定義在<event2/event.h>中,從0.3版本就存在了
13. 優化公用超時
當前版本的libevent使用二進制堆算法跟蹤未決事件的超時值,這讓添加和刪除事件超時值具有O(logN)性能。對於隨機分布的超時值集合,這是優化的,但對於大量具有相同超時值的事件集合,則不是。
比如說,假定有10000個事件,每個都需要在添加后5秒觸發超時事件。這種情況下,使用雙鏈隊列實現才可以取得O(1)性能。
自然地,不希望為所有超時值使用隊列,因為隊列僅對常量超時值更快。如果超時值或多或少地隨機分布,則向隊列添加超時值的性能將是O(n),這顯然比使用二進制堆糟糕得多。
libevent通過放置一些超時值到隊列中,另一些到二進制堆中來解決這個問題。要使用這個機制,需要向libevent請求一個“公用超時(common timeout)”值,然后使用它來添加事件。如果有大量具有單個公用超時值的事件,使用這個優化應該可以改進超時處理性能。
/**
Prepare an event_base to use a large number of timeouts with the same
duration.
Libevent's default scheduling algorithm is optimized for having a large
number of timeouts with their durations more or less randomly
distributed. But if you have a large number of timeouts that all have
the same duration (for example, if you have a large number of
connections that all have a 10-second timeout), then you can improve
Libevent's performance by telling Libevent about it.
To do this, call this function with the common duration. It will return a
pointer to a different, opaque timeout value. (Don't depend on its actual
contents!) When you use this timeout value in event_add(), Libevent will
schedule the event more efficiently.
(This optimization probably will not be worthwhile until you have thousands
or tens of thousands of events with the same timeout.)
*/
const struct timeval *event_base_init_common_timeout(struct event_base *base,
const struct timeval *duration);
這個函數需要event_base和要初始化的公用超時值作為參數。函數返回一個指向特別的timeval結構體的指針,可以使用這個指針指示事件應該被添加到O(1)隊列,而不是O(logN)堆。可以在代碼中自由地復制這個特別的timeval或者進行賦值,但它僅對用於構造它的特定event_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 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);
}
與所有優化函數一樣,除非確信適合使用,應該避免使用公用超時功能。
這個函數由2.0.4-alpha版本引入。
14. 從已清除的內存識別事件
libevent的提供了函數,你可以用它來識別出內存已被設置為0(比如說,通過calloc()分配的,或者使用memset()或者bzero()清除了的)的初始化的事件。
/**
Test if an event structure might be initialized.
The event_initialized() function can be used to check if an event has been
initialized.
Warning: This function is only useful for distinguishing a a zeroed-out
piece of memory from an initialized event, it can easily be confused by
uninitialized memory. Thus, it should ONLY be used to distinguish an
initialized event from zero.
@param ev an event structure to be tested
@return 1 if the structure might be initialized, or 0 if it has not been
initialized
*/
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()函數從0.3版本就存在了。
15. 廢棄的事件操作函數
2.0版本之前的libevent沒有event_assign()或者event_new()。替代的是將事件關聯到“當前”event_base的event_set()。如果有多個event_base,需要記得調用event_base_set()來確定事件確實是關聯到當前使用的event_base的。
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_base之外,event_set()跟event_assign()是相似的。event_base_set()用於修改事件所關聯到的event_base。
event_set()具有一些用於更方便地處理定時器和信號的變體:evtimer_set()大致對應evtimer_assign();evsignal_set()大致對應evsignal_assign()。
2.0版本之前的libevent使用“signal_”作為用於信號的event_set()等函數變體的前綴,而不是“evsignal_”(也就是說,有signal_set()、signal_add()、signal_del()、signal_pending()和signal_initialized())。遠古版本(0.6版之前)的libevent使用“timeout_”而不是“evtimer_”。因此,做代碼考古(code archeology)(注:這個翻譯似乎不正確,是否有更專業的術語?比如說,“代碼復審”)時可能會看到timeout_add()、timeout_del()、timeout_initialized()、timeout_set()和timeout_pending()等等。
較老版本(2.0版之前)的libevent用宏EVENT_FD()和EVENT_SIGNAL()代表現在的event_get_fd()和event_get_signal()函數。這兩個宏直接檢查event結構體的內容,所以會妨礙不同版本之間的二進制兼容性。在2.0以及后續版本中,這兩個宏僅僅是event_get_fd()和event_get_signal()的別名。
因為2.0之前的版本不支持鎖,所以在運行event_base的線程之外的任何線程調用修改事件狀態的函數都是不安全的。這些函數包括event_add()、event_del()、event_active()和event_base_once()。
有一個event_once()與event_base_once()相似,只是用於當前event_base。
2.0版本之前EV_PERSIST標志不能正確地操作超時。標志不會在事件激活時復位超時值,而是沒有任何操作。
2.0之前的版本不支持同時添加多個帶有相同fd和READ/WRITE標志的事件。也就是說,在每個fd上,某時刻只能有一個事件等待讀取,也只能有一個事件等待寫入。
04. 參考
相關書籍: http://www.wangafu.net/~nickm/libevent-book/Ref2_eventbase.html
官方參考網站: https://www.monkey.org/~provos/libevent/doxygen-2.0.1/index.html