Process工具類,提供設置timeout功能

前一篇博文中,簡單介紹瞭如何使用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

  • ExecuteResult類,對執行命令的結果進行封裝,能夠從中獲取退出碼和輸出內容。
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;
    }

}
  • LocalCommandExecutorService 接口,向外暴露executeCommand()方法
public interface LocalCommandExecutorService {
    ExecuteResult executeCommand(String[] command, long timeout);
}
  • LocalCommandExecutorServiceImpl 實現類,實現LocalCommandExecutorService 接口的方法
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) {
        }
    }
}
  • StreamGobbler類,用來包裝輸入輸出流
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

相關文章
相關標籤/搜索