非阻塞文件结束

20 投票
5 回答
2787 浏览
提问于 2025-04-16 15:38

在非阻塞模式下,怎么检测文件的结束呢?

5 个回答

1

在C++中,有一个不错的小技巧(具体效果可能因情况而异),就是如果返回的数据量小于缓冲区的大小(也就是说,缓冲区没有被填满),你可以安全地认为这个操作已经完成。这样的话,最后一部分文件完全填满缓冲区的概率是1/缓冲区大小,所以如果缓冲区很大,你可以相对确定操作会以一个没有填满的缓冲区结束。因此,如果你把返回的数据量和缓冲区的大小进行比较,如果它们不相等,你就知道要么发生了错误,要么操作已经完成。我不确定这个方法能否在Python中使用,但这就是我用来判断文件结束的方法。

5

这个问题问得很好。非阻塞套接字在调用recv()时,如果没有数据可用,它会返回一个空字符串,而不是抛出一个socket.error错误来表示没有数据可用。不过,对于文件来说,Python似乎没有直接的方式来判断这一点。

我能想到的唯一检测文件结束(EOF)的方法,就是在收到空字符串后,将当前文件的位置和文件的总大小进行比较:

def read_nonblock( fd ):
    t = os.read(fd, 4096)
    if t == '':
        if os.fstat(fd).st_size == os.lseek(fd, 0, os.SEEK_CUR):
            raise Exception("EOF reached")
    return t

当然,这个方法假设在非阻塞模式下,常规文件会立即返回,而不是等待从磁盘读取数据。我不确定在Windows或Linux上是否真的如此。值得测试一下,但我不会感到惊讶,如果在非阻塞模式下读取常规文件时,只有在真正遇到文件结束时才会返回空字符串。

15

在POSIX系统(包括Linux)上,简单来说,非阻塞的常规文件是不存在的。常规文件总是会阻塞,而O_NONBLOCK这个选项会被默默忽略。

同样,使用poll()/select()等方法时,无论数据是已经在页面缓存中还是仍然在磁盘上,指向常规文件的文件描述符总是会告诉你可以进行输入输出操作(这主要适用于读取)。

编辑 另外,由于O_NONBLOCK对常规文件没有任何作用,读取常规文件时不会把errno设置为EAGAIN,这与其他回答的说法相反。

编辑2 参考资料:

根据POSIX的(p)select()规范:“与常规文件关联的文件描述符在准备读取、准备写入和错误条件下总是返回真。”

根据POSIX的poll()规范:“常规文件在读取和写入时总是返回真。”

以上内容足以说明,虽然非阻塞的常规文件可能不被严格禁止,但实际上没有意义,因为除了忙等待之外没有其他方式来轮询它们。

除此之外,还有一些间接证据。

根据POSIX的open()规范:关于指向管道、块特殊文件和字符特殊文件的文件描述符的行为是有定义的。“否则,O_NONBLOCK的行为是未定义的。”

一些相关链接:

http://tinyclouds.org/iocp-links.html

http://www.remlab.net/op/nonblock.shtml

http://davmac.org/davpage/linux/async-io.html

甚至在stackoverflow上也有相关讨论:

常规文件读取能从非阻塞IO中受益吗?

正如R.的回答所指出的,由于页面缓存的工作方式,常规文件的非阻塞行为并不容易定义。例如,如果通过某种机制你发现页面缓存中的数据可以读取,但在你读取之前,内核因为内存压力决定将该页面从缓存中踢出去,这就会出现问题。对于套接字和管道等情况则不同,因为正确性要求数据不能被随意丢弃。

另外,你如何选择或轮询一个可寻址的文件描述符呢?你需要一个新的API来指定你感兴趣的文件字节范围。而这个API的内核实现需要与虚拟内存系统结合,因为它需要防止你感兴趣的页面被踢出。这意味着这些页面会计入进程的锁定页面限制(参见ulimit -l),以防止拒绝服务攻击。那么,这些页面什么时候会被解锁呢?等等。

撰写回答