epoll事件模型
POLL事件有两种模型:
Edge Triggered (ET) 边缘触发只有数据到来才触发,不管缓存区中是否还有数据。
Level Triggered (LT) 水平触发只要有数据都会触发。
思考如下步骤:
假定已经把一个用来从管道中读取数据的文件描述符(RFD)添加到epoll描述符。
管道的另一端写入了2KB的数据
调用epoll_wait,并且它会返回RFD,说明它已经准备好读取操作
读取1KB的数据
调用epoll_wait……
在这个过程中,有两种工作模式:
ET模式
ET模式即Edge Triggered工作模式。
如果在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标志,那么在第5步调用epoll_wait之后将有可能会挂起,因为剩余的数据还存在于文件的输入缓冲区内,而且数据发出端还在等待一个针对已经发出数据的反馈信息。只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件。因此在第5步的时候,调用者可能会放弃等待仍在存在于文件输入缓冲区内的剩余数据。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。最好以下面的方式调用ET模式的epoll接口:
基于非阻塞文件句柄
只有当read或者write返回EAGAIN(非阻塞读,暂时无数据)时才需要挂起、等待。但这并不是说每次read时都需要循环读,直到读到产生一个EAGAIN才认为此次事件处理完成,当read返回的读到的数据长度小于请求的数据长度时,就可以确定此时缓冲中已没有数据了,也就可以认为此事读事件已处理完成。
LT模式
LT模式即Level Triggered工作模式。
与ET模式不同的是,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll,无论后面的数据是否被使用。
LT(level triggered):LT是缺省的工作方式,并且同时支持block和no-block socket。在这种做法中,内核告诉一个文件描述符是否就绪了,然后可以对这个就绪的fd进行IO操作。如果不作任何操作,内核还是会继续通知,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。
ET(edge-triggered):ET是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉。然后它会假设知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知。请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once).
基于管道的epoll ET触发模式
1 |
|
现象:
当设置水平触发时, 只要管道中有数据, epoll_wait就会返回, 触发父进程读取数据, 所以虽然父进程每次只读取一半的数据, 但读完一半后剩下的一半又会触发父进程读取, 所以10个字节的数据都会显示出来
当设置边缘触发时, 父进程阻塞读取, 而只有当子进程向管道中进行一次写入时才会触发父进程进行读取, 所以每次只会打印一半的数据
总结:
- 边缘触发: 缓冲区未读尽的数据不会导致epoll_wait返回, 新的数据写入才会触发
- 水平触发: 缓冲区未读尽的数据会导致epoll_wait返回
基于网络C/S模型的epoll ET触发模式
1 | /* server.c */ |