libevent IO多路复用之epoll

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Function_Dou/article/details/87904153

libevent事件驱动, 同步而非阻塞的. 为了保证非阻塞我们在前面已经看到过多次了, 设置文件描述符为非阻塞. libevent内部也是为了满足非阻塞所以集成了一系列的多路复用函数, 但并非每个都会使用. libevent会根据不同系统选择不同的复用函数. 这里我们主要分析linux的复用机制(epoll).

统一接口

不管是在windows还是linux, 也不管使用select还是epoll都统一调用eventop定义的函数指针, 这也可以叫做事件多路分发器.

struct eventop {
	const char *name;
	void *(*init)(struct event_base *); // 初始化
	int (*add)(void *, struct event *); // 注册事件
	int (*del)(void *, struct event *); // 删除事件
	int (*dispatch)(struct event_base *, void *, struct timeval *); // 事件分发
	void (*dealloc)(struct event_base *, void *); // 注销,释放资源
	/* set if we need to reinitialize the event base */
	int need_reinit;
};

而分发器调用的函数都是根据编译器选择默认设置.

static const struct eventop *eventops[] = {
#ifdef HAVE_EVENT_PORTS
		&evportops,
#endif
#ifdef HAVE_WORKING_KQUEUE
		&kqops,
#endif
#ifdef HAVE_EPOLL
		&epollops,
		35
#endif
#ifdef HAVE_DEVPOLL
		&devpollops,
#endif
#ifdef HAVE_POLL
		&pollops,
#endif
#ifdef HAVE_SELECT
		&selectops,
#endif
#ifdef WIN32
		&win32ops,
#endif
		NULL
};

epollop 结构

该结构源码在epoll.c

struct epollop {
	// 对应的event的管理, 由 fds[fd] 来管理具体哪一个文件描述符的 IO 操作
	struct evepoll *fds;
	int nfds;   // fd 的数量
	struct epoll_event *events; // epoll 的事件
	int nevents;	// 事件的数量
	int epfd;   // epoll_create 返回对应的文件描述符
};
// epoll IO
struct evepoll {
	struct event *evread;
	struct event *evwrite;
};

epollops默认初始化设置的5种函数都是对epoll函数的封装而已. 下面我们就来具体分析这些函数的封装.

// epoll 对应的函数
const struct eventop epollops = {
	"epoll",
	epoll_init,
	epoll_add,
	epoll_del,
	epoll_dispatch,
	epoll_dealloc,
	1 /* need reinit */
};

epoll_init 函数

// 初始化
// 调用 epoll_create 初始化 epoll 并且初始化 epollop 结构
static void *
epoll_init(struct event_base *base)
{
	int epfd;
	struct epollop *epollop;

	/* Disable epollueue when this environment variable is set */
	if (evutil_getenv("EVENT_NOEPOLL"))
		return (NULL);

	/* Initalize the kernel queue */
	// 创建 epoll 连接
	if ((epfd = epoll_create(32000)) == -1) {
		if (errno != ENOSYS)
			event_warn("epoll_create");
		return (NULL);
	}

	// 设置文件描述符
	FD_CLOSEONEXEC(epfd);

	if (!(epollop = calloc(1, sizeof(struct epollop))))
		return (NULL);

    // 初始化监听信号
	epollop->epfd = epfd;

	/* Initalize fields */
    // 分配空间
	epollop->events = malloc(INITIAL_NEVENTS * sizeof(struct epoll_event));
	if (epollop->events == NULL) {
		free(epollop);
		return (NULL);
	}
	epollop->nevents = INITIAL_NEVENTS;

	epollop->fds = calloc(INITIAL_NFILES, sizeof(struct evepoll));
	if (epollop->fds == NULL) {
		free(epollop->events);
		free(epollop);
		return (NULL);
	}
    // 事件标志设置为已初始化
	epollop->nfds = INITIAL_NFILES;

    // 并且对信号也设置初始化
	evsignal_init(base);

	return (epollop);
}

epoll_init函数其实就是调用epoll_create并且对描述符设置为非阻塞, 再对event分配返回epoll事件的内存大小, 最后设置初始化标志并初始化信号.

epoll_add 函数

// 添加 epoll 事件
// 其实调用 epoll_ctl 选项的 EPOLL_CTL_MOD 和 EPOLL_CTL_ADD 
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;

	// 如果事件是信号, 则添加到信号中
	if (ev->ev_events & EV_SIGNAL)
		return (evsignal_add(ev));

	// 如果文件描述符大于了 nfds 时, 重新处理分配处理事件的空间大小
	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 是当前事件的 IO状态
	evep = &epollop->fds[fd];
	op = EPOLL_CTL_ADD;
	events = 0;
	// 如果事件本身可读(即 已经在epoll中), 所以设置方法为 EPOLL_CTL_MOD 
	if (evep->evread != NULL) {
		events |= EPOLLIN;
		op = EPOLL_CTL_MOD;
	}
	// 同理
	if (evep->evwrite != NULL) {
		events |= EPOLLOUT;
		op = EPOLL_CTL_MOD;
	}

	// 如果事件是 EV_READ 表示是读事件
	if (ev->ev_events & EV_READ)
		events |= EPOLLIN;
	// 事件事件是 EV_WRITE 表示是写事件
	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);

	/* Update events responsible */
	// 更新事件的监听状态, 之所以没有上面同时修改, 是要保证 epoll_ctl 调用成功
	if (ev->ev_events & EV_READ)
		evep->evread = ev;
	if (ev->ev_events & EV_WRITE)
		evep->evwrite = ev;

	return (0);
}

