> 文章列表 > Epoll的边缘触发ET为什么要搭配非阻塞I/O使用?

Epoll的边缘触发ET为什么要搭配非阻塞I/O使用?

Epoll的边缘触发ET为什么要搭配非阻塞I/O使用?

多路I/O复用有三种方法:select、poll、epoll;其中,select和poll默认采用水平触发的方式进行触发,而epoll可以选择水平触发和边缘触发,默认是水平触发。

水平触发LT

当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。使用水平触发模式时,当被监控的 Socket 上有可读事件发生时,服务器端不断地从 epoll_wait 中苏醒,直到内核缓冲区数据被 read 函数读完才结束,目的是告诉我们有数据需要读取

例子:假设我们的buffer设置为5个字节,某个socket的一次读事件的数据长度是9个字节,那么当epoll_wait()第一次通知处理程序去读之后,就只能读取5字节的数据,没读完,在使用水平触发的情况下,epoll_wait()就会再通知一次,让处理程序去读完剩下的4个字节。

同理,如果是14字节的话epoll_wait()就需要通知3次;如果是20字节的话epoll_wait()就需要通知4次......

LT的缺点:如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率

边缘触发ET 

当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。使用边缘触发模式时,当被监控的 Socket 描述符上有可读事件发生时,服务器端只会从 epoll_wait 中苏醒一次,即使进程没有调用 read 函数从内核读取数据,也依然只苏醒一次

例子:假设我们的buffer设置为5个字节,某个socket的一次读事件的数据长度是9个字节,那么当epoll_wait()第一次通知处理程序去读之后,就只能读取5字节的数据,没读完,在边缘触发的情况下,epoll_wait()不会再通知第二次,而是选择忽略这次事件的剩余数据,那么当下一次事件的数据到来时,上一次没读完的数据会导致本次事件的数据出错。

因此我们程序要保证一次性将内核缓冲区的数据读取完,怎么保证呢?就是使用while循环,一直执行读操作。

所以,边缘触发的正规用法就是要使用循环!!!而这也是边缘触发一般与非阻塞I/O搭配的原因!!!

阻塞I/O

我们称阻塞文件描述符为阻塞I/O。Socket中可能阻塞的API:accept, recv,connect,send

  • 当我们去对于一个阻塞文件描述符进行读取数据的时候,如果该文件描述符上没有数据可以进行读取,那么就会卡在调用的函数上面直到有数据可以进行读取。
  • 同样,如果我们对于一个阻塞文件描述符号进行写操作的时候,如果该缓冲区没有地方可写,那么就会卡在调用函数上直到有数据可写。

非阻塞I/O 

我们称非阻塞文件描述符为非阻塞I/O。

当你去读写一个非阻塞的文件描述符时,不管可不可以读写,它都会立即返回,返回成功说明读写操作完成了,返回失败会设置相应errno状态码,根据这个errno可以进一步执行其他处理。它不会像阻塞IO那样,卡在那里不动

阻塞I/O和非阻塞I/O的应用 

如上面的例子,我们的buffer的有5个字节,现在buffer里面是有数据的,那么不管调用的是阻塞I/O或非阻塞I/O都会完成读操作并成功返回;那如果buffer里面是没有数据的,调用非阻塞I/O时会返回error,我们可以通过判断返回值是不是error来看是不是没有数据可以读了;但是如果调用的是阻塞I/O,则没有数据读时,程序会一直卡在那一行等待读数据的到来。

ET+非阻塞I/O的原因

 ET要搭配while使用才可以保证在一次触发的前提下一次性读完/写完一个事件的数据;若现在这个事件的数据有9个字节,buffer有5个字节,ET是使用的阻塞I/O,则在while中不停地调用read时,第三次read会阻塞住,然后程序就卡在那里,直到有新数据进入buffer;若ET使用的是非阻塞I/O,则会在第三次read的时候返回error,然后跳出while。

对于LT,使用阻塞和非阻塞I/O的结果是一样的,因为LT不需要搭配while循环使用 !

玻璃百科