libevent源码分析(四)

上一篇

一、从event-test.c示例代码开始

前面剖析了同一个文件夹下的定时器事件和信号事件,然而这两个事件基本都和多路复用技术的关系不是太密切,定时器事件依赖于多路复用技术的超时选项的设计,而信号事件则依赖于人为的触发或者机器中断的产生,所以对于某些技术我们的研究还不够到位,对于事件处理逻辑走得还不够完全接下来就看一看对于普通事件,libevent的处理流程。

static void fifo_read(int fd, short event, void *arg){}
int main (int argc, char **argv){
	struct event evfifo;
    。。。
	const char *fifo = "event.fifo";
	int socket;
    /*lstat不具有穿透性,若为符号链接直接处理之*/
	if (lstat (fifo, &st) == 0) {}
        /*校验fifo文件,若存在则删除*/
	unlink (fifo);
	if (mkfifo (fifo, 0600) == -1) {.../*创建fifo*/}
	/* Linux pipes are broken, we need O_RDWR instead of O_RDONLY */
#ifdef __linux
	socket = open (fifo, O_RDWR | O_NONBLOCK, 0);
#else
	socket = open (fifo, O_RDONLY | O_NONBLOCK, 0);
#endif
    ...
	/* Initalize the event library */
	event_init();

	/* Initalize one event */
	event_set(&evfifo, socket, EV_READ, fifo_read, &evfifo);

	/* Add it to the active events, without a timeout */
	event_add(&evfifo, NULL);
	event_dispatch();
	return (0);
}

无比熟悉的开头,令人兴奋的打开方式,首先调用unix的一系列原生函数处理判断和创建将被用来做测试的命名管道fifo,其实网络编程socket更有代表性,呵呵!接下来,调用event_init()创建当前线程的event_base结构,初始全局变量current_base,具体过程在相关文章二中有提到,此处不做剖析。接下来调用event_set初始化该fifo事件,此处将此event具体值贴出,以便下面查看:event_set(&evfifo, socket, EV_READ, fifo_read, &evfifo);

{
	ev->ev_base = current_base;
	ev->ev_callback = fifo_read;
	ev->ev_arg = &evfifo;
	ev->ev_fd = socket;
	ev->ev_events = EV_READ;
	ev->ev_res = 0;
	ev->ev_flags = EVLIST_INIT;
	ev->ev_ncalls = 0;
	ev->ev_pncalls = NULL;
	ev->min_heap_idx = -1;
	ev->ev_pri = current_base->nactivequeues/2;
}

二、普通IO事件的加入

示例:event_add(&evfifo, NULL);,剔除信号和定时事件处理部分得:

int event_add(struct event *ev, const struct timeval *tv)
{
	struct event_base *base = ev->ev_base;
	const struct eventop *evsel = base->evsel;
	void *evbase = base->evbase;
	int res = 0;
    。。。
	if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&
	    !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {
		res = evsel->add(evbase, ev);
		if (res != -1)
			event_queue_insert(base, ev, EVLIST_INSERTED);
	}
	。。。
}

发现仍然不过是老套路,无非是调用事件机制的add函数,然后将该事件加入到event_base中的eventqueue队列上,此处需要注意的是在调用 event_queue_insert插入事件时会调用TAILQ_INSERT_TAIL宏,它是为监听事件队列特别设计的宏,与上一篇讲的event_list密切相关,非常值得一看。之后我们以epoll为例聊一聊就当是复习了:

static int epoll_add(void *arg, struct event *ev)
{
	struct epollop *epollop = arg;
	struct epoll_event epev = {0, {0}};
	struct evepoll *evep;
	int fd, op, events;
    ...
	fd = ev->ev_fd;
	if (fd >= epollop->nfds) {
		/* Extent the file descriptor array as necessary */
		if (epoll_recalc(ev->ev_base, epollop, fd) == -1)
			return (-1);
	}
	evep = &epollop->fds[fd];   /*记录的fd对应的监听事件,便于触发后快速查找,*/
	op = EPOLL_CTL_ADD;         /*刚开始应该为空*/
	events = 0;
	if (evep->evread != NULL) {     /*下面几行还处理了编辑事件*/
		events |= EPOLLIN;
		op = EPOLL_CTL_MOD;
	}
	if (evep->evwrite != NULL) {
		events |= EPOLLOUT;
		op = EPOLL_CTL_MOD;
	}

	if (ev->ev_events & EV_READ)    /*显然本处应该为这个读事件*/
		events |= EPOLLIN;
	if (ev->ev_events & EV_WRITE)
		events |= EPOLLOUT;

	epev.data.fd = fd;
	epev.events = events;
	if (epoll_ctl(epollop->epfd, op, ev->ev_fd, &epev) == -1)
			return (-1);/*熟悉的epoll_ctl将事件加入监听*/

	/* Update events responsible */
	if (ev->ev_events & EV_READ)/*记录fd对应的监听事件,便于触发后快速查找*/
		evep->evread = ev;
	if (ev->ev_events & EV_WRITE)
		evep->evwrite = ev;

	return (0);
}

epoll_add及其简单看注释即可知道,其目的就是设置好监听事件与fd的对应关系,并将其注册到内核而已。至此加入事件任务完成,仅仅等着下一步的event_dispatch()即可。

三、event_dispatch()魅力犹在

