如何读取附加到(又名,如 tail -f)的文件的 [非阻塞] 文件描述符?

How to read a [nonblocking] filedescriptor of a file that is appended to (aka, like tail -f)?

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

当我已经读到最后时,得到通知文件中有 additional/appended 数据可以读取的正确方法是什么?

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

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

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

很多人心中的矛盾只是他们想要被告知"when there is new data",但如果你稍微想一想,你会发现仅仅叫醒你并不是一个合适的界面,因为你无法知道你为什么醒来,以及发生了什么变化。

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

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

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

这仍然没有告诉您文件的哪一部分发生了变化。

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

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

None 这很容易,详细信息取决于您对文件更改的准确程度的了解,但这是您能做的最好的。