为什么InputStreamReader不能实时读取进程的输出?

3
我将使用ProcessBuilder来运行一些外部命令,例如Linux中的ifstatvmstat
这些命令支持自定义采样间隔。如果我向外部命令添加一个采样间隔,例如ifstat 20,则该命令将输出如下内容:
coolcfan@coolcfan-PC:~$ ifstat 20
       eth0               wlan0               vmnet1              vmnet8      
 KB/s in  KB/s out   KB/s in  KB/s out   KB/s in  KB/s out   KB/s in  KB/s out

20秒后

   41.29      1.06      0.36      0.00      0.00      0.00      0.00      0.00

再过20秒

   16.67      0.58      0.38      0.00      0.00      0.00      0.00      0.00

然而,当我使用我的Java代码运行命令时,输出的第一部分将在20秒后读取,就像这样:

Start running "ifstat 20"

20秒后

       eth0               wlan0               vmnet1              vmnet8      
 KB/s in  KB/s out   KB/s in  KB/s out   KB/s in  KB/s out   KB/s in  KB/s out
   66.61      1.73      1.29      0.01      0.00      0.00      0.00      0.00

当我使用NIO服务器运行命令并使用SocketChannel将输出发送给客户端时,问题变得更加严重...(在服务器显示第一个输出后,我的客户端需要等待另外20秒才能获得输出,这是进程启动后20秒)。
我注意到输出延迟的长度与我设置的命令间隔有关。
那么为什么ISR不能实时读取输出呢?
以下是一个简单的测试代码片段以演示我的问题:
public static void main(String[] args) {
    ProcessBuilder pb = new ProcessBuilder();

    pb.command("ifstat 20".trim().split(" "));

    Process p = null;

    System.out.println("Start running \"ifstat 20\"");

    try {
        p = pb.start();

        char[] buf = new char[512];

        InputStreamReader isr = new InputStreamReader(p.getInputStream());

        int count = -1;

        while ((count = isr.read(buf, 0, buf.length)) != -1) {
            System.out.print(new String(buf, 0, count));
        }
    }
    catch (IOException ioe) {
    }
}

更新:

根据Peter的评论,这不是Java相关的问题。这是一个管道延迟

但我仍然不明白为什么vmstat 20 | cat没有这个延迟,而ifstat 20 | cat会延迟显示头部?


2
ISR读取任何你得到的内容。为了比较,请运行ifstat 20 | cat,这样它就会写入管道而不是控制台。 - Peter Lawrey
@PeterLawrey 这是一个管道问题吗?实际上我需要在真实环境中运行 ifstat 120... - Dante WWWW
1
数据似乎没有延迟,只有标题出现了问题。 - Peter Lawrey
@PeterLawrey 好的,嗯,是啊... 但是为什么标题迟迟不显示呢? - Dante WWWW
我怀疑它没有被显式地刷新。当写入控制台时,它会自动刷新,但当写入管道时,它需要显式刷新,直到第一行数据出现之前它才会得到刷新。 - Peter Lawrey
1个回答

2

确实如此。

这是进程输出被延迟的结果。STDIO进行缓冲,这取决于stout是否为终端。


2
在C程序中,@coolcfan的fflush()函数是无法从外部进行操作的。 - user207421
你唯一能做的就是下载ifstat的源代码,在头部写入后添加fflush(),然后使用这个版本。 - Peter Lawrey
1
值得注意的是,一些程序在写入终端时会检测到 tty 并进行较少的缓冲,而在写入其他目标(例如文件或管道)时则不同。即使输出不会进入终端,您也可以创建一个伪 tty 来减少缓冲。 - Ben Voigt
@EJP +1 对于 "C程序中的fflush()",这就是我寻找了8个小时最终能够正常工作的解决方案。我仍然想知道为什么它会起作用?有什么诀窍吗? - Never Quit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接