7

我写的是需要使用使用Apache下议院Exec的图书馆外部命令行应用程序的Java应用程序命令提供多种输入。我需要运行的应用程序有一个相当长的加载时间,所以最好让一个实例保持活动状态,而不是每次都创建一个新进程。应用程序的工作方式非常简单。一旦启动,它会等待一些新的输入并生成一些数据作为输出,这两者都使用应用程序的标准I/O。麻烦使用Apache下议院Exec和提取输出

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

在已经建立了我的CommandLine,创建我的遗嘱执行人,并启动它,像这样:

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将抛出一个异常,因为它已被两个不同的线程访问。

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

有没有人尝试过类似的东西?有什么我失踪?任何帮助将不胜感激!

+0

你应该为你的帖子添加一个java标签,以便为你的问题提供最有经验的“眼睛”。祝你好运。 – shellter

回答

8

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

我已经非常广泛的测试,它似乎运作良好。感谢所有试图解决这个问题的人!

+2

帮兄弟出来?我有同样的问题,你可以做一个“坚实”,并发布你写的代码(AutoFlushingStreamPumper和AutoFlushingPumpStreamHandler)在这里或者要点什么?没有意义重新发明那个轮子......感谢您的发布! – GroovyCakes

+2

这非常有帮助,但正如@GroovyCakes提到的,Gist会帮助更多 - 所以这里有一个。请注意,这是我正在使用的,而不是OP所使用的。 https://gist.github.com/4653381 –

+0

是否存在使用AutoFlushingStreamPumper和AutoFlushingPumpStreamHandler的示例? – Johan

1

对于我而言,事实证明,我只需要重写“ExecuteStreamHandler”。这里是我的解决方案,它捕获标准错误到一个StringBuilder,并且您可以传输的东西标准输入和标准输出,从收到的东西:

class SendReceiveStreamHandler implements ExecuteStreamHandler 

你可以看到整个班级为GitHub上here一个要点。

+0

在您的代码中,Receiver,TransferCompleteEvent和DataReceivedEvent不在您的导入中。什么软件包包含这些类? – Yeti

0

为了能够在这个过程中的标准输入写多个命令,我必须创建一个新的

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()方法或创建自己的。