老套路而已,还是会调用到event_base_loop(current_base, 0);

int
event_base_loop(struct event_base *base, int flags)
{
	const struct eventop *evsel = base->evsel;
	void *evbase = base->evbase; ...
	int res, done;
	...
	done = 0;
	while (!done) {
        ...
		res = evsel->dispatch(base, evbase, tv_p);
		...
		if (base->event_count_active) {
			event_process_active(base);
			if (!base->event_count_active && (flags & EVLOOP_ONCE))
				done = 1;
		} else if (flags & EVLOOP_NONBLOCK)
			done = 1;
	}
    ...
}

显然真正其作用的无非是epoll->dispatch(base, evbase, 0);它会将发生事件加入到current_base->activequeues[0]中去,然后等待后面的event_process_active(base)函数将那些发生的事件处理也!可以看一看epoll->dispatch,后面的event_process_active函数在一二篇文章中皆有介绍,故不在介绍。

static int epoll_dispatch(current_base, evbase, 0)
{
	struct epollop *epollop = arg;
	struct epoll_event *events = epollop->events;
	struct evepoll *evep;
	int i, res, timeout = -1;
    ...
	res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout);
    .../*省略处处理了信号类型的事件*/
	for (i = 0; i < res; i++) {
	    /*获取发生事件的类型和文件描述符*/
		int what = events[i].events;
		struct event *evread = NULL, *evwrite = NULL;
		int fd = events[i].data.fd;
        。。。
        /*获取事件fd对应的事件evepoll*/
		evep = &epollop->fds[fd];
        /*获取事件fd对应的具体事件event*/
		if (what & (EPOLLHUP|EPOLLERR)) {
			evread = evep->evread;
			evwrite = evep->evwrite;
		} else {
			if (what & EPOLLIN) {
				evread = evep->evread;
			}   /*本例为读事件应该获取evread */
			if (what & EPOLLOUT) {
				evwrite = evep->evwrite;
			}
		}
        。。。
        /*下面的event_active()函数将读事件加到current_base->activequeues[0]中
         *此处主要就是在玩尾链表的插入方面值得学习,此处不在深入*/
		if (evread != NULL)
			event_active(evread, EV_READ, 1);
		if (evwrite != NULL)
			event_active(evwrite, EV_WRITE, 1);
	}

	if (res == epollop->nevents && epollop->nevents < MAX_NEVENTS) {
		/* We used all of the event space this time.  We should
		   be ready for more events next time. */
		//这个循环简而言之就是怕此时空间可能并不够用,需要阔容。
		int new_nevents = epollop->nevents * 2;
		struct epoll_event *new_events;

		new_events = realloc(epollop->events,
		    new_nevents * sizeof(struct epoll_event));
		if (new_events) {
			epollop->events = new_events;
			epollop->nevents = new_nevents;
		}
	}
	return (0);
}

四、总结

至此,libevent源码剖析就可以告一个段落了,四篇文章,从信号到定时事件再到普通事件让我见识到了广受欢迎的libevent源码是怎样造就的!活动事件链表的插入和删除,宏定义的高效,对事件的高度抽象与具体化,出神入化的函数回调机制。。。最后再来一起看一看事件处理函数fifo_read:

static void
fifo_read(int fd, short event, void *arg)
{
	char buf[255];
	int len;
	struct event *ev = arg;
	/* Reschedule this event */
	event_add(ev, NULL);    //继续
	fprintf(stderr, "fifo_read called with fd: %d, event: %d, arg: %p\n",
		fd, event, arg);

	len = read(fd, buf, sizeof(buf) - 1);

	if (len == -1) {
		perror("read");
		return;
	} else if (len == 0) {
		fprintf(stderr, "Connection closed\n");
		return;
	}

	buf[len] = '\0';
	fprintf(stdout, "Read: %s\n", buf);
}

END

luotuo44是一个GitHub上的开源项目,其主要目的是分析libevent源码libevent是一个使用C语言编写的事件驱动库,可以用于开发高性能的网络服务器和客户端应用程序。 在分析libevent源码时,luotuo44首先研究了libevent的基本结构和使用方法。他深入研究了libevent的事件循环机制、事件触发方式以及事件回调函数的实现原理。通过仔细阅读源代码,他详细解释了libevent是如何利用系统底层的IO多路复用技术(如select、epoll等)来实现高效的事件处理。 此外,luotuo44还分析libevent的缓冲区管理和事件优先级处理机制。他对libevent的缓冲区数据结构和操作进行了详细解读,包括如何实现缓冲区的读写以及缓冲区事件的触发和处理。他还深入探讨了libevent的事件优先级机制,介绍了如何设置和管理不同优先级的事件,并解释了事件优先级对事件处理效率的影响。 除了基本功能外,luotuo44还分析libevent的线程安全性和性能优化策略。他详细讲解了libevent的线程安全机制,包括互斥锁、条件变量等,并提供了一些最佳实践指南,以确保多线程环境下的稳定性和性能。此外,他还分享了一些他自己的性能优化经验,包括使用合适的数据结构、避免频繁内存分配与释放等。 总的来说,luotuo44的libevent源码分析为那些想深入了解libevent内部原理和如何正确使用libevent的开发者提供了很大的帮助。通过他的分析,读者可以更好地理解libevent的工作原理,并从中学习到一些编程技巧和优化策略。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值