epoll_del 删除事件

// 删除事件
static int
epoll_del(void *arg, struct event *ev)
{
	struct epollop *epollop = arg;
	struct epoll_event epev = {0, {0}};
	struct evepoll *evep;
	int fd, events, op;
	int needwritedelete = 1, needreaddelete = 1;

	// 如果是信号, 则交给信号处理函数处理
	if (ev->ev_events & EV_SIGNAL)
		return (evsignal_del(ev));

	fd = ev->ev_fd;
	if (fd >= epollop->nfds)
		return (0);
	evep = &epollop->fds[fd];

	op = EPOLL_CTL_DEL;
	events = 0;

	if (ev->ev_events & EV_READ)
		events |= EPOLLIN;
	if (ev->ev_events & EV_WRITE)
		events |= EPOLLOUT;

	// 如果事件本身已经存在 epoll 中
	if ((events & (EPOLLIN|EPOLLOUT)) != (EPOLLIN|EPOLLOUT)) {
		// 如果是可写
		if ((events & EPOLLIN) && evep->evwrite != NULL) {
			needwritedelete = 0;
			events = EPOLLOUT;
			op = EPOLL_CTL_MOD;
		// 如果是可读
		} else if ((events & EPOLLOUT) && evep->evread != NULL) {
			needreaddelete = 0;
			events = EPOLLIN;
			op = EPOLL_CTL_MOD;
		}
	}

	epev.events = events;
	epev.data.fd = fd;

	if (needreaddelete)
		evep->evread = NULL;
	if (needwritedelete)
		evep->evwrite = NULL;

	if (epoll_ctl(epollop->epfd, op, fd, &epev) == -1)
		return (-1);

	return (0);
}

epoll_dispatch 函数

epoll_dispatch 分发事件, 这是epoll封装的一个重要的东西.

// 分发事件
// 调用 epoll_wait 函数处理到来的事件. 并检查信号和事件的流量大小
static int
epoll_dispatch(struct event_base *base, void *arg, struct timeval *tv)
{
	struct epollop *epollop = arg;
	struct epoll_event *events = epollop->events;
	struct evepoll *evep;
	int i, res, timeout = -1;

	// 计算时间
	if (tv != NULL)
		timeout = tv->tv_sec * 1000 + (tv->tv_usec + 999) / 1000;

	// 如果时间大于了最大 epoll 设置时间就设置为 epoll 最大时间
	if (timeout > MAX_EPOLL_TIMEOUT_MSEC) {
		/* Linux kernels can wait forever if the timeout is too big;
		 * see comment on MAX_EPOLL_TIMEOUT_MSEC. */
		timeout = MAX_EPOLL_TIMEOUT_MSEC;
	}

	// 调用 epoll_wait 等待事件到来
	res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout);

	if (res == -1) {
		if (errno != EINTR) {
			event_warn("epoll_wait");
			return (-1);
		}

		// 如果是信号则加入将信号加入到就绪队列中
		evsignal_process(base);
		return (0);
	} else if (base->sig.evsignal_caught) {
		// 如果 epoll_wait 正常返回, 检查是否有信号标识被设置, 有则处理信号
		evsignal_process(base);
	}

	event_debug(("%s: epoll_wait reports %d", __func__, res));

	// 处理IO事件
	for (i = 0; i < res; i++) {
		// IO事件的类型
		int what = events[i].events;
		struct event *evread = NULL, *evwrite = NULL;
		int fd = events[i].data.fd;

		if (fd < 0 || fd >= epollop->nfds)
			continue;
		evep = &epollop->fds[fd];

		// 异常
		if (what & (EPOLLHUP|EPOLLERR)) {
			evread = evep->evread;
			evwrite = evep->evwrite;
		} else {    // 可写
			if (what & EPOLLIN) {
				evread = evep->evread;
			}

			// 可读
			if (what & EPOLLOUT) {
				evwrite = evep->evwrite;
			}
		}

		if (!(evread||evwrite))
			continue;

		// 触发 IO 操作
		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);
}

我将比较重要的部分提取了出来, 就是关于信号的处理.

	// 调用 epoll_wait 等待事件到来
	res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout);

	if (res == -1) {
		if (errno != EINTR) {
			event_warn("epoll_wait");
			return (-1);
		}

		// 如果是信号则加入将信号加入到就绪队列中
		evsignal_process(base);
		return (0);
	} else if (base->sig.evsignal_caught) {
		// 如果 epoll_wait 正常返回, 检查是否有信号标识被设置, 有则处理信号
		evsignal_process(base);
	}

在这里可以看到, epoll_wait等待事件

  • 如果被中断且中断源是信号, 则调用evsignal_process将信号加入就绪队列中
  • 如果正常退出, 但是事件的信号标志被设置, 也将信号加入到就绪队列中.

总结

本节基本上没有做太多的分析, 因为基本都是关于网络编程的基础, 所以这里也就不花篇幅来分析.

  • 在epoll_dispatch中执行epoll_wait, 并且通过返回后判断类型来决定是否将信号就如就绪队列.

猜你喜欢

转载自blog.csdn.net/Function_Dou/article/details/87904153