前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >朴素、Select、Poll和Epoll网络编程模型实现和分析——模型比较

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

作者头像
方亮
发布2019-01-16 15:15:29
5530
发布2019-01-16 15:15:29
举报
文章被收录于专栏:方亮方亮

        经过之前四篇博文的介绍,可以大致清楚各种模型的编程步骤。现在我们来回顾下各种模型(转载请指明出于breaksoftware的csdn博客)

模型编程步骤对比

《朴素、Select、Poll和Epoll网络编程模型实现和分析——朴素模型》中介绍的是最基本的网络编程模型,我们使用单线程去实现,它的步骤很简单

        如同这幅流程图,它简单到非常单薄。这个模型暴露出来的问题是在一个线程中,它一次只能处理一个请求。当然我们可以通过多线程或者多进程的模式对该模型进行改进:主线程监听接入socket请求,将其保存到一段空间中。其他线程从该段空间中获取socket并处理。但是这种改善不是从网络编程模型的角度出发的,而是多线程/多进程的一种应用,这种应用也可以用于之后几个模型。所以我们暂且抛开这种优化来讨论。

《朴素、Select、Poll和Epoll网络编程模型实现和分析——Select模型》解决了朴素模型同步的执行的问题,而且还解决了一次只能处理一个请求的问题。

        可以见得这种模型稍微厚实了一点,但是它也有一个问题:最多只能同时处理1024个请求。致命的是这种限制是在内核代码级别的(详见之前文章的分析)。为了解决这个问题于是就有了poll模型。

《朴素、Select、Poll和Epoll网络编程模型实现和分析——Poll模型》中介绍的Poll模型结构和Select模型是非常相似的,但是它不再依赖于结构体的位数来限制最多处理的socket数,而是采用一个数组去保存数据。

        poll模型的问题是其效率随着同时连接的socket数而下降。因为它和Select模型有着相同的缺陷——每次只是知道有事件发生,但是不知道是哪个socket有事件发生,于是需要遍历所有的socket。这样socket如果越来越多,它的效率自然会下降。为了解决这个问题,于是有了Epoll模型。

《朴素、Select、Poll和Epoll网络编程模型实现和分析——Epoll模型》算是目前最优秀的解决多连接的模型。但是它也是最复杂的模型。

模型效率对比

        首先明确一个立场,没有实际测试数据的对比都是空谈。网上一般充斥的一种观点就是EPOLL模型效率最好,Poll和Select模型相当,朴素模型最差。但是这个观点是错误的。因为不同的模型在不同的使用场景下有着不同的效率。

        比如,在我们之前测试的场景下。朴素模型的平均处理能力大概是15000次/秒,Select的平均处理能力是7000次/秒,Poll的平均处理能力是7500次/秒,Epoll的平均处理能力是11000次/秒。为什么朴素模型的处理能力是最高的?因为我们这个是一个典型的短连接、一次性交互的场景,这种场景下如果得到一个请求马上去读和写,而不经过其他复杂的过程,自然是最快的。Poll模型和Select模型能力相当。Epoll是对Poll模型的优化,所以它的效率比Select和Poll要好一些。

        我们使用valgrind对上述四种模型的执行情况进行分析。

        首先我们看朴素模型

        朴素模型的main函数自身(即刨除下面列出的函数的其他执行时间)的执行时间占比是非常小的(5.45%),其主要的耗时操作是读和写操作(server_write占38.94%,server_read占27.64%)。

         我们再看下效率第二的Epoll模型

        可见Epoll模型中,server_write操作耗时占比最高(29.32%),其次是main函数自身耗时占(25.7%),再次是server_read(22.48%)。

        再看下效率倒数第二的Poll模型

        Poll模型中main函数自身耗时占比最高(46.93%),其次是server_write(21.44%),再次是server_read(16.44%)。我们看到main函数自身耗时提高非常多。

        最后我们看看效率最低的Select模型

        Select模型中,main函数自身耗时占比最多(97.99%),其次是server_write(0,83%),再次是server_read(0.64%)。那么main函数中什么是最耗时的呢?我对Select模型源码进行了细分,将之前循环遍历各个Socket的逻辑提炼出来

代码语言:javascript
复制
void deal_socket(int index, int listen_sock, fd_set* active_fd_set, fd_set* read_fd_set) {
	if (listen_sock == index) {
		/* Connection request on original socket. */
		int new_sock;
		new_sock = accept(listen_sock, NULL, NULL);
		if (new_sock < 0) {
			perror("accept error");
			exit(EXIT_FAILURE);
		}
		request_add(1);
		FD_SET(new_sock, active_fd_set);
	} else {
		if (0 == server_read(index)) {
			server_write(index);
		}
		close(index);
		FD_CLR(index, active_fd_set);
	}
}

void deal_request(int listen_sock, fd_set* active_fd_set, fd_set* read_fd_set) {
	int index = 0;
	for (index = 0; index < FD_SETSIZE; ++index) {
		if (FD_ISSET(index, read_fd_set)) {
			deal_socket(index, listen_sock, active_fd_set, read_fd_set);
		}
	}
}

        再进行测试,我们得出以下结果

        deal_request函数函数内部理论上最大调用deal_socket的次数是30万*1024=3072万,而实际调用deal_socket的次数只有60万。这说明平均一次调用deal_request只会有2个socket被命中从而调用deal_socket。也就是说,1次select操作只有2个socket会被处理,而其他1022次循环操作都是不被命中的比较。而恰恰最耗时的就是deal_request操作(deal_socket操作占用deal_request时间不足2%),所以我们可以得出select模型中最耗时的就是对socket的遍历操作。而其根本原因是select函数每次返回时可以被处理的socket数量太少——2个。

        那么epoll和poll模型的循环使用率是多少?我们对它们的代码做点改造,我们引入两个标记函数——total_loop_count和deal_sock_count,前者用于统计一共循环了多少次,后者用于统计需要处理的socket多少次。我们先看Poll模型的结果。

        可见Poll模型中对pollfd数组遍历的次数一共是60万次,有效处理socket也是60万次。使用率100%,而不像Select模型的使用率只有60/3072=2%。同样epoll的使用率也是100%。

        通过上述分析,我们可以看出在短连接、一次性读写完数据的情况下,朴素模型的效率是最高的,其次是Epoll、Poll、Select。其实Select模型性能和Poll是差不多的,在我们测试场景下产生的差距是因为我们实现Select模式的方式问题。我们对其可以参照Poll模型进行改造,使用一个int型数组保存接入的socket,同时动态记录可用socket的个数。这样我们每次遍历就只遍历连续的可用socket数组空间,而不用像例子中将整个数组都遍历,从而提高循环操作的有效率。

        那么什么时候才是该使用Select、Poll或者Epoll模型的呢?我们将在下一篇博文中对其进行分析。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016年06月15日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 模型编程步骤对比
  • 模型效率对比
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档