在编写网络应用程序中,我们经常判断连接请求是否有数据收等待接收或可以发送数据,通常我们可以采用select或poll去判断一个fd_set中是否有数据可读或可写的文件描述符,如果有数据可读,创建相应的线程去读取数据等等。
同时,我们可能要注册信号处理函数,以实现对某个信号进行处理。在linux下通常做法是通过调用signal或sigaction函数来注册信号处理服务函数。然而这种做法就是我们没有办法传送一个context给我们信号处理函数。在libevent给我们带来了一个完美的解决方案,那就是将对signal的处理转换成与文件描述符一样的处理方案,采用select,poll,epoll,kquene等I/O模型进行处理。
在分析其中的技巧之前,我们先来看下libevent里几个重要的数据结构:struct event_base、struct evsig_info、struct eventop。
下面是struct event_base的部分成员:
/**//* signal handling info */
const struct eventop *evsigsel;
void *evsigbase;
struct evsig_info sig;
struct eventop结构定义如下:
struct eventop {
const char *name;
void *(*init)(struct event_base *);
int (*add)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
int (*del)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
int (*dispatch)(struct event_base *, struct timeval *);
void (*dealloc)(struct event_base *);
int need_reinit;
enum event_method_feature features;
size_t fdinfo_len;
};
struct eventop结构封装了各I/O模型的处理方式,在主程序中,我们不关心采用的是select,poll等,只需要调用dispatch方法,将一些处于就绪态的events添加到active_event list中。
结构struct evsig_info定义如下:
struct evsig_info {
struct event ev_signal;
evutil_socket_t ev_signal_pair[2];
int ev_signal_added;
volatile sig_atomic_t evsig_caught;
sig_atomic_t evsigcaught[NSIG];
#ifdef _EVENT_HAVE_SIGACTION
struct sigaction **sh_old;
#else
ev_sighandler_t **sh_old;
#endif
int sh_old_max;
};
下面我们将重点分析struct evsig_info结构体。
ev_signal成员将所有的信号集看成一个event,它对应的文件描述符是ev_signal_pair[1],在文件signal.c中初始如下:
//base就是current_base全局变量
event_assign(&base->sig.ev_signal, base, base->sig.ev_signal_pair[1],
EV_READ | EV_PERSIST, evsig_cb, &base->sig.ev_signal);
struct evsig_info中的sig.ev_signal_pair[2]通过unix socket 域连接起来,对于每个要添加处理函数的signal,我们都给它注册同一个信号处理函数,实现如下:
static void evsig_handler(int sig)
{
int save_errno = errno;
#ifdef WIN32
int socket_errno = EVUTIL_SOCKET_ERROR();
#endif
if (evsig_base == NULL) {
event_warn(
"%s: received signal %d, but have no base configured",
__func__, sig);
return;
}
evsig_base->sig.evsigcaught[sig]++;
evsig_base->sig.evsig_caught = 1;
#ifndef _EVENT_HAVE_SIGACTION
signal(sig, evsig_handler);
#endif
/**//* Wake up our notification mechanism */
send(evsig_base->sig.ev_signal_pair[0], "a", 1, 0);
errno = save_errno;
#ifdef WIN32
EVUTIL_SET_SOCKET_ERROR(socket_errno);
#endif
在该函数中,我们可以看到,它首先对struct evsig_info中sig_evsigcaught[sig]进行加一(通过这个数,后续程序知道哪个信号已经被触发了)。接着去写sig_ev_signal_pair[0],在之前我们讲过sig_ev_signal_pair[0,1]是一对unix域套接字的两端,因此,这里写sig_ev_signal_pair[0],那么sig_ev_signal_pair[1]读就处于active状态。在dispatch中,将evsig_info->ev_signal添加入active_event list中,然后调用ev_signal的call_back函数。该call_back函数仅仅是去读取套接字,使它仍处于dis-active状态。
整个过程下来,我们就获取了哪些信号已经触发,接下来我们只需要轮回event_list,查找包含active signal的event,调用该event的回调函数即可。
下面是一个使用libevent去处理响应信号的例子。
static void signal_cb(int fd, short event, void *arg)
{
struct event *signal = arg;
printf("%s: got signal %d\n", __func__, EVENT_SIGNAL(signal));
if (called >= 2)
event_del(signal);
called++;
}
int main (int argc, char **argv)
{
struct event signal_int;
/**//* Initalize the event library */
event_init();
/**//* Initalize one event */
event_set(&signal_int, SIGINT, EV_SIGNAL|EV_PERSIST, signal_cb,
&signal_int);
event_add(&signal_int, NULL);
event_dispatch();
return (0);
}
在该例子中,通过创建一个event,该event的文件描述符为信号量sigint.并添加到event_list中。然后调用event_dispatch启动循环。
注意:struct event_base中的sig->ev_sig event在event_init里事先已初始好,并添加到event_list当中了。