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

Posted

技术标签:

【中文标题】使用 Apache Commons Exec 向命令提供多个输入并提取输出时出现问题【英文标题】:Trouble providing multiple input to a Command using Apache Commons Exec and extracting output 【发布时间】:2011-10-30 02:12:24 【问题描述】:

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

所以想法是执行命令行,然后使用 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);

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

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

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

    IOQueue.put(input);

如果我删除 while 循环并只执行一次,一切似乎都运行良好。显然,如果我再次尝试执行它,PumpStreamHandler 会抛出异常,因为它已被两个不同的线程访问。

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

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

【问题讨论】:

您应该在您的帖子中添加一个 java 标签,让最有经验的“眼睛”关注您的问题。祝你好运。 【参考方案1】:

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

我已经对它进行了相当广泛的测试,它似乎运行良好。感谢所有试图解决这个问题的人!

【讨论】:

帮兄弟出去?我有同样的问题,你能给我做一个“坚实的”,然后把你写的代码(AutoFlushingStreamPumper 和 AutoFlushingPumpStreamHandler)贴在这里或要点什么的吗?重新发明那个***没有意义...感谢您的帖子! 这帮助很大,但正如@GroovyCakes 所提到的,Gist 会提供更多帮助——所以这里有一个。请注意,这是我正在使用的,不一定是 OP 使用的。 gist.github.com/4653381 是否存在使用 AutoFlushingStreamPumper 和 AutoFlushingPumpStreamHandler 的示例?【参考方案2】:

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

class SendReceiveStreamHandler implements ExecuteStreamHandler

您可以在 GitHub here 上将整个课程视为要点。

【讨论】:

在您的代码中,Receiver、TransferCompleteEvent 和 DataReceivedEvent 不在您的导入中。什么包包含这些类?【参考方案3】:

为了能够在进程的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 中,以避免 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);

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

【讨论】:

以上是关于使用 Apache Commons Exec 向命令提供多个输入并提取输出时出现问题的主要内容,如果未能解决你的问题,请参考以下文章

apache进程框架commons-exec,Java

Java运行外部程序(Apache Commons Exec)

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

如何在没有 IDE 的情况下使用 Apache Commons Lang 代码? (org.apache.commons.lang3)

使用Apache Commons Email 发生邮件

使用 org.apache.commons.validator 验证 IBAN