Java 使用Apache Commons Exec向命令提供多个输入并提取输出时出现问题

Java 使用Apache Commons Exec向命令提供多个输入并提取输出时出现问题,java,multithreading,command-line,inputstream,apache-commons-exec,Java,Multithreading,Command Line,Inputstream,Apache Commons Exec,我正在编写一个Java应用程序,它需要使用ApacheCommonsExec库使用外部命令行应用程序。我需要运行的应用程序有相当长的加载时间,因此最好保持一个实例处于活动状态,而不是每次都创建一个新进程。应用程序的工作方式非常简单。一旦启动,它将等待一些新的输入并生成一些数据作为输出,这两个数据都使用应用程序的标准I/O 因此,我们的想法是执行命令行,然后将PumpStreamHandler与三个单独的流(输出、错误和输入)一起使用,并使用这些流与应用程序交互。到目前为止,我已经在基本场景中完成

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

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

创建命令行后,我创建了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);
然后我继续启动三个不同的线程,每个线程处理不同的流。我还有三个处理输入和输出的SynchronousQueue(一个用作输入流的输入,一个通知outputQueue新命令已启动,一个用于输出)。例如,输入流线程如下所示:

while (!killThreads) {
    String input = inputQueue.take();

    processInput.write(input.getBytes());
    processInput.flush();

    IOQueue.put(input);
}
如果我删除while循环并只执行一次,那么一切似乎都可以完美地工作。显然,如果我再次尝试执行它,PumpStreamHandler会抛出一个异常,因为它已被两个不同的线程访问

这里的问题是,直到线程结束,processInput才真正刷新。调试时,命令行应用程序只有在线程结束时才真正接收其输入,但如果保持while循环,则永远不会获得输入。我尝试了许多不同的方法来让processInput刷新,但似乎没有任何效果


以前有人做过类似的尝试吗?我有什么遗漏吗?任何帮助都将不胜感激

我终于想出了一个办法让这一切顺利进行。通过查看Commons Exec库的代码,我注意到PumpStreamHandler使用的StreamPumper并没有在每次收到新数据时刷新。这就是为什么我只执行一次代码就可以工作,因为它会自动刷新并关闭流。所以我创建了我称之为AutoFlushingStreamPumper和AutoFlushingPumpStreamHandler的类。后者与普通的PumpStreamHandler相同,但使用AutoFlushingStreamPumpers而不是普通的PumpStreamHandler。AutoFlushingStreamPumper的功能与标准StreamPumper相同,但每次写入内容时都会刷新其输出流


我已经对它进行了广泛的测试,它似乎工作得很好。感谢所有试图解决这个问题的人

出于我的目的,我只需要重写“ExecuteStreamHandler”。以下是我的解决方案,它将stderr捕获到StringBuilder中,并允许您将内容流式传输到stdin并从stdout接收内容:

class SendReceiveStreamHandler implements ExecuteStreamHandler

您可以在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();
        }
    }

}
为了使用这个新的执行器,我将管道流保留在PumpStreamHandler中,以避免STDIN靠近PumpStreamHandler

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);

您可以使用executor writeLine()方法或创建自己的方法

你应该在你的文章中添加一个java标签,让最有经验的人看到你的问题。祝你好运,帮个兄弟吗?我也有同样的问题,你能给我一个“可靠”的答案,然后把你写的代码(AutoFlushingStreamPumper和AutoFlushingPumpStreamHandler)发布到这里,或者发布到要点或其他地方吗?重新发明那个轮子毫无意义。。。谢谢你的帖子!这非常有帮助,但正如@GroovyCakes所提到的,一个要点会更有用——所以这里有一个。注意这是我正在使用的,而不是OP所使用的。是否存在使用AutoFlushingStreamPumper和AutoFlushingPumpStreamHandler的示例?在您的代码中,Receiver、TransferCompleteEvent和DataReceivedEvent不在导入中。什么包包含这些类?