Java starts child processes and communication between parent and child processes

Description of how to start the process

  • Through new processbuilder (string... Commands) Start() starts the process
    • ProcessBuilder supports chain programming to configure the related settings of child processes
      • redirectXXX: redirect the stream of subprocesses (standard input, standard output, error message)
      • environment() gets the environment settings, which can be modified
    • Note: commands are not simply obtained by separating command line parameters with spaces. If a single value in commands is too long, it may fail to start. In the Runtime, the StringTokenizer is used to parse the split parameters into an array of commands.
  • Start the process through the method encapsulated by Runtime, such as:
    • Runtime.getRuntime().exec(xxx)
  • If it is, start the java program. Instead of other jar packages, you can splice the command line as follows:
String javaHome = System.getProperty("java.home");
String java = javaHome + File.separator + "bin" + File.separator + "java";
String sysCp = System.getProperty("java.class.path");
String currPath = ClassLoader.getSystemResource("").getPath();
String cp = "\\"" + sysCp + File.pathSeparator + currPath + "\\"";

String encoding = " -Dfile.encoding=" + Charset.defaultCharset().name();
String cmd = java + encoding + " -cp " + cp + ChildProcess.class;
  • Behavior of array commands Runtime method:
public static String[] resolveCommand(String command) {
    if (command.length() == 0) {
        throw new IllegalArgumentException("Empty command");
    }

    StringTokenizer st = new StringTokenizer(command);
    String[] cmdArray = new String[st.countTokens()];
    for (int i = 0; st.hasMoreTokens(); i++) {
        cmdArray[i] = st.nextToken();
    }
    return cmdArray;
}

Case:

  • Parent process
import cn.hutool.core.thread.ThreadUtil;

import java.io.*;
import java.nio.charset.Charset;
import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit;

public class FatherProcess {
    public static void main(String[] args) throws IOException {
        String javaHome = System.getProperty("java.home");
        String java = javaHome + File.separator + "bin" + File.separator + "java";
        String sysCp = System.getProperty("java.class.path");
        String currPath = ClassLoader.getSystemResource("").getPath();
        String cp = "\\"" + sysCp + File.pathSeparator + currPath + "\\"";

        // If the codes of the parent process and the child process are inconsistent, Chinese garbled codes will appear. The stream can be wrapped so that the codes of both parties are consistent
        // Or when the parent process starts the child process, set "- dfile. Encoding =" + charset defaultCharset(). name();
        String encoding = " -Dfile.encoding=" + Charset.defaultCharset().name();
        String cmd = java + encoding + " -cp " + cp + ChildProcess.class;
        // Process p = Runtime.getRuntime().exec(cmd);

        // Failed. You need to use StringTokenizer to parse the command behavior array. A single string may be too long
        // Process p = new ProcessBuilder(java, "-classpath", cp, ChildProcess.class.toString()).start();

        // You can redirect the flow of the child process to the file through ProcessBuilder. At this time, the parent process will not be able to obtain the child process output through p.getInputStream()
        ProcessBuilder processBuilder = new ProcessBuilder(resolveCommand(cmd));
        processBuilder.redirectOutput(new File("target/output.txt"));
        processBuilder.redirectError(new File("target/error.txt"));
        Process p = processBuilder.start();

        System.out.println("FatherProcess's default charset is: " + Charset.defaultCharset().name());
        // The parent process writes information to the input stream of the child process through the IO stream
        System.out.println("[Parent process sends two pieces of data]:" + 2);
        try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(p.getOutputStream()))) {
            // The child process uses readLine, so a newline character is required
            writer.write("Eliminate human tyranny\\n");
            writer.flush();
            ThreadUtil.sleep(1, TimeUnit.SECONDS);
            writer.write("The world belongs to the three bodies\\n");
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static String[] resolveCommand(String command) {
        if (command.length() == 0) {
            throw new IllegalArgumentException("Empty command");
        }

        StringTokenizer st = new StringTokenizer(command);
        String[] cmdArray = new String[st.countTokens()];
        for (int i = 0; st.hasMoreTokens(); i++) {
            cmdArray[i] = st.nextToken();
        }
        return cmdArray;
    }
}
  • Subprocess
import cn.hutool.core.thread.ThreadUtil;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class ChildProcess {
    public static void main(String[] args) throws IOException {
        System.out.println("ChildProcess's default charset is: " + Charset.defaultCharset().name());
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) {
            String line;
            List<String> all = new ArrayList<>();
            // readLine ends with a newline character
            while ((line = reader.readLine()) != null) {
                all.add(line);
            }
            // println has a newline character
            System.out.println("[Number of messages received by child processes]:" + all.size());
            ThreadUtil.sleep(200, TimeUnit.MILLISECONDS);
            System.out.println("Civilization to the years");
            ThreadUtil.sleep(200, TimeUnit.MILLISECONDS);
            System.out.println("Instead of giving time to civilization");
            System.out.println(all);
        }
    }
}

