朴素、Select、Poll和Epoll网络编程模型实现和分析——Epoll模型

        在阅读完《朴素、Select、Poll和Epoll网络编程模型实现和分析——Select模型》《朴素、Select、Poll和Epoll网络编程模型实现和分析——Poll模型》两篇文章后,我们发现一个问题,不管select函数还是poll函数都不够智能,它们只能告诉我们成功、失败或者超时。如果成功,我们需要遍历整个数组去检查哪些socket需要被处理。对于性能有严格要求的服务器来说,这种浪费的行为是不可容忍的。而本文介绍的Epoll模型就完美的解决了这个问题。(转载请指明出于breaksoftware的csdn博客)

        和Poll模型类似,Epoll模型对同时处理的Socket没有限制。但是我们使用Epoll模型时需要定义epoll_event类型的结构体数组

	struct epoll_event ev, events[SOCKET_LIST_COUNT];

        创建Socket、绑定端口和开始监听等操作和《朴素、Select、Poll和Epoll网络编程模型实现和分析——Select模型》一文中一致,本文就不再列出代码。

        和其他几种模式不同,epoll模型需要创建一个epoll文件描述符。

	int epfd, nfds;
	epfd = epoll_create1(0);

        之后我们将对该epoll文件描述符进行操作。在进行操作之前,我们先了解下EPOLL的模式:ET和LT模式。这块内容网上很多,我就大概说下。

        ET是边界触发(edge-triggered),它的特点是一旦被监控的文件描述符状态发生改变,就会通知应用层,应用层需要“一次性”完成完该事件对应的操作(读或者写等)。如果没有全部完成(比如读/写了一半),ET模式就认为是全部完成了,故不再通知该文件描述符本次事件未完成。它是EPOLL模型中第一个被开发出来的模式,但是由于种种原因,后来又引入了LT模式。

        LT是level-triggered。它并不要求用户一次性完成本次事件对应操作,如果操作没有完成,则下次还会通知应用层。所以这种模式是非常安全的。它也是Epoll模型中缺省方式。

        至于网上很多人说ET模式比LT模式效率高很多,我觉得这个观点从原理上来说是成立的,但是到实际应用时,可能它们的效率是差不多的。但是这些关于效率的话题,只有测试数据才是有力的证据,其他都是理论推理。

        再回到我们的程序上来,我们定义之后接入的socket需要关注的消息以及模式,我们暂且使用ET模式吧。

	in_events = EPOLLIN | EPOLLET;
	out_events = EPOLLOUT | EPOLLET;

        下一步,我们将创建的监听socket加入到epoll文件描述符关联的信息中

	ev.data.fd = listen_sock;
	ev.events = EPOLLIN;
	epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sock, &ev);

        此时,我们让监控的事件events是EPOLLIN,即发生读事件。且我们使用了缺省模式——LT模式。这样我们可以保证接入的socket将不会因为意外而被丢弃,从而尽可能的保证不丢包。

        最后,我们还是要在一个死循环中不停监听相关事件

	while (1) {
		nfds = epoll_wait(epfd, events, sizeof(events), 500);

        epoll_wait中第一个参数是epoll文件描述符,第二个参数是用于保存发生事件的epoll_event对象数组;第三个参数是该数组的最大个数;第四个参数是等待的超时时间。注意一下第二个参数,我们传入的是数组首地址。当被监控的socket有被关注的事件发生时,events数组里将保存它的一个副本。这样就让发生了事件的元素保存到该数组中,而没有发生的则不在这个数组中,之后我们就不用遍历整个数组了。epoll_wait函数在执行成功时,将返回填充到events数组中的元素个数。于是我们就开始遍历这个“全部有效”的数组。

		for (index = 0; index < nfds; ++index) {
			int fd = events[index].data.fd; 

        和Select、Poll模型类似,我们需要区分文件描述符是否是监听socket。如果是监听socket,则通过accept获取接入的socket,并使用epoll_ctl将该socket和epoll文件描述符产生关联。

			if (fd == listen_sock) {
				int new_sock;
				new_sock = accept(fd, NULL, NULL);
				if (new_sock < 0) {
					if (errno == EMFILE) {
					}
					else {
						perror("accept error");
						exit(EXIT_FAILURE);
					}
				}
				else {
					request_add(1);
					ev.data.fd = new_sock;
					ev.events = in_events;
					epoll_ctl(epfd, EPOLL_CTL_ADD, new_sock, &ev);
				}
			}

        如果不是监听socket,那就是之后接入的socket。我们需要判断下发生的事件类型,如果是发生了可读事件,则我们去读该文件描述符,并使用epoll_ctl将该文件描述符的状态改成“可写”。

			else {
				if (events[index].events & EPOLLIN) {
					if (0 != server_read(fd)) {
						close(fd);
						epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
					}
					else {
						events[index].events = out_events;
						epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &events[index]);
					}
				}

        如果是接入的socket是写状态,我们就写入数据。最后关闭该连接,同时使用epoll_ctl将该文件描述符和epoll文件描述符断开关系。

				else if (events[index].events & EPOLLOUT) {
					server_write(fd);
					close(fd);
					//memset(&events[index], 0, sizeof(events[index]));
					//events[index].data.fd = -1;
					epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
				}
			}
		}
	}
	close(epfd);
	return 0;
}

        一切就是这么简单。我们看下epoll模型的执行效率。我们采用和《朴素、Select、Poll和Epoll网络编程模型实现和分析——朴素模型》一文中相同的环境和压力,看下服务器的数据输出

        再看下客户端输出


        可见在当前环境下,每秒可以处理11000左右个请求。可见它的效率的确比Select和Poll模型好。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

breaksoftware

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值