Linux NIO 系列(04-4) select、poll、epoll 对比

[toc]

Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html)

既然 select/poll/epoll 都是 I/O 多路复用的具体的实现,之所以现在同时存在,其实他们也是不同历史时期的产物

  • select 出现是 1984 年在 BSD 里面实现的
  • 14 年之后也就是 1997 年才实现了 poll,其实拖那么久也不是效率问题, 而是那个时代的硬件实在太弱,一台服务器处理1千多个链接简直就是神一样的存在了,select 很长段时间已经满足需求
  • 2002, 大神 Davide Libenzi 实现了 epoll

一、API 对比

1.1 select API

int select(int maxfdp, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);
int FD_ZERO(int fd, fd_set *fdset); // 一个 fd_set 类型变量的所有位都设为 0
int FD_CLR(int fd, fd_set *fdset); // 清除某个位时可以使用
int FD_SET(int fd, fd_set *fd_set); // 设置变量的某个位置位
int FD_ISSET(int fd, fd_set *fdset); // 测试某个位是否被置位

select() 的机制中提供一种 fd_set 的数据结构,实际上是一个 long 类型的数组,每一个数组元素都能与一打开的文件句柄建立联系(这种联系需要自己完成),当调用 select() 时,由内核根据IO 状态修改 fd_set 的内容,由此来通知执行了 select() 的进程哪一 Socket 或文件可读。

select 机制的问题

  1. 每次调用 select,都需要把 fd_set 集合从用户态拷贝到内核态,如果 fd_set 集合很大时,那这个开销也很大
  2. 同时每次调用 select 都需要在内核遍历传递进来的所有 fd_set,如果 fd_set 集合很大时,那这个开销也很大
  3. 为了减少数据拷贝带来的性能损坏,内核对被监控的 fd_set 集合大小做了限制(默认为 1024)

1.2 poll API

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
int fd; // 文件描述符
short events; // 感兴趣的事件
short revents; // 实际发生的事件
};

poll 的机制与 select 类似,与 select 在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是 poll 没有最大文件描述符数量的限制。也就是说,poll 只解决了上面的问题 3,并没有解决问题 1,2 的性能开销问题。

1.3 epoll API

// 函数创建一个 epoll 句柄,实际上是一棵红黑树
int epoll_create(int size);
// 函数注册要监听的事件类型,op 表示红黑树进行增删改
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// 函数等待事件的就绪,成功时返回就绪的事件数目,调用失败时返回 -1,等待超时返回 0
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

epoll 在 Linux2.6 内核正式提出,是基于事件驱动的 I/O 方式,相对于 select 来说,epoll 没有描述符个数限制,使用一个文件描述符管理多个描述符,将用户关心的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。

二、总结

I/O 多路复用技术在 I/O 编程过程中,当需要同时处理多个客户端接入请求时,可以利用多线程或者 I/O 多路复用技术进行处理。I/O 多路复用技术通过把多个 I/O 的阻塞复用到同一个 select 的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求。与传统的多线程/多进程模型比,I/O 多路复用的最大优势是系统开销小,系统不需要创建新的额外进程或者线程,也不需要维护这些进程和线程的运行,降低了系统的维护工作量,节省了系统资源,I/O多路复用的主要应用场景如下:

  • 服务器需要同时处理多个处于监听状态或者多个连接状态的套接字
  • 服务器需要同时处理多种网络协议的套接字。

目前支持 I/O 多路复用的系统调用有 select、pselect、poll、epoll,在 Linux 网络编程过程中,很长一段时间都使用 select 做轮询和网络事件通知,然而 select 的一些固有缺陷导致了它的应用受到了很大的限制,最终 Linux 不得不在新的内核版本中寻找 select 的替代方案,最终选择了 epoll。epoll 与 select 的原理比较类似,为了克服 select 的缺点, epoll 作了很多重大改进,现总结如下。

2.1 支持一个进程打开的 socket 描述符(FD)不受限制(仅受限于操作系统的最大文件句柄数)

select、poll 和 epoll 底层数据各不相同。select 使用数组;poll 采用链表,解决了 fd 数量的限制;epoll 底层使用的是红黑树,能够有效的提升效率。

select 最大的缺陷就是单个进程所打开的 FD 是有一定限制的,它由 FD_SETSIZE 设置,默认值是 1024。对于那些需要支持上万个 TCP 连接的大型服务器来说显然太少了。可以选择修改这个宏然后重新编译内核,不过这会带来网络效率的下降。我们也可以通过选择多进程的方案(传统的 Apache 方案)解决这个问题,不过虽然在 Linux 上创建进程的代价比较小,但仍旧是不可忽视的。另外,进程间的数据交换非常麻烦,对于 Java 来说,由于没有共享内存,需要通过 Socket 通信或者其他方式进行数据同步,这带来了额外的性能损耗,増加了程序复杂度,所以也不是一种完美的解决方案。值得庆幸的是, epoll 并没有这个限制,它所支持的 FD 上限是操作系统的最大文件句柄数,这个数字远远大于 1024。例如,在 1GB 内存的机器上大约是 10 万个句柄左右,具体的值可以通过 cat proc/sys/fs/file-max 查看,通常情况下这个值跟系统的内存关系比较大。

