在编写网络应用程序中,我们经常判断连接请求是否有数据收等待接收或可以发送数据,通常我们可以采用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 */
/**//* signal handling info */
 const struct eventop *evsigsel;
const struct eventop *evsigsel;
 void *evsigbase;
void *evsigbase;
 struct evsig_info sig;
struct evsig_info sig;

struct eventop结构定义如下:

 struct eventop
struct eventop  {
{
 const char *name;
    const char *name;
 void *(*init)(struct event_base *);
    void *(*init)(struct event_base *);
 int (*add)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
    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 (*del)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
 int (*dispatch)(struct event_base *, struct timeval *);
    int (*dispatch)(struct event_base *, struct timeval *);
 void (*dealloc)(struct event_base *);
    void (*dealloc)(struct event_base *);
 int need_reinit;
    int need_reinit;
 enum event_method_feature features;
    enum event_method_feature features;
 size_t fdinfo_len;
    size_t fdinfo_len;
 };
};

struct eventop结构封装了各I/O模型的处理方式,在主程序中,我们不关心采用的是select,poll等,只需要调用dispatch方法,将一些处于就绪态的events添加到active_event list中。
 
结构struct evsig_info定义如下:

 struct evsig_info
struct evsig_info  {
{
 struct event ev_signal;
struct event ev_signal;
 evutil_socket_t ev_signal_pair[2];
evutil_socket_t ev_signal_pair[2];
 int ev_signal_added;
int ev_signal_added;
 volatile sig_atomic_t evsig_caught;
volatile sig_atomic_t evsig_caught;
 sig_atomic_t evsigcaught[NSIG];
sig_atomic_t evsigcaught[NSIG];
 #ifdef _EVENT_HAVE_SIGACTION
#ifdef _EVENT_HAVE_SIGACTION
 struct sigaction **sh_old;
struct sigaction **sh_old;
 #else
#else
 ev_sighandler_t **sh_old;
ev_sighandler_t **sh_old;
 #endif
#endif
 int sh_old_max;
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],
event_assign(&base->sig.ev_signal, base, base->sig.ev_signal_pair[1],

 EV_READ | EV_PERSIST, evsig_cb, &base->sig.ev_signal);
              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)
static void evsig_handler(int sig)


 {
{
 int save_errno = errno;
    int save_errno = errno;
 #ifdef WIN32
#ifdef WIN32
 int socket_errno = EVUTIL_SOCKET_ERROR();
    int socket_errno = EVUTIL_SOCKET_ERROR();
 #endif
#endif


 if (evsig_base == NULL)
    if (evsig_base == NULL)  {
{
 event_warn(
        event_warn(
 "%s: received signal %d, but have no base configured",
            "%s: received signal %d, but have no base configured",
 __func__, sig);
            __func__, sig);
 return;
        return;
 }
    }
 evsig_base->sig.evsigcaught[sig]++;
    evsig_base->sig.evsigcaught[sig]++;
 evsig_base->sig.evsig_caught = 1;
    evsig_base->sig.evsig_caught = 1;
 #ifndef _EVENT_HAVE_SIGACTION
#ifndef _EVENT_HAVE_SIGACTION
 signal(sig, evsig_handler);
    signal(sig, evsig_handler);
 #endif
#endif

 /**//* Wake up our notification mechanism */
    /**//* Wake up our notification mechanism */
 send(evsig_base->sig.ev_signal_pair[0], "a", 1, 0);
    send(evsig_base->sig.ev_signal_pair[0], "a", 1, 0);
 errno = save_errno;
    errno = save_errno;
 #ifdef WIN32
#ifdef WIN32
 EVUTIL_SET_SOCKET_ERROR(socket_errno);
    EVUTIL_SET_SOCKET_ERROR(socket_errno);
 #endif
#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)
static void signal_cb(int fd, short event, void *arg)


 {
{
 struct event *signal = arg;
    struct event *signal = arg;
 printf("%s: got signal %d\n", __func__, EVENT_SIGNAL(signal));
    printf("%s: got signal %d\n", __func__, EVENT_SIGNAL(signal));
 if (called >= 2)
    if (called >= 2)
 event_del(signal);
        event_del(signal);
 called++;
        called++;
 }
}
 int main (int argc, char **argv)
int main (int argc, char **argv)


 {
{
 struct event signal_int;
    struct event signal_int;

 /**//* Initalize the event library */
     /**//* Initalize the event library */
 event_init();
    event_init();

 /**//* Initalize one event */
    /**//* Initalize one event */
 event_set(&signal_int, SIGINT, EV_SIGNAL|EV_PERSIST, signal_cb,
    event_set(&signal_int, SIGINT, EV_SIGNAL|EV_PERSIST, signal_cb,
 &signal_int);
        &signal_int);
 event_add(&signal_int, NULL);
    event_add(&signal_int, NULL);
 event_dispatch();
    event_dispatch();
 return (0);
    return (0);
 }
}

在该例子中,通过创建一个event,该event的文件描述符为信号量sigint.并添加到event_list中。然后调用event_dispatch启动循环。
注意:struct event_base中的sig->ev_sig event在event_init里事先已初始好,并添加到event_list当中了。