1

实际上,我正在使用 libev;但在幕后,这是使用 epoll (我只在 linux 上)。当我添加一个观察者来读取文件并且所有数据都已读取时,我确实会收到一个回调,表明有数据要读取,但 read(2) 返回 0 (EOF)。那时我必须停止观察者,否则它会继续告诉我有东西要读。但是,如果我停止观察程序,然后其他一些进程将数据附加到该文件,那么我将永远看不到它。

在我已经阅读到最后之前,获得通知文件中可以读取的附加/附加数​​据的正确方法是什么?

我更喜欢关于 libev 的答案,但较低级别也可以(然后我可以将其转换为如何使用 libev 来做到这一点)。

4

1 回答 1

7

出于某种原因,人们通常认为使 fd 成为非阻塞或调用 poll/select/.. 与其他类型的文件描述相比对文件具有不同的行为,但非阻塞行为和 I/O 就绪行为是对于所有类型的文件描述基本相同:如果结果已知,内核将立即从读/写等返回,并在这种情况下发出 I/O 就绪信号。当套接字具有 EOF 条件时,select 将发出套接字已准备好读取的信号,您将得到 0(表示 EOF)。文件也会发生同样的情况 - 如果您在文件末尾,内核将立即从读取返回并返回 0 以发出 EOF 信号。

重要的区别是文件可以在随机位置更改内容,并且可以扩展。管道和套接字不是随机访问,一旦关闭就不能附加。因此,虽然行为是一致的,但通常不是想要的,即等待文件以某种方式更改。

很多人心里的矛盾只是想被告知“什么时候有新数据”,但是稍微想一想,你就会发现,仅仅唤醒你并不足以解决这个问题,因为你无法知道你为什么醒来,以及发生了什么变化。

POSIX 没有执行此操作的接口,除了定期轮询 fd 或文件(并且在随机更改的情况下,定期读取整个文件!)。一些操作系统有一个接口来做类似的事情(BSD 上的 kqueue,GNU/Linux 上的 inotify),但它们通常也不是完美的匹配(例如,inotify 不能监视fd的变化,它会监视改变路径)。

与 libev 最接近的是使用ev_stat观察者。它的行为就像您stat()定期使用路径一样,并在统计数据更改时调用观察者回调。可移植的,它就是这样做的:它定期调用stat,但在某些操作系统上(目前仅在 GNU/Linux 上为 inotify,因为 kqueue 对此没有正确的语义)它可以在某些情况下使用其他机制来加速它,尽管它将回退到stat各处的常规轮询,例如当文件位于网络文件系统上时,inotify 无法看到远程更改。

回答你的问题:如果你有一个路径,你可以使用一个ev_stat观察者来观察统计数据的变化,比如大小/时间等变化。正确执行此操作可能有点棘手(请参阅 libev 文档,尤其是有关统计时间分辨率的部分:http://pod.tst.eu/http: //cvs.schmorp.de/libev/ev.pod#code_ev_stat_code_did_the_file_attri) ,并且您必须记住,这会监视路径,而不是文件描述符,因此您可能需要定期比较文件描述符的设备/inode 和监视的路径,以查看是否仍然打开了正确的文件。

这仍然不能告诉您文件的哪一部分已更改。

或者,由于您显然只想读取附加数据,您可以选择read()定期(在ev_timer回调中)只读取文件并消除ev_stat观察者设置的所有复杂性和麻烦(同时不要忘记将路径统计数据与你的 fd stat 数据,看看你是否仍然打开了正确的文件,这取决于你正在阅读的文件是否会被重命名或替换。有时程序也会截断文件,你也可以通过查看 stat 调用之间的大小减小来检测)。

这基本上是旧tail -f实现所做的,而较新的实现可能,例如,(仅)从 inotify 中获取提示,就像ev_stat观察者一样。

这一切都不是一件容易的事,细节取决于您对文件更改方式的了解,但这是您能做的最好的事情。

于 2018-07-25T22:31:02.627 回答