Libevent源码解读(三)

事件主循环

根据系统提供的事件多路分发机制执行事件循环,堆已注册的就绪事件,调用注册事件的回调函数来处理事件

事件处理主循环

Libevent 的事件主循环主要是通过event_base_loop()函数完成的,其主要操作入下面的流程图,event_base_loop所做的就是持续执行下面的循环

在这里插入图片描述

IO和Timer 事件的统一

Libevent将信号事件和定时器事件都集成到了系统的IO的demultiplex机制中,首先将Timer事件的最小超时时间来设置系统IO的timeout时间:当系统IO返回时,在激活所有就绪的Timer事件就可以。这样就能将Timer事件完美融入系统的IO机制中。这是Reactor和Proactor模式的处理的经典方法
堆(小根堆)插入和删除的事件复杂度为LogN,N为堆中元素,二获取最小Key值为O(1)

IO和signal事件的统一

signal是异步事件的经典实例,如果当signal事件发生时,并不立即调用event的callback函数,而是设法通知系统IO机制,让其返回。然后在统一和IO事件以及Timer一起处理。
问题核心,当signal发生是如何通知系统的IO多路复用机制,比如pipe

总结:介绍了libevent是如何处理就绪的IO事件,定时器和信号事件

集成信号处理

主要分析signal集成到事件主循环的框架中

集成策略 — socket pair

可以实现上述在同一个文件描述符中进行读写的功能。该系统调用能创建一对已连接的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;

集成到事件主循环----通知event_base

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);//处理信号事件

在这里插入图片描述

evsignal_info结构体

/* Data structure for the default signal-handling implementation in signal.c
 */
struct evsig_info {
	/* Event watching ev_signal_pair[1] */
	struct event ev_signal;
	//为socketpair的读socket向event_base注册读事件时使用的event结构体
	/* Socketpair used to send notifications from the signal handler */
	evutil_socket_t ev_signal_pair[2];
	/* True iff we've added the ev_signal event yet. */
	int ev_signal_added;
	/* Count of the number of signals we're currently watching. */
	int ev_n_signals_added;

	/* Array of previous signal handler objects before Libevent started
	 * messing with them.  Used to restore old signal handlers. */
#ifdef EVENT__HAVE_SIGACTION
	struct sigaction **sh_old;
#else
	ev_sighandler_t **sh_old;
#endif
	/* Size of sh_old. */
	int sh_old_max;
};

注册 注销signal事件

注册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);

Timer小根堆

Libevent使用堆来管理Timer事件,其key值就是事件超时时间,源代码位于min_heap.h中

支持IO多路复用技术

Libevent的核心是事件驱动和同步非阻塞。为了达到这一目标。必须使用系统提供的IO多路复用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值