在前一篇博文中,简单介绍了如何使用Process类来调用命令行的功能,那样使用Process会有一个很大的问题,就是可能会出现无限阻塞的状况,永远都没法返回结果。如下是Process的API说明,注意加粗的部分。java
ProcessBuilder.start() 和 Runtime.exec 方法建立一个本机进程,并返回 Process 子类的一个实例,该实例可用来控制进程并得到相关信息。Process 类提供了执行从进程输入、执行输出到进程、等待进程完成、检查进程的退出状态以及销毁(杀掉)进程的方法。
建立进程的方法可能没法针对某些本机平台上的特定进程很好地工做,好比,本机窗口进程,守护进程,Microsoft Windows 上的 Win16/DOS 进程,或者 shell 脚本。建立的子进程没有本身的终端或控制台。它的全部标准 io(即 stdin、stdout 和 stderr)操做都将经过三个流 (getOutputStream()、getInputStream() 和 getErrorStream()) 重定向到父进程。父进程使用这些流来提供到子进程的输入和得到从子进程的输出。由于有些本机平台仅针对标准输入和输出流提供有限的缓冲区大小,若是读写子进程的输出流或输入流迅速出现失败,则可能致使子进程阻塞,甚至产生死锁。shell
解决进程无限阻塞的方法是在执行命令时,设置一个超时时间,下面提供一个工具类,对Process使用进行包装,向外提供设置超时的接口。apache
public class ExecuteResult { @Override public String toString() { return "ExecuteResult [exitCode=" + exitCode + ", executeOut=" + executeOut + "]"; } private int exitCode; private String executeOut; public ExecuteResult(int exitCode, String executeOut) { super(); this.exitCode = exitCode; this.executeOut = executeOut; } public int getExitCode() { return exitCode; } public void setExitCode(int exitCode) { this.exitCode = exitCode; } public String getExecuteOut() { return executeOut; } public void setExecuteOut(String executeOut) { this.executeOut = executeOut; } }
public interface LocalCommandExecutorService { ExecuteResult executeCommand(String[] command, long timeout); }
public class LocalCommandExecutorServiceImpl implements LocalCommandExecutorService { static final Logger logger = LoggerFactory .getLogger(LocalCommandExecutorServiceImpl.class); static ExecutorService pool = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 3L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); @Override public ExecuteResult executeCommand(String[] command, long timeout) { Process process = null; InputStream pIn = null; InputStream pErr = null; StreamGobbler outputGobbler = null; StreamGobbler errorGobbler = null; Future<Integer> executeFuture = null; try { process = Runtime.getRuntime().exec(command); final Process p = process; //close process's output stream. p.getOutputStream().close(); pIn = process.getInputStream(); outputGobbler = new StreamGobbler( pIn, "OUTPUT"); outputGobbler.start(); pErr = process.getErrorStream(); errorGobbler = new StreamGobbler(pErr, "ERROR"); errorGobbler.start(); // create a Callable for the command's Process which can be called // by an Executor Callable<Integer> call = new Callable<Integer>() { public Integer call() throws Exception { p.waitFor(); return p.exitValue(); } }; // submit the command's call and get the result from a executeFuture = pool.submit(call); int exitCode = executeFuture.get(timeout, TimeUnit.MILLISECONDS); return new ExecuteResult(exitCode, outputGobbler.getContent()); } catch (IOException ex) { String errorMessage = "The command [" + command + "] execute failed."; logger.error(errorMessage, ex); return new ExecuteResult(-1, null); } catch (TimeoutException ex) { String errorMessage = "The command [" + command + "] timed out."; logger.error(errorMessage, ex); return new ExecuteResult(-1, null); } catch (ExecutionException ex) { String errorMessage = "The command [" + command + "] did not complete due to an execution error."; logger.error(errorMessage, ex); return new ExecuteResult(-1, null); } catch (InterruptedException ex) { String errorMessage = "The command [" + command + "] did not complete due to an interrupted error."; logger.error(errorMessage, ex); return new ExecuteResult(-1, null); } finally { if(executeFuture != null){ try{ executeFuture.cancel(true); } catch(Exception ignore){} } if(pIn != null) { this.closeQuietly(pIn); if(outputGobbler != null && !outputGobbler.isInterrupted()){ outputGobbler.interrupt(); } } if(pErr != null) { this.closeQuietly(pErr); if(errorGobbler != null && !errorGobbler.isInterrupted()){ errorGobbler.interrupt(); } } if (process != null) { process.destroy(); } } } private void closeQuietly(Closeable c) { try { if (c != null) c.close(); } catch (IOException e) { } } }
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class StreamGobbler extends Thread { private static Logger logger = LoggerFactory.getLogger(StreamGobbler.class); private InputStream inputStream; private String streamType; private StringBuilder buf; private volatile boolean isStopped = false; /** * Constructor. * * @param inputStream * the InputStream to be consumed * @param streamType * the stream type (should be OUTPUT or ERROR) * @param displayStreamOutput * whether or not to display the output of the stream being * consumed */ public StreamGobbler(final InputStream inputStream, final String streamType) { this.inputStream = inputStream; this.streamType = streamType; this.buf = new StringBuilder(); this.isStopped = false; } /** * Consumes the output from the input stream and displays the lines * consumed if configured to do so. */ @Override public void run() { try { //默认编码为UTF-8,这里设置编码为GBK,由于WIN7的编码为GBK InputStreamReader inputStreamReader = new InputStreamReader( inputStream,"GBK"); BufferedReader bufferedReader = new BufferedReader( inputStreamReader); String line = null; while ((line = bufferedReader.readLine()) != null) { this.buf.append(line + "\n"); } } catch (IOException ex) { logger.trace("Failed to successfully consume and display the input stream of type " + streamType + ".", ex); } finally { this.isStopped = true; synchronized (this) { notify(); } } } public String getContent() { if(!this.isStopped){ synchronized (this) { try { wait(); } catch (InterruptedException ignore) { } } } return this.buf.toString(); } }
public class LocalCommandExecutorTest { public static void main(String[] args) { LocalCommandExecutorService service = new LocalCommandExecutorServiceImpl(); String[] command = new String[]{"ping","127.0.0.1"}; ExecuteResult result = service.executeCommand(command, 5000); System.out.println("退出码:"+result.getExitCode()); System.out.println("输出内容:"+result.getExecuteOut()); } }
输出结果以下:
segmentfault
直接在命令行执行“ping 127.0.0.1”,结果以下:
app
Apache提供了一个开源库,对Process类进行了封装,也提供了设置超时的功能,建议在项目中使用Apache Commons Exec这个开源库来实现超时功能,除了功能更强大外,稳定性也有保障。ide