libevent源码学习3---事件event

libevent源码学习3—事件event

libevent 的基本操作单元是事件。每个事件代表一组条件的集合, 这些条件包括:

  • 文件描述符已经就绪, 可以读取或者写入
  • 文件描述符变为就绪状态,可以读取或者写入(仅对于边沿触发 IO)
  • 超时事件
  • 发生某信号
  • 用户触发事件
    所有事件具有相似的生命周期。调用 libevent 函数设置事件并且关联到 event_base 之后, 事件进入“已初始化(initialized)”状态。此时可以将事件添加到 event_base 中,这使之进入“未决(pending)” 状态。在未决状态下, 如果触发事件的条件发生(比如说,文件描述符的状态改变, 或者超时时间到达 ),则事件进入“激活(active)”状态,(用户提供的)事件回调函数将被执行。如果配置为“持久的(persistent)”, 事件将保持为未决状态。否则, 执行完回调后, 事件不再是未决的。删除操作可以让未决事件成为非未决(已初始化)的 ; 添加操作可以让非未决事件再次成为未决的。

ps:未决: 简单来说就是一个已经产生的信号,但是还没有传递给任何进程,此时该信号的状态就称为未决状态。

1.创建事件

1.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           0x20
typedef void (*event_callback_fn)(evutil_socket_t, short, void *);
struct event *event_new(struct event_base *base, evutil_socket_t fd,
    					short events, event_callback_fn cb, void *arg);
void event_free(struct event *event);

event_new()试图分配和构造一个用于 base 的新的事件。events参数是上述标志的集合。如果 fd 非负, 则它是将被观察其读写事件的文件。事件被激活时, libevent 将调用 cb 函数, 传递这些参数: 文件描述符 fd, 表示所有被触发事件的位字段 , 以及构造事件时的 arg 参数。发生内部错误, 或者传入无效参数时, event_new()将返回 NULL。

所有新创建的事件都处于已初始化和非未决状态 ,调用 event_add()可以使其成为未决的。

要释放事件, 调用 event_free()。对未决或者激活状态的事件调用 event_free()是安全的: 在释放事件之前, 函数将会使事件成为非激活和非未决的。

event_new()实现:

struct event *event_new(struct event_base *base, evutil_socket_t fd, short events, 
						void (*cb)(evutil_socket_t, short, void *), void *arg)
{
    struct event *ev;
    ev = mm_malloc(sizeof(struct event));
    if (ev == NULL)
        return (NULL);
    if (event_assign(ev, base, fd, events, cb, arg) < 0)
    {
        mm_free(ev);
        return (NULL);
    }

    return (ev);
}

event_add()实现:

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

    if (EVUTIL_FAILURE_CHECK(!ev->ev_base))
    {
        event_warnx("%s: event has no event_base set.", __func__);
        return -1;
    }

    EVBASE_ACQUIRE_LOCK(ev->ev_base, th_base_lock);

    res = event_add_nolock_(ev, tv, 0);

    EVBASE_RELEASE_LOCK(ev->ev_base, th_base_lock);

    return (res);
}

event结构体定义:

struct event {
	struct event_callback ev_evcallback;

	/* for managing timeouts */
	union {
		TAILQ_ENTRY(event) ev_next_with_common_timeout;
		int min_heap_idx;
	} ev_timeout_pos;
	evutil_socket_t ev_fd;

	struct event_base *ev_base;

	union {
		/* used for io events */
		struct {
			LIST_ENTRY (event) ev_io_next;
			struct timeval ev_timeout;
		} ev_io;

		/* used by signal events */
		struct {
			LIST_ENTRY (event) ev_signal_next;
			short ev_ncalls;
			/* Allows deletes in callback */
			short *ev_pncalls;
		} ev_signal;
	} ev_;

	short ev_events;
	short ev_res;		/* result passed to event callback */
	struct timeval ev_timeout;
};

1.2 事件标志

  • EV_TIMEOUT:这个标志表示某超时时间流逝后事件成为激活的。构造事件的时候, EV_TIMEOUT 标志是被忽略的: 可以在添加事件的时候设置超时, 也可以不设置。超时发生时,回调函数的 what 参数将带有这个标志。
  • EV_READ:表示指定的文件描述符已经就绪, 可以读取的时候, 事件将成为激活的。
  • EV_WRITE:表示指定的文件描述符已经就绪, 可以写入的时候, 事件将成为激活的。
  • EV_SIGNAL:用于实现信号检测
  • EV_PERSIST:表示事件是“持久的”
  • EV_ET:表示如果底层的 event_base 后端支持边沿触发事件(默认水平),则事件应该是边沿触发的。这个标志影响 EV_READ 和 EV_WRITE 的语义。

