无法使用Apache Commons Exec为命令提供多个输入并提取输出

我正在编写一个Java应用程序,需要使用Apache Commons Exec库来使用外部命令行应用程序。 我需要运行的应用程序具有相当长的加载时间,因此最好保持一个实例处于活动状态而不是每次都创建一个新进程。 应用程序的工作方式非常简单。 一旦启动,它会等待一些新输入并生成一些数据作为输出,两者都使用应用程序的标准I / O.

因此,我们的想法是执行CommandLine,然后将PumpStreamHandler与三个独立的流(输出,错误和输入)一起使用,并使用这些流与应用程序进行交互。 到目前为止,我已经在基本场景中完成了这项工作,我有一个输入,一个输出,然后应用程序关闭。 但是一旦我试图进行第二次交易,就会出现问题。

在创建了我的CommandLine之后,我创建了我的Executor并像这样启动它:

this.executor = new DefaultExecutor(); PipedOutputStream stdout = new PipedOutputStream(); PipedOutputStream stderr = new PipedOutputStream(); PipedInputStream stdin = new PipedInputStream(); PumpStreamHandler streamHandler = new PumpStreamHandler(stdout, stderr, stdin); this.executor.setStreamHandler(streamHandler); this.processOutput = new BufferedInputStream(new PipedInputStream(stdout)); this.processError = new BufferedInputStream(new PipedInputStream(stderr)); this.processInput = new BufferedOutputStream(new PipedOutputStream(stdin)); this.resultHandler = new DefaultExecuteResultHandler(); this.executor.execute(cmdLine, resultHandler); 

然后我继续启动三个不同的线程,每个线程处理一个不同的流。 我还有三个处理输入和输出的SynchronousQueues(一个用作输入流的输入,一个用于通知outputQueue已经启动了一个新命令而一个用于输出)。 例如,输入流线程如下所示:

 while (!killThreads) { String input = inputQueue.take(); processInput.write(input.getBytes()); processInput.flush(); IOQueue.put(input); } 

如果我删除while循环并执行一次,一切似乎都完美无缺。 显然,如果我再次尝试执行它,PumpStreamHandler会抛出exception,因为它已被两个不同的线程访问。

这里的问题是,在线程结束之前,似乎没有真正刷新processInput。 调试时,命令行应用程序仅在线程结束时才真正接收其输入,但如果保留while循环则永远不会获得它。 我已经尝试了许多不同的东西来使processInput刷新,但似乎没有任何工作。

以前有人尝试过类似的东西吗? 有什么我想念的吗? 任何帮助将不胜感激!

我最终找到了一种方法来完成这项工作。 通过查看Commons Exec库的代码,我注意到PumpStreamHandler使用的StreamPumpers每次进入时都没有刷新。 这就是我执行一次代码时代码工作的原因,因为它会自动刷新并关闭流。 所以我创建了一个名为AutoFlushingStreamPumper和AutoFlushingPumpStreamHandler的类。 后者与普通的PumpStreamHandler相同,但使用AutoFlushingStreamPumpers而不是通常的。 AutoFlushingStreamPumper与标准StreamPumper的作用相同,但每次向其写入内容时都会刷新其输出流。

我已经对它进行了相当广泛的测试,看起来效果很好。 感谢所有试图解决这个问题的人!

为了我的目的,事实certificate我只需要覆盖“ExecuteStreamHandler”。 这是我的解决方案,它将stderr捕获到StringBuilder中,并允许您将内容流式传输到stdin并从stdout接收内容:

 class SendReceiveStreamHandler implements ExecuteStreamHandler 

你可以在这里看到全class作为GitHub的要点。

为了能够在进程的STDIN中编写多个命令,我创建了一个新命令

 import java.io.BufferedWriter; import java.io.File; import java.io.IOException; import java.io.OutputStreamWriter; import java.util.Map; import org.apache.commons.exec.CommandLine; import org.apache.commons.exec.DefaultExecutor; import org.apache.commons.lang3.CharEncoding; public class ProcessExecutor extends DefaultExecutor { private BufferedWriter processStdinput; @Override protected Process launch(CommandLine command, Map env, File dir) throws IOException { Process process = super.launch(command, env, dir); processStdinput = new BufferedWriter(new OutputStreamWriter(process.getOutputStream(), CharEncoding.UTF_8)); return process; } /** * Write a line in the stdin of the process. * * @param line * does not need to contain the carriage return character. * @throws IOException * in case of error when writing. * @throws IllegalStateException * if the process was not launched. */ public void writeLine(String line) throws IOException { if (processStdinput != null) { processStdinput.write(line); processStdinput.newLine(); processStdinput.flush(); } else { throw new IllegalStateException(); } } } 

为了使用这个新的Executor,我将管道流保存在PumpStreamHandler中,以避免PumpDreamHandler关闭STDIN。

 ProcessExecutor executor = new ProcessExecutor(); executor.setExitValue(0); executor.setWorkingDirectory(workingDirectory); executor.setWatchdog(new ExecuteWatchdog(ExecuteWatchdog.INFINITE_TIMEOUT)); executor.setStreamHandler(new PumpStreamHandler(outHanlder, outHanlder, new PipedInputStream(new PipedOutputStream()))); executor.execute(commandLine, this); 

您可以使用执行程序writeLine()方法或创建自己的方法。