libevent學習文檔(三)working with event

Events have similar lifecycles. Once you call a Libevent function to set up an event and associate it with an event base, it becomes initialized. At this point, you can add, which makes it pending in the base. When the event is pending, if the conditions that would trigger an event occur (e.g., its file descriptor changes state or its timeout expires), the event becomesactive, and its (user-provided) callback function is run. If the event is configured persistent, it remains pending. If it is not persistent, it stops being pending when its callback runs. You can make a pending event non-pending by deleting it, and you can add a non-pending event to make it pending again.後端

 

事件有類似的生命週期,一旦你調用libevent 函數設置event和event_base關聯後,event被初始化了。add這個事件會使它阻塞,當事件阻塞時,有觸發事件的條件出現,事件會激活,回調函數會被調用。安全

若是事件被設置爲永久,它保持阻塞。若是不是永久,當事件的回調函數調用的時候就不阻塞了。能夠經過刪除一個事件使它由阻塞變爲非阻塞。經過添加使它由非阻塞變爲阻塞。app

 

#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_free釋放。 參數base 表示event綁定在那個event_base上, fd表示event關聯的描述符, what表示事件的類型,是一個bitfield, 上面那些宏按位或,socket

cb是事件回調函數,事件就緒後能夠觸發。ide

event_free釋放event事件。函數

All new events are initialized and non-pending. To make an event pending, call event_add() (documented below).oop

To deallocate an event, call event_free(). It is safe to call event_free() on an event that is pending or active: doing so makes the event non-pending and inactive before deallocating it.學習

全部新建立的事件都是初始化的,而且非阻塞,調用event_add可讓一個事件變爲阻塞。調用event_free釋放event, 在事件阻塞或者激活狀態下調用event_free是安全的,這個函數會在釋放event以前將this

事件變爲非阻塞而且非激活狀態。spa

 

EV_TIMEOUT

This flag indicates an event that becomes active after a timeout elapses.

EV_READ

This flag indicates an event that becomes active when the provided file descriptor is ready for reading.

EV_WRITE

This flag indicates an event that becomes active when the provided file descriptor is ready for writing.

EV_SIGNAL

Used to implement signal detection. See "Constructing signal events" below.

EV_PERSIST

Indicates that the event is persistent. See "About Event Persistence" below.

EV_ET

Indicates that the event should be edge-triggered, if the underlying event_base backend supports edge-triggered events.

 

EV_TIMEOUT:表示過一段事件後event變爲active。

EV_READ:當文件描述可讀的時候變爲就緒。

EV_WRITE:當文件描述符可寫的時候變爲就緒。

EV_SIGNAL:信號事件的標記

EV_PERSIST:永久事件,下面會介紹。

EV_ET:若是後端支持邊緣觸發事件,那麼事件是邊緣觸發的。

 

About Event Persistence

By default, whenever a pending event becomes active (because its fd is ready to read or write, or because its timeout expires), it becomes non-pending right before its callback is executed. Thus, if you want to make the event pending again, you can call event_add() on it again from inside the callback function.

If the EV_PERSIST flag is set on an event, however, the event is persistent. This means that event remains pending even when its callback is activated. If you want to make it non-pending from within its callback, you can call event_del() on it.

The timeout on a persistent event resets whenever the event’s callback runs. Thus, if you have an event with flags EV_READ|EV_PERSIST and a timeout of five seconds, the event will become active:

  • Whenever the socket is ready for reading.

  • Whenever five seconds have passed since the event last became active.

默認狀況下,當一個阻塞事件變爲active時,(讀事件可讀,寫事件可寫,超時間到期等),在事件對應的回調函數調用前該事件就會變爲非阻塞的。所以,若是想要將事件變爲阻塞,須要在事件的回調函數裏調用event_add()

若是設置了EV_PERSIST 標記位, 那麼事件就變味永久的,這意味着事件在回調函數觸發時任然保持pending,若是你想要在回調函數調用後該事件變爲非阻塞,須要調用event_del()。

當事件回調函數調用後超時會被重置,所以,若是事件帶有EV_READ|EV_PERSIST標記,而且有5秒的超時值,以下狀況事件會變爲active:

1當socket可讀時

2從上次變爲active以後過了5秒後事件會變爲active。

當事件的回調函數須要用到本身做爲參數時候,須要將參數傳遞爲

void *event_self_cbarg();

代碼例子
#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 100
       times. */
    ev = event_new(base, -1, EV_PERSIST, cb_func, event_self_cbarg());
    event_add(ev, &one_sec);
    event_base_dispatch(base);
}

For performance and other reasons, some people like to allocate events as a part of a larger structure. For each use of the event, this saves them:

  • The memory allocator overhead for allocating a small object on the heap.

  • The time overhead for dereferencing the pointer to the struct event.

  • The time overhead from a possible additional cache miss if the event is not already in the cache.

