select、poll、epoll

目录

IO多路复用

IO多路复用使用场景

select:

poll:

epoll:

 底层实现原理

三组 I/O 多路复用的优缺点

select

poll

epoll

三种IO多路复用的适用场景 


IO多路复用

I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。select,poll,epoll都是IO多路复用的机制。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

IO多路复用使用场景

  1. 当客户处理多个描述符时(一般是交互式输入和网络套接口),必须使用I/O复用。
  2. 当一个客户同时处理多个套接口时,这种情况是可能的,但很少出现.
  3. 如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。
  4. 如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。
  5. 如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。
     

select:

使用 fd_set 结构体来存放被监听的文件描述符的,本质上是使用一个位图结构来存放这些被监听的文件描述符的,因此select能够监听的文件描述符数量是有限制的。同时,fd_set 没有将文件描述符和事件进行绑定,它仅仅是一个文件描述符集合,因此,select需要提供3个fd_set类型的参数来分别传入和传出可读、可写及异常事件。一方面,使得select不能处理更多类型的事件,另一方面,由于内核对fd_set集合的在线修改,使得下次再调用select()函数前不得不重置这3个fd_set集合,这使得编程变成很麻烦,并且容易出错。

poll:

使用 struct pollfd结构体来存放被监听的文件描述符,它比select“聪明”的地方就在于它把文件描述符和与其关联的事件都定义在这个结构体中了,从而使得编程接口变得简洁很多,同时内核每次修改的都是pollfd结构体的revents成员,而events成员保持不变,因此下次调用poll()函数时应用程序无须重置pollfd类型的事件集参数。

由于每次select 和 poll 调用都是返回整个用户监听的事件集合(其中包括就绪的和未就绪的),所以应用程序索引就绪文件描述符的时间复杂度为O(n)。

epoll:

采用与select 和 poll 完全不同的方式来管理用户注册的事件。它在内核中维护一个事件表,并提供了一个独立的系统调用函数 epoll_ctl来控制往该内核事件表中添加、删除、修改事件。这样,每次调用epoll_wait()函数时,都是直接从内核事件表中取得用户注册的事件,而无须反复从用户空间将这些注册事件读入到内核区中,节省了复制的系统开销。epoll_wait 系统调用中的 events 指针参数仅用来返回就绪的事件,这使得应用程序索引就绪文件描述符的时间复杂度为O(1)。需要注意的是,epoll 和 poll一样,也是将文件描述符和与其关联的事件是绑定在一起的,这样做的好处是,编程接口变得简洁,不像select那样复杂。

select、poll、epoll_第1张图片

 底层实现原理

select 和 poll 都是采用轮询的方式,即每次调用都要扫描整个注册的文件描述符,并将其中就绪文件描述符的数量返回给应用程序,因此它们检测就绪文件描述符的事件复杂度为O(n)。

而epoll则不同,它采用的是回调的方式,内核检测到就绪的文件描述符时,将触发回调函数,回调函数就将该文件描述符上对应的事件插入到内核就绪事件队列。当调用epoll_wait 系统调用时,无须轮询整个内核事件表中的文件描述符,而只需检测就绪事件队列是否有内容,如有,内核则将该就绪队列中的内容拷贝到用户空间,因此epoll检测就绪文件描述符的时间复杂度为O(1)。

三组 I/O 多路复用的优缺点

select


【优点】

1、select的可移植性好,因为在某些Unix系统上并不支持poll 和 epoll(极少)。

2、select 对于超时时间提供了更好的精度:微秒,而 poll 和 epoll 都是毫秒级。

【缺点】

1、select 支持监听的文件描述符fd的数量有限制,默认是1024个。(最大数量限制)

2、select 需要维护一个用来存放文件描述符fd的数据结构(fd_set),每次调用select都需要把fd集合从用户区拷贝到内核区,而select系统调用结束后,又需要把fd集合从内核区拷贝到用户区,这个系统开销在fd数量很多时会很大。(内存复制开销)

3、每次调用select系统调用时,都需要在内核遍历传入的整个文件描述符集合,逐个检测,查看是否有就绪的文件描述符,然后返回就绪文件描述符的个数。也就是说,select对文件描述符是线性扫描的,当注册的文件描述符fd的数量很多时,效率会较低,时间复杂度为O(n)。(时间复杂度)

poll


poll的实现原理和select非常相似,但是相比select,它做了一些改进的地方。首先是存放文件描述符的数据结构(pollfd),它将文件描述符和与其对应的事件关联起来了,使得编程接口变得简洁了;其次,它没有了最大文件描述符的限制,原因是它是基于链表结构来存储的。

