1、入门 1.1、概述Libevent是一个用于开发可扩展性网络服务器的基于事件驱动(event-driven)模型的网络库。Libevent有几个显著的亮点: (1)事件驱动(event-driven),高性能; (2)轻量级,专注于网络,不如 ACE 那么臃肿庞大; (3)源代码相当精炼、易读; (4)跨平台,支持 Windows、Linux、*BSD和 Mac Os; (5)支持多种 I/O多路复用技术, epoll、poll、dev/poll、select 和kqueue 等; (6)支持 I/O,定时器和信号等事件; (7)注册事件优先级; Libevent 已经被广泛的应用,作为底层的网络库;比如 memcached、 Vomi t、 Nylon、 Netchat等等。
1.2、一个简单示例 代码
这是一个简单的基于libevent的定时器程序,运行结果:
用libevent编程非常简单,只需要调用event_init初始化环境,然后调用event_add注册相应的事件,接着调用event_dispatch等待并处理相应的事件即可。
代码
1 //event.h 2 struct event { 3 TAILQ_ENTRY (event) ev_next; //已注册事件链表 4 TAILQ_ENTRY (event) ev_active_next;//就绪事件链表 5 TAILQ_ENTRY (event) ev_signal_next; //signal链表 6 unsigned int min_heap_idx; /* for managing timeouts,事件在堆中的下标 */ 7 8 struct event_base *ev_base; 9 10 int ev_fd; //对于I/O事件,是绑定的文件描述符;对于signal事件,是绑定的信号 11 short ev_events; //event关注的事件类型 12 short ev_ncalls; //事件就绪执行时,调用 ev_callback 的次数 13 short *ev_pncalls; /* Allows deletes in callback */ 14 15 struct timeval ev_timeout; //timout事件的超时值 16 17 int ev_pri; /* smaller numbers are higher priority,优先级 */ 18 19 void (*ev_callback)(int, short, void *arg); //回调函数 20 void *ev_arg; //回调函数的参数 21 22 int ev_res; /* result passed to event callback */ 23 int ev_flags; //event的状态 24 }; 25 Libevent通过event对象将I/O事件、信号事件和定时器事件封装,从而统一处理,这也是libevent的精妙所有。
代码
1 //evenet_internal.h 2 struct event_base { 3 const struct eventop *evsel; //底层具体I/O demultiplex操作函数集 4 void *evbase; 5 int event_count; /* counts number of total events,总的事件数量 */ 6 int event_count_active; /* counts number of active events,就绪事件数量 */ 7 8 int event_gotterm; /* Set to terminate loop */ 9 int event_break; /* Set to terminate loop immediately */ 10 11 /* active event management */ 12 //就绪事件链表数组 13 struct event_list **activequeues; 14 int nactivequeues;//就绪事件队列个数 15 16 /* signal handling info */ 17 struct evsignal_info sig; //用于管理信号 18 19 struct event_list eventqueue; //注册事件队列 20 struct timeval event_tv; 21 22 struct min_heap timeheap; //管理定时器的小根堆 23 struct timeval tv_cache; //记录时间缓存 24 }; (1)evsel:libevent支持Linux、Windows等多种平台,也支持epoll、poll、select、kqueue等多种I/O多路复用模型。如果把event_init、event_add看成高层抽象的统一事件操作接口,则evsel为这些函数在底层具体的I/O demultiplex的对应的操作函数集。eventop为函数指针的集合:
代码
1 struct eventop { 2 const char *name; 3 void *(*init)(struct event_base *); 4 int (*add)(void *, struct event *); 5 int (*del)(void *, struct event *); 6 int (*dispatch)(struct event_base *, void *, struct timeval *); 7 void (*dealloc)(struct event_base *, void *); 8 /* set if we need to reinitialize the event base */ 9 int need_reinit; 10 }; 11 在初始化函数event_base_new中,libevent将evsel指向全局数组eventops的具体元素: 代码
2.3、主要函数
代码
1 struct event_base * 2 event_base_new(void) 3 { 4 5 //初始化小根堆 6 min_heap_ctor(&base->timeheap); 7 8 //初始化注册事件队列 9 TAILQ_INIT(&base->eventqueue); 10 11 for (i = 0; eventops[i] && !base->evbase; i++) { 12 //I/O demultiplex机制实例 13 base->evsel = eventops[i]; 14 15 //初始化I/O demultiplex实例(参见win32_init) 16 base->evbase = base->evsel->init(base); 17 } 18 19 //分配1个就绪事件队列 20 event_base_priority_init(base, 1); 21 22 } 2.3.2、event_add(注册事件)
代码
/*设置event对象 **ev:事件对象 **fd:事件对应的文件描述符或信号,对于定时器设为-1 **events:事件类型,比如 EV_READ,EV_PERSIST, EV_WRITE, EV_SIGNAL **callback:事件的回调函数 **arg:回调函数参数 */ void event_set(struct event *ev, int fd, short events, void (*callback)(int, short, void *), void *arg) 在将事件注册事件处理框架之前,应该先调用event_set对事件进行相关设置。
3、事件处理框架主循环 代码
3.3、timeout_next 代码
3.4、dispatch函数 代码
4、Timer事件
代码
1 //evsignal.h 2 struct evsignal_info { 3 struct event ev_signal; //内部socket读事件 4 int ev_signal_pair[2]; //对应socket pair的两个socket描述符 5 int ev_signal_added; //内部socket读事件是否已经加入注册链表 6 volatile sig_atomic_t evsignal_caught; //是否有信号发生 7 //信号事件链表数组,evsigevents[signo]表示注册信号signo的事件 8 struct event_list evsigevents[NSIG]; 9 //具体记录每个信号触发的次数,evsigcaught[signo]是记录信号 signo被触发的次数 10 sig_atomic_t evsigcaught[NSIG]; 11 12 //sh_old记录了原来的 signal 处理函数指针,当信号 signo 注册的 event 被清空时,需要重新设置其处理函数 13 #ifdef HAVE_SIGACTION 14 struct sigaction **sh_old; 15 #else 16 ev_sighandler_t **sh_old; 17 #endif 18 int sh_old_max; 19 }; 20 5.3、主要函数
代码
1 int 2 evsignal_init(struct event_base *base) 3 { 4 evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, base->sig.ev_signal_pair); 5 base->sig.sh_old = NULL; 6 base->sig.sh_old_max = 0; 7 8 //事件发生次数设为0 9 base->sig.evsignal_caught = 0; 10 memset(&base->sig.evsigcaught, 0, sizeof(sig_atomic_t)*NSIG); 11 /* initialize the queues for all events */ 12 for (i = 0; i < NSIG; ++i) 13 TAILQ_INIT(&base->sig.evsigevents[i]); 14 15 evutil_make_socket_nonblocking(base->sig.ev_signal_pair[0]); //写端 16 17 //设置内部读事件 18 event_set(&base->sig.ev_signal, base->sig.ev_signal_pair[1], 19 EV_READ | EV_PERSIST, evsignal_cb, &base->sig.ev_signal); //读端 20 base->sig.ev_signal.ev_base = base; 21 22 //sig.ev_signal == EV_READ | EV_PERSIST | EVLIST_INTERNAL 23 base->sig.ev_signal.ev_flags |= EVLIST_INTERNAL; 24 } 该函数的关键在于这里会设置libevent用于管理信号事件的内部读事件evsignal_info的ev_signal,并将该事件对应的文件描述符设为socket pair的读端。该函数由I/O multiplex的init函数调用。注:这里只是设置,而并没有注册socket pair的读事件(见下一节)。 代码
这里有两个地方需要注意,一是调用_evsignal_set_handler设置外部注册信号事件对应的信号的信号处理函数evsignal_handler:
代码
1 static void 2 evsignal_handler(int sig) 3 { 4 int save_errno = errno; 5 6 //设置信号事件的发生次数 7 evsignal_base->sig.evsigcaught[sig]++; 8 evsignal_base->sig.evsignal_caught = 1; 9 10 #ifndef HAVE_SIGACTION 11 signal(sig, evsignal_handler); 12 #endif 13 14 /* Wake up our notification mechanism */ 15 //向socket pair的写端写数据 16 send(evsignal_base->sig.ev_signal_pair[0], "a", 1, 0); 17 errno = save_errno; 18 } 当用户注册信号事件对应的信号发生时,OS转到evsignal_handler函数,从而设置sig.evsignal_caught,并向socket pair的写端发送数据。
代码
1 static int 2 epoll_dispatch(struct event_base *base, void *arg, struct timeval *tv) 3 { 4 struct epollop *epollop = arg; 5 struct epoll_event *events = epollop->events; 6 struct evepoll *evep; 7 int i, res, timeout = -1; 8 9 if (tv != NULL) 10 timeout = tv->tv_sec * 1000 + (tv->tv_usec + 999) / 1000; 11 12 if (timeout > MAX_EPOLL_TIMEOUT_MSEC) { 13 /* Linux kernels can wait forever if the timeout is too big; 14 * see comment on MAX_EPOLL_TIMEOUT_MSEC. */ 15 timeout = MAX_EPOLL_TIMEOUT_MSEC; 16 } 17 18 //等待I/O事件 19 res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout); 20 21 if (res == -1) { 22 if (errno != EINTR) { 23 event_warn("epoll_wait"); 24 return (-1); 25 } 26 //epoll_wait被信号中断 27 evsignal_process(base); 28 return (0); 29 } else if (base->sig.evsignal_caught) {//发生了信号事件 30 //处理信号事件 31 evsignal_process(base); 32 } 33 //… 34 } epoll_dispatch函数调用epoll_wait函数等待I/O发生。然后,如果有信号事件发生,则调用evsignal_process处理信号事件,evsignal_process的逻辑比较简单,它只是将事件从注册事件链表转移到就绪事件链表。
代码
1 static void 2 evsignal_cb(int fd, short what, void *arg) 3 { 4 static char signals[1]; 5 #ifdef WIN32 6 SSIZE_T n; 7 #else 8 ssize_t n; 9 #endif 10 //接收数据 11 n = recv(fd, signals, sizeof(signals), 0); 12 if (n == -1) 13 event_err(1, "%s: read", __func__); 14 } 6、libevent的应用 |
|