# (所有进程)当前计算机所能打开的最大文件个数。受硬件影响,这个值可以改(通过limits.conf)
cat /proc/sys/fs/file-max # (单个进程)查看一个进程可以打开的socket描述符上限。缺省为1024
ulimit -a
# 修改为默认的最大文件个数。【注销用户,使其生效】
ulimit -n 2000 # soft软限制 hard硬限制。所谓软限制是可以用命令的方式修改该上限值,但不能大于硬限制
vi /etc/security/limits.conf
* soft nofile 3000 # 设置默认值。可直接使用命令修改
* hard nofile 20000 # 最大上限值

2.2 I/O 效率不会随着 FD 数目的増加而线性下降

传统 select/poll 的另一个致命弱点,就是当你拥有一个很大的 socket 集合时,由于网络延时或者链路空闲,任一时刻只有少部分的 socket 是“活跃”的,但是 select/poll 每次调用都会线性扫描全部的集合,导致效率呈现线性下降。 epoll 不存在这个问题,它只会对“活跃”的 socket 进行操作一一这是因为在内核实现中, epoll 是根据每个 fd 上面的 callback 函数实现的。那么,只有“活跃”的 socket オ会去主动调用 callback 函数,其他 idle 状态的 socket 则不会。在这点上, epoll 实现了一个伪 AIO。针对 epoll 和 select 性能对比的 benchmark 测试表明:如果所有的 socket 都处于活跃态 - 例如一个高速 LAN 环境, epoll 并不比 select/poll 效率高太多;相反,如果过多使用 epoll_ctl,效率相比还有稍微地降低但是一旦使用 idle connections 模拟 WAN 环境, epoll 的效率就远在 select/poll 之上了。

2.3 使用 mmap 加速内核与用户空间的消息传递

无论是 select、poll 还是 epoll 都需要内核把 FD 消息通知给用户空间,如何避免不必要的内存复制就显得非常重要,epoll 是通过内核和用户空间 mmap 同一块内存来实现的。

2.4 epoll API 更加简单

包括创建一个 epoll 描述符、添加监听事件、阻塞等待所监听的事件发生、关闭 epoll 描述符等。

值得说明的是,用来克服 select/poll 缺点的方法不只有 epoll, epoll 只是一种 Linux 的实现方案。在 freeBSD 下有 kqueue,而 dev/poll 是最古老的 Solaris 的方案,使用难度依次递增。 kqueue 是 freeBSD 宠儿,它实际上是一个功能相当丰富的 kernel 事件队列,它不仅仅是 select/poll 的升级,而且可以处理 signal、目录结构变化、进程等多种事件。 kqueue 是边缘触发的。 /dev/poll 是 Solaris 的产物,是这一系列高性能 API 中最早出现的。 Kernel 提供了一个特殊的设备文件 /dev/poll,应用程序打开这个文件得到操作 fd_set 的句柄,通过写入 polled 来修改它,一个特殊的 ioctl 调用用来替换 select。不过由于出现的年代比较早,所以 /dev/poll 的接口实现比较原始。

附表1: select/poll/epoll 区别

比较 | select | poll | epoll
---- | ---- | ---- | ----
操作方式 | 遍历 | 遍历 | 回调
底层实现 | 数组 | 链表 | 红黑树
IO效率 | 每次调用都进行线性遍历,
时间复杂度为O(n) | 每次调用都进行线性遍历,
时间复杂度为O(n) |
事件通知方式,每当fd就绪,
系统注册的回调函数就会被调用,
将就绪fd放到readyList里面,
时间复杂度O(1)
最大连接数 | 1024 | 无上限 | 无上限
fd拷贝 | 每次调用select,
都需要把fd集合从用户态拷贝到内核态 | 每次调用poll,
都需要把fd集合从用户态拷贝到内核态 | 调用epoll_ctl时拷贝进内核并保存,
之后每次epoll_wait不拷贝

总结:epoll 是 Linux 目前大规模网络并发程序开发的首选模型。在绝大多数情况下性能远超 select 和 poll。目前流行的高性能 web 服务器 Nginx 正式依赖于 epoll 提供的高效网络套接字轮询服务。但是,在并发连接不高的情况下,多线程+阻塞 I/O 方式可能性能更好。

参考:

  1. IO多路复用的三种机制Select,Poll,Epoll