【优点】(对比select而言)

1、没有最大文件描述符数量的限制(相对select而言)。(基于链表存储)poll 主要是解决了这个最大文件描述符数量的限制问题。

当然,它还是有上限的,这个上限是操作系统所支持的能开启的最大文件描述符数量(cat /proc/sys/fs/file-max)。

2、优化了编程接口。select()函数有5个参数,而poll()减少到了3个参数。并且每次调用select函数前,都必须重置该函数中的3个fd_set类型的参数值,而poll不需要重置。

【缺点】

1、poll 同样需要维护一个用来存放文件描述符的数据结构(pollfd),当注册的文件描述符无数量很多时,会使得用户区和内核区之间传递该数据结构的复制开销很大。(内存复制开销)

每次调用poll系统调用时,都需要把文件描述符fd集合从用户区拷贝到内核区,然后poll系统调用返回前,又需要把文件描述符fd集合从内核区拷贝到用户区,这个内存拷贝的系统开销在fd数量很多的时候会很大。

<说明> 系统调用函数的执行是发生在内核区的,而用户程序的执行是发生在用户区的,所以会存在内核区与用户区之间的内存复制的系统开销。

2、与select一样,每次poll系统调用时,需要在内核遍历传入的整个文件描述符集合,逐个检测,查看是否有就绪的文件描述符,然后返回就绪文件描述符的个数。也就是说,poll也是线性扫描的方式,当注册的文件描述符fd的数量很多时,效率会较低,时间复杂度为O(n)。(时间复杂度)

3、poll 只能工作在水平触发(LT)模式下。(工作模式)

水平触发模式下,当描述符处于就绪状态下,内核通知了应用程序,但是应用程序没有进行处理,那么下次调用poll时仍会向应用程序发出通知。

<注意> select 和 poll 都需要在返回后,通过遍历整个文件描述符集合来获取就绪的文件描述符。事实上,在网络连接中,同时连接的大量客户端在某一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的递增,其效率也会线性递减。

epoll

epoll 是在Linux 2.6内核版本中提出的,是之前select和poll的增强版本。

epoll使用一个epoll文件描述符管理多个被监听的文件描述符,将用户关心的文件描述符的事件存放到内核的一个事件表中,这样在用户区和内核区只需要拷贝一次被监听的文件描述符的数据结构(epoll_event)即可。

epoll 既解决了select的最大文件描述符数量限制的问题,又解决了poll的内存复制开销大、时间复杂度大的问题(前提条件:文件描述符数量很大的情况下)。

【优点】(对比select和poll)

1、和poll一样,没有最大文件描述符数量的限制(相对select而言)。

2、epoll 虽然也需要维护用来存放文件描述符的数据结构(epoll_event),但是它只需要将该数据结构拷贝进内核区一次,不需要重复拷贝。

epoll只在调用 epoll_ctl 系统调用时拷贝一次要监听的文件描述符数据结构到内核区,在调用 epoll_wait系统调用时不需要再把所有要监听的文件描述符fd重复拷贝进内核区。而select和poll每次调用都需要把所有要监听的fd重新拷贝到内核区。这就解决了内存复制开销的问题。

3、epoll 采用回调方式来检测就绪文件描述符。

epoll 通过epoll_ctl系统调用注册一个文件描述符,一旦该文件描述符就绪,内核就会采用callback回调机制来进行通知,并将该就绪描述符放入就绪事件链表中。然后在epoll_wait系统调用中,当接收到有通知信号到来时,就会去检测就绪事件链表是否有内容,如果有内容,就将就绪事件链表的内容从内核区拷贝到用户区,最后epoll_wait系统调用返回就绪描述符的个数。也就是说,epoll只会对活跃的文件描述符进行管理,而不需要像select和poll那样,每次调用都要线性扫描全部的文件描述符,导致效率呈现线性下降。

【缺点】

目前只有Linux操作系统支持epoll,不支持跨平台使用。而Unix操作系统上是使用kqueue。
 

三种IO多路复用的适用场景 

select、poll:适合在连接数少并且连接都十分活跃的情况下。

epoll:适用在连接数很多,活跃连接较少的情况下。

表面上看epoll的性能最好,但是在连接数少并且连接都十分活跃的情况下,select和poll的性能可能比epoll要好,毕竟epoll的通知机制需要调用很多的函数回调,这也是一笔不小的系统开销。
select、poll的低效是因为每次它们都需要轮询。但低效也是相对的,视情况而定,也可通过良好的设计改善。
 

你可能感兴趣的:(服务器,运维)