在了解libevent实现如何实现侦听signal事件以及调用其回调函数前,需要明确如何创建一个signal事件:
(一)signal事件创建方法:
(1)struct event * event_new(struct event_base * base, evutil_socket_t fd, short event, event_callback_fn callback, void *callback_args);
可以通过调用上面的函数创建一个新的event,新的event可以通过event_add()或者event_del()加入侦听队列中或删除,下面是这个函数参数介绍
1、base 这个时间将要关联的event_base,每一个event同一时刻只能关联一个event_base
2、fd 如果监听一个套接口或文件描述符,这fd表示一个socket或者文件描述符字,如果是一个signal,则fd表示此signal的信号值,fd为-1,那
么此时间可以表示超时事件,在加入监听队列后,当且仅当超时或者调用event_active()才能进入激活队列,等待调用其注册的回调函数
3、event 表示希望监听发生事件的类型,事件类型包括:EV_READ, EV_WRITE, EV_SIGNAL, EV_TIMEOUT, EV_PERSIST , EV_ET,意义如
如同其名字,其中EV_ET(edge_trigger)需要多路复用机制支持,当指定EV_PERSIST时,在事件发生后,并不将事件从侦听队列移除而是继续
侦听(对于指定了timeout的事件,在事件发生后timeout被重置为0?????)
4、callback 为此事件注册的回调函数,在事件发生并加入激活队列后将被调用,注意回调函数的声明方式:
typedef void (*event_callback_fn)(evutil_socket_t, short, void *); 回调函数中接受三个参数,第一个对应fd,第二个对应event,第三个对应
event_new 的最后一个参数callback_args
5、callback_args 提供给回调函数的参数
(2)还可一通过 evsignal_new(b, x, cb, arg),这是一个宏定义,实际上也是调用event_new(),其原型如下:
#define evsignal_new(b, x, cb, arg) event_new((b), (x), EV_SIGNAL|EV_PERSIST, (cb), (arg))
销毁一个通过event_new创建的event:
void event_free(struct event *),调用此函数时,如果传递的参数event已经在侦听队列,激活队列的,则先从队列中删除,在释放其空间
创建好一个新的event后,就可以将其加入监听队列,等待事件发生,并被调度,方法如下:
int event_add(struct event *ev, const struct timeval *timeout);
参数event就是加入的event,参数timeout可以为空,表示只有在希望发生的事件发生是才会被激活,如果参数timeout不为空,且先前已经设置过
timeout,先前的timeout被替代为新的timeout
(二)signal事件在libevent处理方式
signal事件要想同统一到libevent事件机制中去,并不是一件很自然的就能完成的事,不想timer事件那样可以直接利用现有的io复用机制就能轻松完成(例如:select和epoll中 设置timeout),signal是典型的异步事件,当signal发生时需要调用为其注册的回调函数,但是libevent中用来实现io复用的方式(select,kqueue,epoll等,windows不了解),不能直接侦听信号的发生。libevent采用另一种方式:当信号发生时,并不立即调用为其注册的回调函数,而是通知系统的IO机制,然后和IO时间以及timer事件一起处理。
signal事件集成具体实现:
(1)socket pair
前面曾经提到signal发生时,想法通知系统的IO机制,做法就是通过socketpair实现,socketpair分为读端以及写端(事实上两端可同时读写,但在系统中用不着
同时读写),系统通过为读端注册一个EV_PERSIST|EV_READ的事件来侦听读端,当信号来临时,向socketpair写端写如数据,则系统就知道有信号来临,接着
会根据信号的类型调用其注册的回调函数,具体细节会在后面讲到。
(2)(1)中说道信号发生时想socketpair写端写入数据,其实现方式是:当我们向系统中注册一个signal事件时,系统会为其选择一个统一的信号处理函数:
static void evsig_handler(int sig),这个信号处理函数所作的事情很简单,向socketpair写端写入信号值,随后系统就能检测到socketpair读端可读,
知道有信号发生
(3)socketpair读端知道有信号发生后,就会调用系统为socketpair读端注册的回调函数:static void evsig_cb(evutil_socket_t fd, short what, void *arg)
这个函数所作的工作是:读入信号值,并将信号发生次数加一,信号发生次数通过数组 :int ncaught[NSIG];实现,NSIG是信号种类数,数组下表即为信号值,
数组元素值为信号发生次数。接着调用函数:evmap_signal_active将此信号事件加入激活队列,传递给此函数的参数包括信号值以及信号发生次数。