Process类详解
- 一、相关类和方法介绍
- 二、安全风险
- 1. external processes block on I|O streams
一、相关类和方法介绍
ProcessBuilder是一个final类,Process是一个抽象类。ProcessBuilder.start()
和 Runtime.exec()
方法都被用来创建一个操作系统进程(执行命令行操作),并返回 Process
子类的一个实例,该实例可用来控制进程状态并获得相关信息。
每个进程生成器ProcessBuilder对象管理这些进程属性:
- 命令 是一个字符串列表,它表示要调用的可执行外部程序文件及其参数(如果有)。
- 环境 是从变量 到值 的依赖于系统的映射。
- 工作目录 默认值是当前进程的当前工作目录,通常根据系统属性 user.dir 来命名。
- redirectErrorStream 属性 子进程的标准输出和错误输出是否被发送给发送给两个独立的流(Process.getInputStream() 和 Process.getErrorStream()),默认false发送。
Runtime.exec()
可接受一个单独的字符串,这个字符串是通过空格来分隔可执行命令程序和参数的;也可以接受字符串数组参数/list
。ProcessBuilder.start()
只支持字符串数组参数。
创建的子进程没有自己的终端或控制台。它的所有标准 io(即 stdin,stdout,stderr)操作都将通过三个流(getOutputStream(),getInputStream(),getErrorStream()) 重定向到父进程。
代码语言:javascript复制// 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后已挂起,则可能导致子进程阻塞,进程间相互等待甚至产生死锁。
现有如下三种解决方法,缓冲区内容消费掉即可。
代码语言:javascript复制// 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();