Epoll一般有两种方式边缘触发(ET)和水平触发(LT)
epoll 默认模式是水平触发
- 水平触发: 只要满足条件,就触发事件(只要内核缓冲区中的数据没有读完,就会一直触发)
- 边缘触发: 每当状态发生变化的时候就触发一个事件.
- 举一个例子,你在吃一碗饭,LT当你没有吃完这碗饭,它会一直给你提示,ET 当你去打第二碗饭的时候,才会给你提示.
LT相当于一个快速的poll,可读可写就绪条件和poll是一样的.
ET在使用的过程中,建议:
- 设置非阻塞IO
- 只有当read或者write 返回EAGAIN的时候,才调用下一次epoll_wait.
- 在应用层可以维护一个列表,防止有一个文件描述符号有大量的输入,非阻塞IO可能导致一直处理它,其他就绪文件描述符处于处于饥饿状态.
LT和ET相比不容易遗漏事件,会一直提醒
ET为什么要和非阻塞IO进行搭配
- 因为边缘触发只有当IO事件发生的时候,我们才会收到通知,在另外一个IO事件到来的时候,我们不会收到任何新的通知,我们不知道我们每次需要处理多少IO,所以我们每次需要尽可能的多读,如果使用阻塞IO一直读,在最后一次读取的时候,就会发生阻塞现象,所以要使用非阻塞IO,直到返回相应的系统调用返回EAGIN/EWOULDBLOCK
- 如果send 数据过大,阻塞式IO就会阻塞了.(socket发送缓冲区放不下,send只负责数据拷贝到socket缓冲区,拷贝完立即返回.)
解决当某一个文件描述符有大量输入,不间断的输入流,让非阻塞IO可能导致一直处理它,其他就绪文件描述符处于饥饿状态:
我们可以维护一个列表,列表中存放已经被通知就绪的文件描述符,通过循环按照如下方式处理
1.我们可以调用epoll_wait 监视文件描述符,并将就绪态的描述符添加到table中去,如果文件描述符已经注册到table中去,那么这次监视的时间应该设置为较小值或者0,这样如果没有新的文件描述符成为就绪态,应用程序就可以迅速进行下一步,去处理已经处于就绪态的文件描述符号.
2. 在table 中,只有那些已经注册为就绪态的文件描述符上进行一定限度的IO操作,当相关的非阻塞IO调用出现EAGIN/EWOULDBLOCK时,可以从列表中移除.
epoll为什么比select高效
- select 在调用的时候都需要将所要监控的fd从用户态传递给内核态,epoll只拷贝一次即可,挂载到eventpoll上的红黑树之上.
- epoll 是基于事件驱动的,给每一个fd上注册了一个回调函数,select则需要通过遍历去寻找发生事件的fd.
- select的文件描述符的大小为FD_SETSIZE,由内核决定,epoll的文件描述符号大小比select高很多.