1.3 关于事件持久性

默认情况下,每当未决事件成为激活的(fd 已经准备好读取或者写入,或者因为超时),事件将在其回调被执行前成为非未决的。如果想让事件再次成为未决的,可以在回调函数中再次对其调用 event_add()。

然而,如果设置了 EV_PERSIST 标志,事件就是持久的。这意味着即使其回调被激活,事件还是会保持为未决状态。 如果想在回调中让事件成为非未决的,可以对其调用 event_del()。

每次执行事件回调的时候,持久事件的超时值会被复位。因此,如果具有 EV_READ|EV_PERSIST 标志,以及5秒(你设置的值)的超时值,则事件将在以下情况下成为激活的:

  • 套接字已经准备好被读取的时候
  • 从最后一次成为激活的开始,已经过去5秒

1.4 信号事件

libevent 也可以监测 POSIX 风格的信号。要构造信号处理器

#define evsignal_new(b, x, cb, arg)				\
	event_new((b), (x), EV_SIGNAL|EV_PERSIST, (cb), (arg))

libevent 也提供了一组方便使用的宏用于处理信号事件:

#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_del(ev)		event_del(ev)
#define evsignal_pending(ev, tv)	event_pending((ev), EV_SIGNAL, (tv))
#define evsignal_initialized(ev)	event_initialized(ev)

2.事件的未决和非未决

构造事件之后,在将其添加到 event_base 之前实际上是不能对其做任何操作的。使用event_add()将事件添加到 event_base

2.1 设置未决事件

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

在非未决的事件上调用 event_add()将使其在配置的 event_base 中成为未决的。成功时函数返回0, 失败时返回-1。

如果 tv 为 NULL, 添加的事件不会超时。否则, tv 以秒和微秒指定超时值。

如果对已经未决的事件调用 event_add(), 事件将保持未决状态, 并在指定的超时时间被重新调度。

2.2 设置非未决事件

int event_del(struct event *ev);

对已经初始化的事件调用 event_del()将使其成为非未决和非激活的。如果事件不是未决的或者激活的, 调用将没有效果。成功时函数返回 0, 失败时返回-1。

注意: 如果在事件激活后, 其回调被执行前删除事件, 回调将不会执行。

3.事件的优先级

int event_priority_set(struct event *ev, int pri)
{
    event_debug_assert_is_setup_(ev);

    if (ev->ev_flags & EVLIST_ACTIVE)
        return (-1);
    if (pri < 0 || pri >= ev->ev_base->nactivequeues)
        return (-1);

    ev->ev_pri = pri;

    return (0);
}

注意优先级的值的范围在第7行源码。相关数据结构释义:

/* Active event management. */
/** An array of nactivequeues queues for active 			
 event_callbacks (ones that have triggered, and whose callbacks need to be called). 
 Low priority numbers are more important, and stall higher ones.
*/
struct evcallback_list *activequeues;
/** The length of the activequeues array */
int nactivequeues;

4.检查事件状态

有时候需要了解事件是否已经添加,检查事件代表什么。libevent中有很多函数来获取不同的信息。

/**
  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);

event_pending()函数确定给定的事件是否是未决的或者激活的。如果是,·而且 events 参数设置了 EV_READ、EV_WRITE、EV_SIGNAL 或者 EV_TIMEOUT 等标志,则函数会返回事件当前为之未决或者激活的所有标志 。

/**
   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_get_assignment()复制所有为事件分配的字段到提供的指针中。任何为 NULL 的参数会被忽略。

当然还有很多获取状态的函数,详情去源码event.c中查看。

5.一次触发事件

如果不需要多次添加一个事件,或者要在添加后立即删除事件,而事件又不需要是持久的 , 则可以使用 event_base_once()

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

除了不支持 EV_SIGNAL 或者 EV_PERSIST 之外,这个函数的接口与 event_new()相同。

6.手动激活事件

极少数情况下,需要在事件的条件没有触发的时候让事件成为激活的。

/* 
	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);

7.事件状态之间的转换

libevent源码学习3---事件event_第1张图片

参考《libevent深入浅出》、libevent官方文档。

你可能感兴趣的:(Libevent,libevent)