资讯专栏INFORMATION COLUMN

Process工具类,提供设置timeout功能

DesGemini / 837人阅读

摘要:类提供了执行从进程输入执行输出到进程等待进程完成检查进程的退出状态以及销毁杀掉进程的方法。解决进程无限阻塞的方法是在执行命令时,设置一个超时时间,下面提供一个工具类,对使用进行包装,向外提供设置超时的接口。

在前一篇博文中,简单介绍了如何使用Process类来调用命令行的功能,那样使用Process会有一个很大的问题,就是可能会出现无限阻塞的情况,永远都无法返回结果。以下是Process的API说明,注意加粗的部分。

  

ProcessBuilder.start() 和 Runtime.exec 方法创建一个本机进程,并返回 Process 子类的一个实例,该实例可用来控制进程并获得相关信息。Process 类提供了执行从进程输入、执行输出到进程、等待进程完成、检查进程的退出状态以及销毁(杀掉)进程的方法。
创建进程的方法可能无法针对某些本机平台上的特定进程很好地工作,比如,本机窗口进程,守护进程,Microsoft Windows 上的 Win16/DOS 进程,或者 shell 脚本。创建的子进程没有自己的终端或控制台。它的所有标准 io(即 stdin、stdout 和 stderr)操作都将通过三个流 (getOutputStream()、getInputStream() 和 getErrorStream()) 重定向到父进程。父进程使用这些流来提供到子进程的输入和获得从子进程的输出。因为有些本机平台仅针对标准输入和输出流提供有限的缓冲区大小,如果读写子进程的输出流或输入流迅速出现失败,则可能导致子进程阻塞,甚至产生死锁。

解决进程无限阻塞的方法是在执行命令时,设置一个超时时间,下面提供一个工具类,对Process使用进行包装,向外提供设置超时的接口。

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

    @Override
    public ExecuteResult executeCommand(String[] command, long timeout) {
        Process process = null;
        InputStream pIn = null;
        InputStream pErr = null;
        StreamGobbler outputGobbler = null;
        StreamGobbler errorGobbler = null;
        Future 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 call = new Callable() {
                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 + "
");
            }
        } 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());     
    }
}

输出结果如下:

直接在命令行执行“ping 127.0.0.1”,结果如下:

  

Apache提供了一个开源库,对Process类进行了封装,也提供了设置超时的功能,建议在项目中使用Apache Commons Exec这个开源库来实现超时功能,除了功能更强大外,稳定性也有保障。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/64019.html

相关文章

  • Nodejs高性能原理(下) --- 事件循环详解

    摘要:如果一个即时定时器是被一个正在执行的回调排入队列的,则该定时器直到下一次事件循环迭代才会被触发。参数描述在事件循环的当前回合结束时要调用的函数。事件轮询随后的调用,会在任何事件包括定时器之前运行。 系列文章 Nodejs高性能原理(上) --- 异步非阻塞事件驱动模型Nodejs高性能原理(下) --- 事件循环详解 前言 终于开始我nodejs的博客生涯了,先从基本的原理讲起.以前写...

    newsning 评论0 收藏0
  • 使用java.lang.Process的简单例子

    摘要:在之前,都是由类处来实现进程的控制管理。导致当前线程等待,如有必要,一直要等到由该对象表示的进程已经终止。如果已终止该子进程,此方法立即返回。为了防止进程无限阻塞或者死锁,使用类时,需要加上超时控制,详细内容可以看博文工具类,提供设置功能。 ProcessBuilder类是J2SE 1.5在java.lang中新添加的一个新类,此类用于创建操作系统进程,它提供一种启动和管理进程(也就是...

    MonoLog 评论0 收藏0
  • dubbo源码解析(九)远程通信——Transport层

    摘要:层也就是网络传输层,在远程通信中必然会涉及到传输。值为,不等待消息发出,将消息放入队列,即刻返回。三该类继承了并且实现接口,是服务器抽象类。八该类是多消息处理器的抽象类。创建线程池设置组件的获得实例把线程池放到 远程通讯——Transport层 目标:介绍Transport层的相关设计和逻辑、介绍dubbo-remoting-api中的transport包内的源码解析。 前言 先预警一...

    Magicer 评论0 收藏0
  • [Java]使用Apache Commons Execs调用脚本

    摘要:还有,需要支持字符编码设置,在下对象调试程序很有帮助,因此,我们可以列表表示整个需求。第二种是无法设置字符编码的,而第一种是获得了整个标准输出和错误输出后再设置字符编码的。 概述 写这篇的主要目的是为了整理和记录,归档以便以后查阅。 我之前在SF上提问了一个问题:如何正确使用PipedInputStream和PipedOutputStream 问题中提到的Apache Commons ...

    chnmagnus 评论0 收藏0
  • FE.ES-理解Event Loop

    摘要:新加了一个微任务和一个宏任务在当前执行栈的尾部下一次之前触发回调函数。阶段这个阶段主要执行一些系统操作带来的回调函数,如错误,如果尝试链接时出现错误,一些会把这个错误报告给。 JavaScript引擎又称为JavaScript解释器,是JavaScript解释为机器码的工具,分别运行在浏览器和Node中。而根据上下文的不同,Event loop也有不同的实现:其中Node使用了libu...

    longshengwang 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<