一、IO复用之select对比epoll(redis、nginx、netty使用)
参考:源码图解 参考:文章讲解
1.1、select
因为传统阻塞io对于accept、connect、read、write等系统调用可能会永远阻塞直到套接字上发生 可读\可写 事件。 所以对于系统而言应该是等待IO就绪之后再通知我们过来处理。所以便希望能够使用一个线程,通过记录I/O流的状态来同时管理多个I/O,可以提高服务器的吞吐能力即IO复用。
io复用可以通过select、poll、epoll来实现,而自Linux 2.6内核正式引入epoll以来,epoll已经成为了目前实现高性能网络服务器的必备技术。那为什么选择epoll呢?
上面主要建立了一个客户端,利用accept创建了5个文件描述符即模拟5个客户端连接。 然后后续主要关注select函数的相关参数: 1.文件描述符的个数 2.读文件描述符集合 3.写文件描述符集合 4.异常描述符集合 5. 超时时间
- 可见由于是一个轮询,每次select都会传最大文件描述符个数值(即全部待监控的连接),比如当数十万并发连接存在时,可能每一毫秒只有数百个活跃的连接,同时其余数十万连接在这一毫秒是非活跃的,则大大浪费相关监控资源和时间。
- 当select需要回调时,还得遍历文件描述符数据,判断$reset中哪个fd被置位了(即有数据),即这种轮询机制,导致每次检测都会遍历所有FD_SET中的句柄,select函数执行时间与FD_SET中的句柄个数有一个比例关系。
select函数的缺点: 1.select中所用到的文件描述符有限,即内核中有个参数__FD_SETSIZE定义了每个FD_SET的句柄个数。 默认大小1024 2.需要从用户态拷贝到内核态,由内核态判断是否有数据,存在拷贝的开销 3.当有数据时select就会返回,但是selelct函数不知道哪个文件描述符有数据了,后面还需要再次对文件描述符数组进行遍历,导致$reset不能复用,效率较低
1.2、epoll
epoll精巧的使用了3个方法来实现select方法要做的事:
- 新建epoll描述符==epoll_create()
- epoll_ctrl(epoll描述符,添加或者删除所有待监控的连接)
- 返回的活跃连接 ==epoll_wait( epoll描述符 )
1.首先通过epoll_create(int maxfds)来创建一个epoll的句柄,其中maxfds为你epoll所支持的最大句柄数。这个函数会返回一个新的epoll句柄,之后的所有操作将通过这个句柄来进行操作。在用完之后,记得用close()来关闭这个创建出来的epoll句柄。 2.nfds=epoll_wait(…);其中kdpfd为用epoll_create创建之后的句柄,events是一个epoll_event*的指针,当epoll_wait这个函数操作成功之后,epoll_events里面将储存所有的读写事件。maxevents是最大事件数量。最后一个timeout是epoll_wait的超时,为0的时候表示马上返回,为-1的时候表示一直等下去,直到有事件发生,为任意正整数的时候表示等这么长的时间,如果一直没有事件,则返回
采用百度相关说明
epoll它只会对“活跃”的socket进行操作:这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有“活跃”的socket才会主动的去调用 callback函数,其他idle状态socket则不会,在这点上,epoll实现了一个“伪”AIO,因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃的—比如一个高速LAN环境,epoll并不比select/poll有什么效率,相反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。
总结(epoll关键就是回调):
- epoll_create 创建一个白板 存放fd_events
- epoll_ctl 用于向内核注册新的描述符或者是改变某个文件描述符的状态。已注册的描述符在内核中会被维护在一棵红黑树上
- epoll_wait 通过回调函数内核会将 I/O 准备好的描述符加入到一个链表中管理,进程调用 epoll_wait() 便可以得到事件完成的描述符
通过epoll_ctl函数添加进来的事件都会被放在红黑树的某个节点内,所以,重复添加是没有用的。当把事件添加进来的时候时候会完成关键的一步,那就是该事件都会与相应的设备(网卡)驱动程序建立回调关系,当相应的事件发生后,就会调用这个回调函数,该回调函数在内核中被称为:ep_poll_callback,这个回调函数其实就所把这个事件添加到rdllist这个双向链表中。一旦有事件发生,epoll就会将该事件添加到双向链表中。那么当我们调用epoll_wait时,epoll_wait只需要检查rdlist双向链表中是否有存在注册的事件,效率非常可观。这里也需要将发生了的事件复制到用户态内存中即可。
|