Communication through IO streams - DataOutputStream and DataInputStream

BufferedWriter and BufferedReader can also be used. If you use the readLine method, note that the BufferedWriter must write a newline character (or close the stream) before BufferedReader Readline() to read the content.

Based on the default IO flow:

  • The parent process uses process Getoutputstream writes data to the child process through System.in Read data
  • The parent process uses process Getinputstream reads the data output by the child process.

case

  • Parent process
import cn.hutool.core.util.StrUtil;

import java.io.*;
import java.util.StringTokenizer;

/**
 * @author zhy
 */
public class FatherProcessWithDOS {
    private final Process process;
    private final DataOutputStream dos;

    public FatherProcessWithDOS() throws IOException {
        String javaHome = System.getProperty("java.home");
        String java = javaHome + File.separator + "bin" + File.separator + "java";
        String sysCp = System.getProperty("java.class.path");
        String currPath = ClassLoader.getSystemResource("").getPath();
        String cp = "\\"" + sysCp + File.pathSeparator + currPath + "\\"";

        String cmd = java + " -cp " + cp + ChildProcessWithDIS.class;
        process = new ProcessBuilder(resolveCommand(cmd)).start();

        // region listens for the error output of child processes
        new Thread(() -> {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    System.err.println(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
        // endregion

        // region listens to the standard output of child processes
        new Thread(() -> {
            try (DataInputStream dis = new DataInputStream(process.getInputStream())) {
                String line;
                while (!StrUtil.isEmpty((line = dis.readUTF()))) {
                    System.out.println(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
        // endregion

        dos = new DataOutputStream(process.getOutputStream());
    }

    public void sendToChild(String message) {
        try {
            dos.writeUTF(message);
            // dos.writeBytes(message);
            dos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public boolean isChildAlive() {
        return process.isAlive();
    }

    public void destroyChild() {
        process.destroy();
    }

    private String[] resolveCommand(String command) {
        if (command.length() == 0) {
            throw new IllegalArgumentException("Empty command");
        }

        StringTokenizer st = new StringTokenizer(command);
        String[] cmdArray = new String[st.countTokens()];
        for (int i = 0; st.hasMoreTokens(); i++) {
            cmdArray[i] = st.nextToken();
        }
        return cmdArray;
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        FatherProcessWithDOS father = new FatherProcessWithDOS();

        String message = "Eliminate human tyranny";
        System.out.println("Parent process send data:" + message);
        father.sendToChild(message);

        Thread.sleep(100);

        message = "The world belongs to the three bodies";
        System.out.println("Parent process send data:" + message);
        father.sendToChild(message);

        Thread.sleep(100);

        message = "man remember love because romantic only.";
        System.out.println("Parent process send data:" + message);
        father.sendToChild(message);

        Thread.sleep(100);

        message = "exit";
        System.out.println("Parent process end command:" + message);
        father.sendToChild(message);

        Thread.sleep(100);

        System.out.println("Whether the child process survives:" + father.isChildAlive());
        System.exit(0);
    }
}
  • Subprocess
import cn.hutool.core.util.StrUtil;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @author zhy
 */
public class ChildProcessWithDIS {

    public static void main(String[] args) {
        read(System.in);
    }

    private static void read(InputStream inputStream) {
        DataOutputStream out = new DataOutputStream(System.out);
        try (DataInputStream reader = new DataInputStream(inputStream)) {
            String line;
            while (!StrUtil.isEmpty((line = reader.readUTF()))) {
                if ("exit".equalsIgnoreCase(line)) {
                    // You must write an empty string or throw an exception Java io. EOFException
                    out.writeUTF("");
                    return;
                }

                String dateStr = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date());
                String msg = MessageFormat.format("[{0}][receive a line message]:{1}", dateStr, line);
                // System.out.println(msg);
                out.writeUTF(msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Communicate through IO stream + Socket (port is required, so it is unnecessary to use it)

  • Parent process
import cn.hutool.core.net.NetUtil;

import java.io.*;
import java.net.Socket;
import java.util.StringTokenizer;

/**
 * @author zhy
 */
public class FatherProcessWithSocket {
    private final Process process;
    private final int port;

    public FatherProcessWithSocket() throws IOException {
        String javaHome = System.getProperty("java.home");
        String java = javaHome + File.separator + "bin" + File.separator + "java";
        String sysCp = System.getProperty("java.class.path");
        String currPath = ClassLoader.getSystemResource("").getPath();
        String cp = "\\"" + sysCp + File.pathSeparator + currPath + "\\"";

        port = NetUtil.getUsableLocalPort();

        String cmd = java + " -cp " + cp + ChildProcessWithSocket.class + " " + port;
        process = new ProcessBuilder(resolveCommand(cmd)).start();

        // region listens for the error output of child processes
        new Thread(() -> {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    System.err.println(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        }).start();
        // endregion

        // region listens to the standard output of child processes
        new Thread(() -> {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    System.err.println(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
        // endregion

    }

    public void sendToChild(String message) {
        try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new Socket("localhost", port).getOutputStream()))) {
            writer.write(message);
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public boolean isChildAlive() {
        return process.isAlive();
    }

    public void destroyChild() {
        process.destroy();
    }

    private String[] resolveCommand(String command) {
        if (command.length() == 0) {
            throw new IllegalArgumentException("Empty command");
        }

        StringTokenizer st = new StringTokenizer(command);
        String[] cmdArray = new String[st.countTokens()];
        for (int i = 0; st.hasMoreTokens(); i++) {
            cmdArray[i] = st.nextToken();
        }
        return cmdArray;
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        FatherProcessWithSocket father = new FatherProcessWithSocket();

        String message = "Eliminate human tyranny";
        System.out.println("Parent process send data:" + message);
        father.sendToChild(message);

        Thread.sleep(100);

        message = "The world belongs to the three bodies";
        System.out.println("Parent process send data:" + message);
        father.sendToChild(message);

        Thread.sleep(100);

        message = "exit";
        System.out.println("Parent process end command:" + message);
        father.sendToChild(message);

        Thread.sleep(100);

        System.out.println("Whether the child process survives:" + father.isChildAlive());
    }
}
  • Subprocess
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @author zhy
 */
public class ChildProcessWithSocket {

    public static void main(String[] args) {
        if (args.length > 0) {
            int port = Integer.parseInt(args[0]);
            new Thread(() -> startSocket(port)).start();
        }

        // read(System.in);
    }

    private static void startSocket(int port) {
        try {
            ServerSocket serverSocket = new ServerSocket(port);
            while (true) {
                Socket accept = serverSocket.accept();
                InputStream inputStream = accept.getInputStream();
                if (read(inputStream)) {
                    return;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static boolean read(InputStream inputStream) {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
            String line;
            while ((line = reader.readLine()) != null) {
                if ("exit".equalsIgnoreCase(line)) {
                    return true;
                }

                String dateStr = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date());
                String msg = MessageFormat.format("[{0}][receive a line message]:{1}", dateStr, line);
                System.out.println(msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

}

Summary

How Java starts a subprocess

  • Through new processbuilder (string... Commands) Start() starts the process
    • ProcessBuilder supports chain programming to configure the related settings of child processes
      • redirectXXX: redirect the stream of subprocesses (standard input, standard output, error message)
      • environment() gets the environment settings, which can be modified
    • Note: commands are not simply obtained by separating command line parameters with spaces. If a single value in commands is too long, it may fail to start. In the Runtime, the StringTokenizer is used to parse the split parameters into an array of commands.
  • Start the process through the method encapsulated by Runtime, such as:
    • Runtime.getRuntime().exec(xxx)
  • If it is, start the java program. Instead of other jar packages, you can splice the command line as follows:
String javaHome = System.getProperty("java.home");
String java = javaHome + File.separator + "bin" + File.separator + "java";
String sysCp = System.getProperty("java.class.path");
String currPath = ClassLoader.getSystemResource("").getPath();
String cp = "\\"" + sysCp + File.pathSeparator + currPath + "\\"";

String encoding = " -Dfile.encoding=" + Charset.defaultCharset().name();
String cmd = java + encoding + " -cp " + cp + ChildProcess.class;
  • How the Runtime parses the commands array of command behavior:
public static String[] resolveCommand(String command) {
    if (command.length() == 0) {
        throw new IllegalArgumentException("Empty command");
    }

    StringTokenizer st = new StringTokenizer(command);
    String[] cmdArray = new String[st.countTokens()];
    for (int i = 0; st.hasMoreTokens(); i++) {
        cmdArray[i] = st.nextToken();
    }
    return cmdArray;
}

Process API

  • isAlive(): judge whether it is alive or not
  • destroy(): end the process

signal communication

Based on the default IO flow:

  • The parent process uses process Getoutputstream writes data to the child process through System.in Read data
  • The parent process uses process Getinputstream reads the data output by the child process.

Other methods: Socket

Handle the Chinese garbled code problem in the communication between parent and child processes

The only point: ensure that the sender and reader use the same code.

  • InputStreamReader and OutputStreamWriter use jvm default encoding to read data by default: charset defaultCharset(). name();
    • If the default encoding of the parent process and the child process is inconsistent, you need to specify the encoding manually
    • You can also the parent process through "java" + "- Dfile.encoding =" + charset defaultCharset(). Name() ensures that the parent-child process codes are consistent.
  • You can also use DataOutputStream and DataInputStream, special streams that handle encoding.
    • When using this stream, it is recommended to use the writeUTF and readUTF methods, and write an empty string before closing the stream.

Keywords: Java Back-end

Added by nolos on Sat, 15 Jan 2022 16:35:19 +0200