1.select
select的原理如下:
用户程序发起读操作后,将阻塞查询读数据是否可用,直到内核准备好数据后,用户程序才会真正的读取数据。
poll与select的原理相似,用户程序都要阻塞查询事件是否就绪,但poll没有最大文件描述符的限制。
2.epoll
epoll是select和poll的改进,原理图如下:
epoll使用“事件”的方式通知用户程序数据就绪,并且使用内存拷贝的方式使用户程序直接读取内核准备好的数据,不用再读取数据
使用Netty构建服务器时,需要指定parent线程池和child线程池,parent线程负责监听端口,一旦有连接接入,则注册到child线程池中的一个线程上,该连接的IO操作/任务都由该线程完成。
换句话说,一个线程会负责多个连接的IO操作,也就是多路复用。Netty底层是使用系统提供的select或者epoll来实现多路复用的。
先来科普下select/poll/epoll。
3.select/poll
服务端建立每个连接,相当于打开文件,会获得对应的文件描述符(fd),相同的源IP/源端口/目标IP/目标端口对应同一个fd。
select和poll是相似的,不一样的地方是,select是使用数组,有连接数限制,而poll使用链表,无连接数限制。
监听连接时,从用户层的角度看,
(1)会构建3个fd数组,分别关注读/写/异常事件,设置超时时间,调用系统提供的select方法。
(2)调用select方法时,需要将fd数组传到内核态,等待部分fd就绪后,把fd数组(包含就绪状态)返回到用户态
(3)用户程序对fd数组进行遍历,处理就绪的fd
(4)重新调用select方法。
可以看出不好的地方是(1)每次都要传入fd数组,返回整个fd数组,导致了大量在用户空间和内核空间的相互拷贝。
(2)用户程序仍需要遍历fd数组才能找出就绪的fd
从系统层的角度看,调用select方法时
(1)遍历fd数组,对于每个fd,调用其对应的poll方法(由设备对应的驱动程序实现),将fd所在线程加入等待队列,并且检查就绪状态,记录感兴趣的就绪状态。
(2)如果存在感兴趣的就绪状态,直接返回
(3)如果不存在感兴趣的就绪状态,进入休眠,等待fd就绪后,会唤醒等待队列中的线程
(4)被唤醒后,重复1-4的操作。
可以看出不好的地方是每次都需要检查所有fd。
epoll
epoll相对select改善了很多。
(1)在使用epoll时,首先会构建epoll对象。
(2)有连接接入时,会插入到epoll对象中,epoll对象里实际是一个红黑树+双向链表,fd插入到红黑树中,通过红黑树查找到是否重复
(3)一旦fd就绪,会触发回调把fd的插入到就绪链表中,并唤醒等待队列中的线程。
(4)调用epoll_wait方法时只需要检查就绪链表,如有则返回给用户程序,如没有进入等待队列。
由于epoll把fd管理起来,不需要每次都重复传入,而且只返回就绪的fd,因此减少了用户空间和内核空间的相互拷贝,在fd数量庞大的时候更加高效。
Netty可以选择使用不同的多路复用技术。
NioEventLoop
NioEventLoop底层会根据系统选择select或者epoll。如果是windows系统,则底层使用WindowsSelectorProvider(select)实现多路复用;如果是linux,则使用epoll
当为select模式,在NioEventLoop对应的Selector中会维护着newKeys,updateKeys,cancelledKeys,分别是新增的fd,更新fd的感兴趣状态,取消fd监听。每当连接接入,或者断连,都会调用NioEventLoop的注册/解除注册方法,更新这几个集合。(这里是JDK11的实现,在JDK8中则直接更新PollArrayWrapper)
NioEventLoop在运行的时候,会不断的监听注册的连接,核心逻辑在doSelect方法中,主要的几个操作是
(1)processUpdateQueue,将newKeys,updateKeys,cancelledKeys中的key更新到PollArrayWrapper中
(2)subSelector.poll(),这里实际是调用native方法,会将PollArrayWrapper的fd拷贝至readFds/writeFds/exceptFds,并监听这些fd。
private native int poll0(long pollAddress, int numfds,
int[] readFds, int[] writeFds, int[] exceptFds, long timeout);
1
2
(3)updateSelectedKeys,遍历freadFds/writeFds/exceptFds,将就绪的fd存储到selectedKey中
当存在fd就绪后,doSelect方法返回,应用程序可以遍历selectedKey进行处理。
EpollEventLoop
EpollEventLoop底层使用epoll实现多路复用。
EpollEventLoop中会初始化epollFd、eventFd、timerFd。
epollFd是调用系统方法生成的epoll对象,后续会使用其管理所有需要监听的fd
eventFd是用于线程通讯,程序会把eventFd添加到epollFd中,监听eventFd,一旦eventFd有操作,则会唤醒调用epoll_wait的线程。
timerFd是用于计时,同样的监听timerFd,一旦时间到达,则会唤醒调用epoll_wait的线程。
每当连接接入或者断连,都会调用epoll_ctl_add/epoll_ctl_del方法来操作epoll对象。
在EpollEventLoop的run方法中,会调用epoll_wait来监听所有fd,一旦有fd就绪,会拷贝至EpollEventArray中,应用程序遍历EpollEventArray处理所有就绪事件。
————————————————
版权声明:本文为CSDN博主「lbl2018」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lblblbl...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。