有時候開闢event做爲一個較大結構體的一部分,能夠節省在堆上開闢小對象的內存,也能夠節省間接引用事件指針的事件和額外內存流失的處理。

文檔的做者並不提倡用event_assign這個函數,推薦使用event_new,並且對於一些問題event_assign並很差調試

下面是使用event_assign的例子

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

Never call event_assign() on an event that is already pending in an event base. Doing so can lead to extremely hard-to-diagnose errors. If the event is already initialized and pending, call event_del() on it before you call event_assign() on it again.

 

event在event_base中阻塞時不要調用event_assign(),不然會形成很難查找分析的問題,若是一個事件已經初始化而且pending了,須要調用event_del()刪除他,而後再次調用event_assign()。

evtimer_assign和
evsignal_assign分別是定時器和信號的註冊函數。


因爲調用event_assign()可能會形成版本兼容的問題,調用以下函數,能夠獲取到event運行時大小。
size_t event_get_struct_event_size(void);


This function returns the number of bytes you need to set aside for a struct event. As before, you should only be using this function if you know that heap-allocation is actually a significant problem in your program, since it can make your code much harder to read and write.

這個函數返回event結構體旁邊的偏移位置的字節數,只有在你以爲堆開闢確實是一個難題的時候才採用這個方法。由於這麼作會是你的代碼更難去讀和寫。


事件的添加:
int event_add(struct event *ev, const struct timeval *tv);

Calling event_add on a non-pending event makes it pending in its configured base. The function returns 0 on success, and -1 on failure. If tv is NULL, the event is added with no timeout. Otherwise, tv is the size of the timeout in seconds and microseconds.

If you call event_add() on an event that is already pending, it will leave it pending, and reschedule it with the provided timeout. If the event is already pending, and you re-add it with the timeout NULL, event_add() will have no effect.

調用event_add會讓一個event變得pending,返回0表示成功,-1表示失敗。若是tv設置爲NULL,表示沒有超時檢測。不然,tv表示超時的秒數和毫秒。
若是在一個pending的event上調用add,會使它pengding,而且根據超時值從新計時。

事件的刪除:
int event_del(struct event *ev);

 


事件刪除函數,會將一個阻塞或者激活的事件變爲非阻塞和非激活的,若是事件是非阻塞的或者非激活的,調用這個函數並無什麼影響。一樣,返回0表示成功,-1表示失敗。


優先級設置:
int event_priority_set(struct event *event, int priority);

 


每一個event_base有priorities,event能夠設置從0到這個值之間的一個數,0表示成功,-1表示失敗。
優先級高的先處理,優先級低的後處理。
若是不設置優先級,默認值爲event_base中隊列大小除以2

#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. */
}

 


除此以外,libevent還爲咱們提供了一些接口訪問當前event_base 和event屬性。

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 獲取當前event對應的what屬性事件是否pending或者被激活,If it is, and any of the flags EV_READ, EV_WRITE, EV_SIGNAL, and EV_TIMEOUT are set in the whatargument, the function returns all of the flags that the event is currently pending or active on

任類型均可以設置到what參數裏,這個函數返回當前pending或者激活狀態的標記按位或。event_get_signal和event_get_fd返回event關聯的信號id和文件描述符id, event_get_base返回event綁定的event_base,
event_get_events返回event監聽的事件集合,event_get_callback返回event的回調函數,以及
event_get_callback_arg返回回調函數參數,
event_get_priority返回event的優先級
event_get_assignment這個函數返回event全部綁定的信息到對應的指針域,若是形參爲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;
}

 


還有個能只調用一次事件的建立接口

int event_base_once(struct event_base *, evutil_socket_t, short,
  void (*)(evutil_socket_t, short, void *), void *, const struct timeval *);

 



這個函數不支持EV_SIGNAL 和 EV_PERSIST ,這個事件也不支持手動刪除和激活。當該事件對應的回調函數觸發後,該事件會自動從event_base中移除,而且libevent會析構掉該event。


激活event的接口

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

 

 這個函數能夠根據what(EV_READ, EV_WRITE,EV_TIMER等)將event設置爲active,調用函數前,event是否爲pengding並不影響,
而且激活它而且變爲非阻塞狀態。
在同一個事件遞歸的調用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;
}

 


有兩種改進的方式,一種是採用定時器,另外一種是採用libevent提供的event_config_set_max_dispatch_interval

定時器的就是隻調用一次loop,以後的回調函數cb會反覆調用,由於cb內部發現event不是阻塞狀態了,就要將event刪除後再加入,loop內部檢測到新的event,繼續調用cb,反覆調用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設置了dispatch的時間間隔,每一個一段時間纔派發就緒時間,這樣就不會致使遞歸形成的資源耗盡了。

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

 



今天的學習就到這裏,這是個人公衆號


相關文章
相關標籤/搜索