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();
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Process是C#中一个非常重要的,它表示一个进程,可以用来启动、停止、监视进程。下面是Process的详细介绍: 1. 创建Process对象 可以通过以下方式来创建一个Process对象: ``` Process process = new Process(); ``` 2. 启动进程 可以使用Process的Start方法来启动一个进程。Start方法可以带一个ProcessStartInfo对象作为参数,用于设置进程的启动属性。 ``` ProcessStartInfo startInfo = new ProcessStartInfo("notepad.exe"); Process process = new Process(); process.StartInfo = startInfo; process.Start(); ``` 上述代码将启动一个记事本进程。 3. 停止进程 可以使用Process的Kill方法来强制停止一个进程。 ``` Process process = Process.GetProcessById(processId); process.Kill(); ``` 上述代码将停止一个指定ID的进程。 4. 获取进程信息 可以使用Process的静态方法GetProcesses来获取当前系统上所有正在运行的进程。也可以使用Process的属性和方法来获取指定进程的信息,如ProcessName、MainWindowTitle、StartTime、PrivateMemorySize等。 ``` Process[] processes = Process.GetProcesses(); foreach (Process process in processes) { Console.WriteLine("Process Name: {0} Process ID: {1}", process.ProcessName, process.Id); } ``` 上述代码将获取当前系统上所有正在运行的进程的名称和进程ID,并输出到控制台。 5. 监视进程 可以使用Process的EnableRaisingEvents属性和Exited事件来监视一个进程的结束。 ``` Process process = new Process(); process.EnableRaisingEvents = true; process.Exited += new EventHandler(process_Exited); process.Start(); ``` 上述代码将启动一个进程,并在进程结束时触发process_Exited事件。 以上就是Process的基本用法,可以根据实际需求灵活运用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值