【问题标题】:Processbuilder without redirecting StdOutProcessbuilder 不重定向 StdOut
【发布时间】:2023-08-28 16:11:02
【问题描述】:

是否可以将输出流重定向回进程,或者根本不重定向?

背景故事: 我正在尝试使用 processbuilder 启动可执行文件。 (确切地说是源专用服务器/srcds.exe)

由于使用 processbuilder 启动它,这个可执行文件的控制台窗口仍然是空的。启动几秒钟后,可执行文件崩溃并显示错误“CTextConsoleWin32::GetLine: !GetNumberOfConsoleInputEvents”,因为它的控制台是空的。

【问题讨论】:

  • 奇怪。重定向StdOut 对我来说很好。对我来说,您的错误仅在尝试重定向StdIn 时发生。我目前的解决方法是使用RCON 而不是StdIn

标签: java stdout processbuilder


【解决方案1】:

我认为您是在谈论使启动的进程'stdout 转到当前进程'stdout。如果你使用的是JDK7,那很简单:

.redirectOutput(ProcessBuilder.Redirect.INHERIT)

更新:(评论太多了)我想你很困惑。当您从终端启动进程时,该进程将成为该终端进程的子进程,并将标准输出发送到该终端。当您从 Java 启动一个进程时,该进程是 Java 进程的子进程,其标准输出将转到 Java。

在第一种情况下,有一个显示 stdout 的终端,因为您自己从终端启动了它,这就是终端对 stdout 所做的事情。然而,当从 Java 启动时,不会有终端窗口,除非您启动的进程中的某些东西打开了一个终端,并且您启动的进程的标准输出被交还给程序员,您可以随意处理。从终端启动时看到的等效行为是我已经提到的Redirect.INHERIT

您现在的问题不是 Java。您的问题是不了解此“srcds.exe”如何处理标准输入和标准输出。弄清楚这一点,然后回来询问如何用 Java 做到这一点。

我现在只是在猜测,但您可以尝试从进程的标准输出中读取并将其反馈回标准输入。也许这就是它所期待的?不过,这听起来很疯狂。

【讨论】:

  • 那行不通。它现在做的事情与读取 Inputstream 并打印它完全相同。我需要的是让它不读取输出,这样子进程就不会得到它的输出“被盗”。
  • 那么请说明您要达到的目标。不读取输出最终会导致生成的进程冻结。这不是你要找的。您必须读取进程的输出并对其进行处理。
  • 我正在启动的应用程序的控制台窗口通常会打印一些内容。通过使用 processbuilder 启动它,输出到我的 java 而不是那个控制台窗口。因为这个控制台窗口仍然是空的,所以应用程序崩溃了,因为它在里面有行。我想要做的是让这些行仍然出现在子进程的控制台窗口中,这样它就不会崩溃。
  • 问题是 srcds.exe 启动了自己的终端。用 java 启动它会导致所有正常进入该终端的内容进入 java(终端仍然出现,但仍然是空的),当它尝试读取自己的终端时,它会崩溃。关键是在不重定向标准输出的情况下启动进程。
【解决方案2】:

你可以得到这样的输出

ProcessBuilder pb = new ProcessBuilder(args);
Process p = pb.start(); 

//below code gets the output from the process
InputStream in = p.getInputStream();
BufferedInputStream buf = new BufferedInputStream(in);
InputStreamReader inread = new InputStreamReader(buf);
BufferedReader bufferedreader = new BufferedReader(inread);
String line;
while ((line = bufferedreader.readLine()) != null) {
    *do something / collect output*
}

【讨论】:

  • 请注意,API 文档不鼓励在这种情况下进行缓冲。见这里:docs.oracle.com/javase/1.5.0/docs/api/java/lang/…
  • 你刚刚给我的代码正是造成问题的代码。这不是我问题的答案。
  • @vizier:您的链接有点损坏 - 它不会跳转到 getInputStream()。然而,getInputStream() 文档中关于缓冲的讨论说“实现说明:缓冲输入流是一个好主意。”也许您想将我们的注意力引向开篇(类级别)文档,该文档说:“由于一些原生平台只为标准输入和输出流提供有限的缓冲区大小,未能及时写入输入流或读取子进程的输出流可能会导致子进程阻塞,甚至死锁。” ?
【解决方案3】:

我已经为此苦苦挣扎了一段时间,但我想你可以做的最简单的方法是自己传输流,使用某种 StreamTransfer 类,你可以说哪个InputStream 被写入哪个OutputStream在单独的线程中以避免死锁。 在这个例子中,我执行ls然后cat并手动将lsstdout连接到catstdin,然后将catstdout连接到System.out进行打印最终结果:

public class TestRedirectingStreams {

    public static void main(String[] args) throws IOException, InterruptedException {
        ExecutorService threads = Executors.newFixedThreadPool(10); 
        Process echo = Runtime.getRuntime().exec("ls"),
                cat = Runtime.getRuntime().exec("cat");
        threads.submit(StreamTransfer.transfer(echo.getInputStream(), cat.getOutputStream()));
        threads.submit(StreamTransfer.transfer(cat.getInputStream(), System.out));
        threads.shutdown();
    }
}

class StreamTransfer implements Callable<Void> {
    public static final int BUFFER_SIZE = 1024;
    private InputStream in;
    private OutputStream out;

    public static StreamTransfer transfer(InputStream in, OutputStream out) {
        return new StreamTransfer(in, out);
    }

    private StreamTransfer(InputStream in, OutputStream out) {
        this.in = in;
        this.out = out;
    }

    @Override
    public Void call() throws Exception { // write to streams when thread executes
        byte[] buffer = new byte[BUFFER_SIZE];
        int read = 0;
        while ((read = in.read(buffer)) != -1) 
            out.write(buffer, 0, read);
        in.close();
        out.close();
        return null;
    }
}

如果您想改用 ProcessBuilder,示例保持不变,因为 ProcessBuilder.start() 返回进程,您仍然可以检索所需的流并进行相应的传输。

【讨论】: