zl程序教程

您现在的位置是:首页 >  其它

当前栏目

Process类详解

详解 Process
2023-06-13 09:13:57 时间

Process类详解

一、相关类和方法介绍

ProcessBuilder是一个final类,Process是一个抽象类。ProcessBuilder.start()Runtime.exec() 方法都被用来创建一个操作系统进程(执行命令行操作),并返回 Process 子类的一个实例,该实例可用来控制进程状态并获得相关信息。

每个进程生成器ProcessBuilder对象管理这些进程属性:

  • 命令 是一个字符串列表,它表示要调用的可执行外部程序文件及其参数(如果有)。
  • 环境 是从变量 到值 的依赖于系统的映射。
  • 工作目录 默认值是当前进程的当前工作目录,通常根据系统属性 user.dir 来命名。
  • redirectErrorStream 属性 子进程的标准输出和错误输出是否被发送给发送给两个独立的流(Process.getInputStream() 和 Process.getErrorStream()),默认false发送。

Runtime.exec() 可接受一个单独的字符串,这个字符串是通过空格来分隔可执行命令程序和参数的;也可以接受字符串数组参数/listProcessBuilder.start() 只支持字符串数组参数。

创建的子进程没有自己的终端或控制台。它的所有标准 io(即 stdin,stdout,stderr)操作都将通过三个流(getOutputStream(),getInputStream(),getErrorStream()) 重定向到父进程。

// Runtime.exec最终是通过调用ProcessBuilder来真正执行操作的
public Process exec(String[] cmdarray, String[] envp, File dir)
    throws IOException {
    // 在 directory() 指定的工作目录中,利用 environment() 指定的进程环境,新进程将调用由 command() 给出的命令和参数。
    return new ProcessBuilder(cmdarray)
        .environment(envp)
        .directory(dir)
        .start();
}
  • 注意 ProcessBuilder 第一个参数必须是可执行程序,可以添加参数使用{"cmd", "/c"}{"/bin/bash", "-c"}

二、安全风险

  • 描述 java.lang.Process 对象描述进程可能需要通过其输入流对其提供输入,并且其输出流、错误流或两者同时会产生输出。不正确地处理这些外部程序可能会导致一些意外的异常、DoS,及其他安全问题。 一个进程如果试图从一个空的输入流中读取输入,则会一直阻塞,直到为其提供输入。因此,在调用这样的进程时,必须为其提供输入。 一个外部进程的输出可能会耗尽该进程输出流与错误流的缓冲区。当发生这种情况时,Java 程序可能会阻塞外部进程,同时阻碍Java程序与外部程序的继续运行。因此,在运行一个外部进程时,如果此进程往其输出流发送任何数据,则必须将其输出流清空。类似的,如果进程会往其错误流发送数据,其错误流也必须被清空。
  • 处理建议 对于那些从来不会读取其输入流的进程,不对其提供输入非但无害,且还有益。而对于那些从来不会发送数据到其输出流或者错误流的进程,不对其输出流或者错误流进行清空同样是有益无害的。因此,只要能够保证进程不会使用这些流,那么在程序中可以忽略其输入流、输出流、以及错误流。

1. external processes block on I|O streams

  • 原因 有些本机平台仅针对标准输入和输出流提供有限的=缓冲区大小,如果读写子进程的输出流或输入流迅速出现失败(如不断发送数据),而主进程调用Process.waitfor后已挂起,则可能导致子进程阻塞,进程间相互等待甚至产生死锁。

现有如下三种解决方法,缓冲区内容消费掉即可。

// Do not let external processes block on I|O streams

// 场景一: 使用java.lang.ProcessBuilder.redirectErrorStream(boolean redirectErrorStream)方法即可清空流
ProcessBuilder builder = new ProcessBuilder(cmds);
builder.redirectErrorStream(true);
try {
	process = builder.start();
} catch (IOException e) {
	e.pringtStackTrace();
}

// 场景二:当出现IOException异常时不应该将IOException异常throws,使用try/catch对IOException单独捕获
Process process = null;
try {
	process = builder.start();
} catch (IOException e) {
	e.pringtStackTrace();
}

String handleMessage = "";
BufferedReader bufferedReader = new BufferedSReader(new InputStreamReader(process.getInputStream, StandardCharesets.UTF_8));
try {
	while ((handleMessage = bufferedReader.readLine()) != null) {
		System.out.println(handleMessage);
	}
} catch (IOException e) {
	e.pringtStackTrace();
}

try {
	bufferedReader.close();
} catch (IOException e) {
	e.pringtStackTrace();
}

// 场景三:有时候我们可能需要调用系统外部的某个程序,此时就可以用Runtime.getRuntime().exec()来调用,他会生成一个新的进程去运行调用的程序,waitFor()方法也有很明显的弊端,因为java程序给进程的输出流分配的缓冲区是很小的,有时候当进程输出信息很大的时候回导致缓冲区被填满,如果不及时处理程序会阻塞,解决的方法就是处理缓冲区中的信息,开两个线程分别去处理标准输出流和错误输出流
Process process = Runtime.getRuntime().exec(str);
// 记录进程缓存错误信息
final StringBuffer errorLog = new StringBuffer();
final InputStream errorStream = process.getErrorStream();
final InputStream inputStream = process.getInputStream();

// 处理InputStream的线程
new Thread() {
   @Override
   public void run() {
       BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
       String line = null;
       try {
       	   // 消费掉缓存中的数据
           while ((line = in.readLine()) != null && !errorLog.toString().contains("ERROR")) {
               if (line != null) {
                   errorLog.append(line);
               }
           }
       } catch (IOException e) {
       	   // public RuntimeException(String message, Throwable cause)
           throw new RuntimeException("[shell exec error]:" + errorLog, e);
       } finally {
           try {
               inputStream.close();;
           } catch (IOException e) {
               e.printStackTrace();
           }
       }
   }
}.start();

// 处理errorStream的线程
new Thread() {
   @Override
   public void run() {
       BufferedReader err = new BufferedReader(new InputStreamReader(errorStream));
       String line = null;
       try {
       	   // 消费掉缓存中的数据
           while ((line = err.readLine()) != null && !errorLog.toString().contains("ERROR")) {
               if (line != null) {
                   errorLog.append(line);
               }
           }
       } catch (IOException e) {
           throw new RuntimeException("[shell exec error]:" + errorLog, e);
       } finally {
           try {
               errorStream.close();;
           } catch (IOException e) {
               e.printStackTrace();
           }
       }
   }
}.start();

logger.info("等待shell脚本执行完成");
Thread.sleep(1000);

// 异常终止
if (errorLog != null && errorLog.length() > 0 && errorLog.toString().contains("ERROR")) {
   dispatchLogger.error("[shell exec error]:" + errorLog);
   throw new RuntimeException("[shell exec error]:" + errorLog);
}

// 等待shell脚本执行完成
process.waitFor();