根据系统提供的事件多路分发机制执行事件循环,堆已注册的就绪事件,调用注册事件的回调函数来处理事件
Libevent 的事件主循环主要是通过event_base_loop()函数完成的,其主要操作入下面的流程图,event_base_loop所做的就是持续执行下面的循环
Libevent将信号事件和定时器事件都集成到了系统的IO的demultiplex机制中,首先将Timer事件的最小超时时间来设置系统IO的timeout时间:当系统IO返回时,在激活所有就绪的Timer事件就可以。这样就能将Timer事件完美融入系统的IO机制中。这是Reactor和Proactor模式的处理的经典方法 堆(小根堆)插入和删除的事件复杂度为LogN,N为堆中元素,二获取最小Key值为O(1)
signal是异步事件的经典实例,如果当signal事件发生时,并不立即调用event的callback函数,而是设法通知系统IO机制,让其返回。然后在统一和IO事件以及Timer一起处理。 问题核心,当signal发生是如何通知系统的IO多路复用机制,比如pipe
总结:介绍了libevent是如何处理就绪的IO事件,定时器和信号事件
主要分析signal集成到事件主循环的框架中
可以实现上述在同一个文件描述符中进行读写的功能。该系统调用能创建一对已连接的UNIX族socket。在Linux中,完全可以把这一对socket当成pipe返回的文件描述符一样使用,唯一的区别就是这一对文件描述符中的任何一个都可读和可写,libevent使用的就是socket pair
int socketpair(int d, int type, int protocol, int sv[2]);socketpair创建逻辑
listener = socket(AF_INET, type, 0); if (listener < 0) return -1; memset(&listen_addr, 0, sizeof(listen_addr)); listen_addr.sin_family = AF_INET; listen_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); listen_addr.sin_port = 0; /* kernel chooses port. */ if (bind(listener, (struct sockaddr *) &listen_addr, sizeof (listen_addr)) == -1) goto tidy_up_and_fail; if (listen(listener, 1) == -1) goto tidy_up_and_fail; connector = socket(AF_INET, type, 0); if (connector < 0) goto tidy_up_and_fail; memset(&connect_addr, 0, sizeof(connect_addr)); /* We want to find out the port number to connect to. */ size = sizeof(connect_addr); if (getsockname(listener, (struct sockaddr *) &connect_addr, &size) == -1) goto tidy_up_and_fail; if (size != sizeof (connect_addr)) goto abort_tidy_up_and_fail; if (connect(connector, (struct sockaddr *) &connect_addr, sizeof(connect_addr)) == -1) goto tidy_up_and_fail; size = sizeof(listen_addr); acceptor = accept(listener, (struct sockaddr *) &listen_addr, &size); if (acceptor < 0) goto tidy_up_and_fail; if (size != sizeof(listen_addr)) goto abort_tidy_up_and_fail; /* Now check we are talking to ourself by matching port and host on the two sockets. */ if (getsockname(connector, (struct sockaddr *) &connect_addr, &size) == -1) goto tidy_up_and_fail; if (size != sizeof (connect_addr) || listen_addr.sin_family != connect_addr.sin_family || listen_addr.sin_addr.s_addr != connect_addr.sin_addr.s_addr || listen_addr.sin_port != connect_addr.sin_port) goto abort_tidy_up_and_fail; evutil_closesocket(listener); fd[0] = connector; fd[1] = acceptor; return 0;socketPair创建好了,可是libevent的主循环还是不知道signal是否发生了:为socket pair的读socket在libevent的event_base实例上注册一个persist的读事件 前面提到饿了Libevent会在事件主循环检查标记,来确定是否有触发的signal,如果有标记就处理。已epoll为例子,在epoll_dispatch()中
res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout); EVBASE_ACQUIRE_LOCK(base, th_base_lock); if (res == -1) { if (errno != EINTR) { event_warn("epoll_wait"); return (-1); } evsignal_process(base);//处理信号事件 return (0); }else if(base->sig.evsignal_caught) evsignal_process(base);//处理信号事件注册signal事件是evsignal_add(struct event *ev)函数完成的,libevent对所有信号注册同一个处理函数evsignal_handler(),该函数将下一段介绍,注册过程如下:
static void __cdecl evsig_handler(int sig) { int save_errno = errno; #ifdef _WIN32 int socket_errno = EVUTIL_SOCKET_ERROR(); #endif ev_uint8_t msg; if (evsig_base == NULL) { event_warnx( "%s: received signal %d, but have no base configured", __func__, sig); return; } //记录信号sig的触发次数,并且设置event触发标记 //evsignal_base->sig.evsigcaught[sig]++; //evsignal_base->sig.evsig_caught = 1; #ifndef EVENT__HAVE_SIGACTION signal(sig, evsig_handler); //重新注册信号 #endif /* Wake up our notification mechanism */ msg = sig; #ifdef _WIN32 send(evsig_base_fd, (char*)&msg, 1, 0); #else { int r = write(evsig_base_fd, (char*)&msg, 1); (void)r; /* Suppress 'unused return value' and 'unused var' */ } #endif errno = save_errno; #ifdef _WIN32 EVUTIL_SET_SOCKET_ERROR(socket_errno); #endif }总结:主要介绍了libevent对signal事件的具体处理框架,包括事件的注册,删除和socket pair通知机制,以及是如何将signal事件集成到事件主循环中
相较于signal事件来说,Timer与IO的集成较为直观和简单
由于IO多路复用都允许一个最大等待时间(最大超时时间)timeout,即使事件没有发生都能够保证timeout事件内放回。因此根据小根堆的最小超时时间来设置超时时间:系统IO放回后,在激活所有的就绪的Timer时间
具体代码在源文件event.c的event_base_loop()中
if (!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK)) { //根据Timer事件计算evsel->dispatch的最大等待时间 timeout_next(base, &tv_p); } else { /* * if we have active events, we just poll new events * without waiting. */ evutil_timerclear(&tv); } //调用select() or epoll_wait()等待IO事件 res = evsel->dispatch(base, tv_p); //处理超时事件,将超时事件插入到激活链表中 timeout_process(base);Libevent使用堆来管理Timer事件,其key值就是事件超时时间,源代码位于min_heap.h中
Libevent的核心是事件驱动和同步非阻塞。为了达到这一目标。必须使用系统提供的IO多路复用