java实现socket代理执行shell获取返回值,程序发生的进化

/ 默认分类 / 1 条评论 / 1095浏览

第一次代码是这样的

    static String interpreter = "/bin/sh";

    public static String runSimpleCommand(String command) throws IOException {
        String[] cmd = {interpreter,"-c",command};
        Process process = Runtime.getRuntime().exec(cmd);
        String result = RuntimeUtil.getResult(process);
        return result;
    }

一般情况下,以上代码执行是没有问题的,执行命令,读取返回结果。 但是如果命令执行过程中异常了,或者进程不止写了标准输出,还有标准错误输出,那么RuntimeUtil.getResult(process)将无法读取到错误信息,于是程序变成了下面这样。

    public static ShellResult runSimpleCommand(String command) throws IOException {
        String[] cmd = {interpreter,"-c",command};
        Process process = Runtime.getRuntime().exec(cmd);
        String stdout = RuntimeUtil.getResult(process);
        String stderr = RuntimeUtil.getErrorResult(process);
        return new ShellResult(stdout,stderr);
    }

    @Data
    @AllArgsConstructor
    public class ShellResult{
        private String stdout;
        private String stderr;
        public String std(){
            return this.stdout+"\r\n"+this.stderr;
        }
    }

这时又出现了问题,代码居然执行抛出了异常! debug后才发现,hutool的RuntimeUtil获取返回值源码是这样的

      public static String getResult(Process process, Charset charset) {
		InputStream in = null;
		try {
			in = process.getInputStream();
			return IoUtil.read(in, charset);
		} finally {
			IoUtil.close(in);
			destroy(process);
		}
	}

原来hutool这货在读取结果后居然字节关闭流,destroy了这个process,难怪会抛异常。还是太相信这些工具类了,难道hutool的开发者就没想过,有些时候还需要同时读stderr吗。既然发现了问题。那这次就来自己实现流的读取,这里没有使用一次性读取inputstream,而是使用按行读取原因是因为业务需要,需要把标准输出过程数据写入远端socket中,这里代码就略去了。


    public ShellResult runSimpleCommand(String command) throws IOException {
        String[] cmd = {interpreter,"-c",command};
        Process process = Runtime.getRuntime().exec(cmd);
        BufferedReader processBr = new BufferedReader(new InputStreamReader(process.getInputStream(), "utf-8"));
        BufferedReader processErrorBr = new BufferedReader(new InputStreamReader(process.getErrorStream(), "utf-8"));
        String processLine;
        //--标准输出
        StringBuilder stdout = new StringBuilder();
        try {
            while ((processLine = processBr.readLine()) != null) {
                stdout.append(processLine+"\r\n");
            }
        } catch (IOException e) {
            log.debug("读stdout异常",e);
        }
        //--标准err输出
        StringBuilder stderr = new StringBuilder();
        try {
            while ((processLine = processErrorBr.readLine()) != null) {
                stderr.append(processLine+"\r\n");
            }
        } catch (IOException e) {
            log.debug("读stderr异常",e);
        }
        ShellResult shellResult = new ShellResult(stdout.toString(), stderr.toString());
        return shellResult;
    }

如果业务不需要过程输出,只要结果的话,当然文件流的长度是可预见的,直接读取效率更高,见代码。

public static byte[] readBytes(InputStream in, boolean isClose) throws IOException {
	// 文件流的长度是可预见的,此时直接读取效率更高
	final byte[] result;
	try {
		final int available = in.available();
		result = new byte[available];
		final int readLength = in.read(result);
		if (readLength != available) {
			throw new IOException(StrUtil.format("File length is [{}] but read [{}]!", available, readLength));
		}
	} finally {
		if (isClose) {
			close(in);
		}
	}
	return result;
}

后记

期间报错,一度以为process获取标准输出也是通过读取/proc/{pid}/fd/1这个文件,以为命令执行时间太短,导致读取完stdout后,进程销毁导致无法读取stderr,还把读取方式一度改为并发读取。

    public ShellResult runSimpleCommandMultiGetResult(String command) throws IOException {
        String[] cmd = {interpreter,"-c",command};
        Process process = Runtime.getRuntime().exec(cmd);
        String result = "";
        String errors = "";
        Callable<String> resultCall = () -> {
            String result1 = RuntimeUtil.getResult(process);
            return result1;
        };
        Callable<String> errorsCall = () -> {
            String errors1 = RuntimeUtil.getErrorResult(process);
            return errors1;
        };
        FutureTask<String> resultTask = new FutureTask<>(resultCall);
        FutureTask<String> errorResultTask = new FutureTask<>(errorsCall);
        socketPool.submit(resultTask);
        socketPool.submit(errorResultTask);
        try {
            result = resultTask.get();
        } catch (Exception e) {
            log.debug("读stdout异常",e);
        }
        try {
            errors = errorResultTask.get();
        } catch (Exception e) {
            log.debug("读stderr异常",e);
        }
        ShellResult shellResult = new ShellResult(result, errors);
        return shellResult;
    }

其实在这个process对象没有被销毁,流没有关闭的情况下,一直都可以读取,读取流的方式也不是通过读取pid下的文件。

  1. 学习了,受益匪浅

    回复