每天用心记录一点点。内容也许不重要,但习惯很重要!

Linux NIO 系列(04-4) select、poll、epoll 对比的更多相关文章

  1. Linux I/O复用中select poll epoll模型的介绍及其优缺点的比較

    关于I/O多路复用: I/O多路复用(又被称为"事件驱动"),首先要理解的是.操作系统为你提供了一个功能.当你的某个socket可读或者可写的时候.它能够给你一个通知.这样当配合非 ...

  2. select/poll/epoll 对比

    前两篇文章介绍了select,poll,epoll的基本用法,现在我们来看看它们的区别和适用场景. 首先还是来看常见的select和poll.对于网络编程来说,一般认为poll比select要高级一些 ...

  3. bio,nio,aio的区别 select,poll,epoll的区别

    先了解一些基本概念,什么是socket?什么是I/O操作 unix(like)世界里,一切皆文件,而文件是什么呢?文件就是一串二进制流而已,不管socket,还是FIFO.管道.终端,对我们来说,一切 ...

  4. Linux NIO 系列(04-3) epoll

    目录 一.why epoll 1.1 select 模型的缺点 1.2 epoll 模型优点 二.epoll API 2.1 epoll_create 2.2 epoll_ctl 2.3 epoll_ ...

  5. Linux NIO 系列(04-1) select

    目录 一.select 机制的优势 二.select API 介绍与使用 2.1 select 2.2 fd_set 集合操作 2.3 select 使用范例 三.深入理解 select 模型: 四. ...

  6. Linux NIO 系列(04-2) poll

    目录 一.select 和 poll 比较 二.poll API 附1:linux 每个进程IO限制 附2:poll 网络编程 Linux NIO 系列(04-2) poll Netty 系列目录(h ...

  7. Linux 网络编程的5种IO模型:多路复用(select/poll/epoll)

    Linux 网络编程的5种IO模型:多路复用(select/poll/epoll) 背景 我们在上一讲 Linux 网络编程的5种IO模型:阻塞IO与非阻塞IO中,对于其中的 阻塞/非阻塞IO 进行了 ...

  8. Linux下select&poll&epoll的实现原理(一)

    最近简单看了一把 linux-3.10.25 kernel中select/poll/epoll这个几个IO事件检测API的实现.此处做一些记录.其基本的原理是相同的,流程如下 先依次调用fd对应的st ...

  9. Linux下select&poll&epoll的实现原理(一)【转】

    转自:http://www.cnblogs.com/lanyuliuyun/p/5011526.html 最近简单看了一把 linux-3.10.25 kernel中select/poll/epoll ...

随机推荐

  1. 【python】 字符串转小写(含汉字等时仍work)

    def mylower(str): outstr = ""; strlen = len(str); idx = 0; while idx < strlen: if ord(s ...

  2. <JAVA - 大作业(1)文本编辑器 >

    <JAVA - 大作业(1)文本编辑器 > 背景 JAVA上机大作业:qq / 代码评价系统 第一次上机主题是练习JAVA自带的GUI图形化编程 目的:实现一个跟window10记事本界面 ...

  3. CommonJS规范 by ranyifeng

    1,概述 CommonJS是服务器端模块的规范,Node.js采用了这个规范. 根据CommonJS规范,一个单独的文件就是一个模块.加载模块使用require方法,该方法读取一个文件并执行,最后返回 ...

  4. java中Comparator比较器顺序问题,源码分析

    提示: 分析过程是个人的一些理解,如有不对的地方,还请大家见谅,指出错误,共同学习. 源码分析过程中由于我写的注释比较啰嗦.比较多,导致文中源代码不清晰,还请一遍参照源代码,一遍参照本文进行阅读. 原 ...

  5. Java数组遍历

    1.数组声明格式: 数据类型 [] 数组名 = new 数据类型[长度]: 数组长度一旦确定无法更改. 数组里的数据必须是相同类型或自动向上转型后兼容的类型 2.数组遍历 //一维数组 String ...

  6. elasticsearch relevance score相关性评分的计算

    一.多shard场景下relevance score不准确问题 1.问题描述: 多个shard下,如果每个shard包含指定搜索条件的document数量不均匀的情况下,会导致在某个shard上doc ...

  7. Vim默认开启语法标识功能

    把syntax on加到$HOME/.vimrc文件中.

  8. 使用springBoot完成阿里云短信验证

    <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot ...

  9. Python深度学习

    序言 目的驱动型学习 概念解释 资料 https://www.tensorflow.org/ https://www.imooc.com/video/17186 https://www.cnblogs ...

  10. 【Dart学习】--Dart之正则表达式相关方法总结

    一,部分属性 RegExp exp = new RegExp(r"(\w+)"); 返回正则表达式的哈希码 print(exp.hashCode); 正则表达式是否区分大小写 pr ...