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. [题解]Magic Line-计算几何(2019牛客多校第三场H题)

    题目链接:https://ac.nowcoder.com/acm/contest/883/H 题意: 给你偶数个点的坐标,找出一条直线将这n个点分成数量相等的两部分 并在这条直线上取不同的两个点,表示 ...

  2. js获取下拉框的value值

    var Resultstr=""; var param = { action: "MoneyList" };//参数拼接 $.ajax({ type: &quo ...

  3. spring注解开发:bean的作用域与懒加载

    1.bean的作用域 1.新建一个maven工程,添加如下依赖 <dependency> <groupId>org.springframework</groupId> ...

  4. this关键字与super关键字区别

        this super 1 访问属性 访问本类中属性,如果本类中没有此属性,就从父类继承过来的属性中查找 (遵循就近原则) 访问父类中的属性 2 调用方法 访问本类中方法 直接访问父类中方法 3 ...

  5. C# DataTable转json 时间格式化

    1.NewTonSoft.json public static string DataTableToJson(DataTable dt) { ) { return ""; } el ...

  6. Java小游戏

    这是一个飞机躲避子弹的小游戏,其中有许多干货 这是蒟蒻我第二次做,请各位大佬多多指教 目录 1.游戏主窗口的创建 2.图形绘制_文本绘制_颜色改变_图像对象的加载 3.线程内部类实现动画 4.游戏物体 ...

  7. go语言从例子开始之Example17.指针

    Go 支持 指针,允许在程序中通过引用传递值或者数据结构 Example: package main import "fmt" func zeroval(ival int){ iv ...

  8. lsyncd+rsync文件实时同步

    1.rsync两端都需要安装 yum -y install rsync 2.提供lsyncd的安装源 rpm -ivh http://dl.fedoraproject.org/pub/epel/6/x ...

  9. 小白jquery横向菜单弹出菜单制作

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  10. Qt QSS图片样式切割,三种状态normal,hover,pressed

    要切割的样式图片如下: pix_Button->setStyleSheet("QPushButton{ border-image:url(:/image/MyButtonimage/m ...