Epoll TCP边缘触发最后一次读取(2)调用的必要性

时间:2014-01-14 10:20:16

标签: linux sockets tcp epoll epollet

给定非阻塞TCP套接字,如果调用

read(sock, buf, bufLen)

返回一个值< bufLen,等待边缘触发的EPOLLIN事件是否安全?或者我必须再次致电read以确保它为零或EAGAIN吗?

在我的测试中,当我删除最后一个调用时,一切都保持工作,我只是想知道它是否在任何地方保证,或者是Linux源代码,以及我是否可以摆脱额外的调用。

2 个答案:

答案 0 :(得分:4)

您的问题已在man 7 epoll中得到解答。如您所见,它取决于套接字类型(数据包/流):

Q9 使用EPOLLET标志(边缘触发行为)时,是否需要连续读取/写入文件描述符,直到EAGAIN?

A9 从epoll_wait(2)接收事件应该建议您为所请求的I / O操作准备好此类文件描述符。您必须考虑它准备好,直到下一个(非阻塞)读/写产生EAGAIN。您何时以及如何使用文件描述符完全取决于您。

对于面向数据包/令牌的文件(例如,数据报套接字,规范模式下的终端),检测读/写I / O空间结束的唯一方法是继续读/写,直到EAGAIN。

对于面向流的文件(例如,管道,FIFO,流套接字),还可以通过检查从目标文件读取/写入的数据量来检测读/写I / O空间耗尽的情况。描述。 例如,如果通过要求读取一定数量的数据来调用read(2)并且read(2)返回较少的字节数,则可以确保已经耗尽了文件的读取I / O空间描述。使用write(2)进行写入时也是如此。 (如果您不能保证受监视的文件描述符始终引用面向流的文件,请避免使用后一种技术。)

答案 1 :(得分:2)

它是“安全的”只要它不会崩溃,但除非你继续调用read直到你得到EAGAIN(或者为零,这意味着另一端)已关闭连接),您有时会对数据的可用性做出错误的假设。最糟糕的是,它大部分时间看起来都能正常工作。

边缘触发与水平触发通知相反,只保证自上次调用epoll_wait后准备状态发生变化时,您会收到一个通知,即使数据仍然存在你可以读。
边缘触发的事件通知 在Linux下有点奇怪或不直观,因此可能做出与您期望的不同的事情,例如当有更多数据到达时给你另一个通知(所以你的代码似乎“无论如何”工作),但这不是保证的。
epolleventfd一起使用时,我有类似的“惊喜”。在边缘触发模式下你期望发生的是所有已经被阻塞的线程(同时,只有一次),并且在事件发生之后调用epoll_wait的每个人都会阻塞,直到事件已消耗并再次发出信号。它真正做的是唤醒调用epoll_wait第一个线程。再次惊喜,水平触发模式完全按照您的意愿工作,除非您必须消耗该事件才能再次准备它,因为没有正确的方法(因为您必须这样做一次或者您将阻止read)。

因此,如果您不消耗所有数据,然后等待再次收到通知,您可能会很幸运,它会“无论如何”工作,或者您可能会等待很长时间,可能永远。因此,我的建议是绝对继续阅读,直到你得到EAGAIN,这是避免意外的唯一真正可靠的事情。

请注意,如果你保持天真的阅读,你可能会使慢发送者饿死。如果你有一个非常快的发送者,你继续阅读快速发送者,那么你将永远不会看到EAGAIN(至少不会在另一端不断发送!),你将完全饿死其他发件人。
因此,将所有准备好的描述符放在列表中并循环读取它们是有意义的,当它们返回EAGAIN时将它们从列表中删除。