Node is designed to handle I/O operations efficiently, but you should be aware that some types of programs are not suitable for this model. For example, if you plan to use Node to handle a CPU-intensive task, you may clog the event loop and therefore reduce the responsiveness of the program. The alternative is to offload CPU-intensive tasks to a separate process, thereby freeing up the event loop. Node allows you to spawn a process and make the new process a child of its parent. In Node, the child process can communicate with the parent process in two-way, and to some extent, the parent process can also monitor and manage the child process.
Another situation where you need to use a subprocess is when you want to simply execute an external command and let Node get the return value of the command. For example, you can execute a UNIX command, script, or other commands that cannot be executed directly in Node.
This chapter will show you how to execute external commands, create and communicate with child processes, and terminate child processes. The point is to let you understand how to complete a series of tasks outside the Node process.
Execute external command
When you need to execute an external shell command or executable file, you can use the child_process module and import it like this:
exec(command,callback);
//Translator’s note: If you use windows, you can change it to windows commands, such as dir, which will not be described again
});
If an error occurs, the first parameter will be an instance of the Error class. If the first parameter does not contain an error, then the second parameter stdout will contain the standard output of the command. The last parameter contains error output related to the command.
Listing 8-1 shows a more complex example of executing external commands
LISTING 8-1: Execute external commands (source code: chapter8/01_external_command.js)
On the fourth line, we pass "cat *.js | wc -l" as the first parameter to exec. You can also try any other command, as long as you have used the command in the shell.
Then pass a callback function as the second parameter, which will be called when an error occurs or the child process terminates.
You can also pass a third optional parameter before the callback function, which contains some configuration options, such as:
Timeout: 1000,
killSignal: ‘SIGKILL’
};
//…
});
1.cwd - Current directory, you can specify the current working directory.
2.encoding - the encoding format of the output content of the child process. The default value is "utf8", which is UTF-8 encoding. If the output of the child process is not utf8, you can use this parameter to set it. The supported encoding formats are:
Use Buffer to process, encode, and decode binary data".
1.timeout - command execution timeout in milliseconds, the default is 0, which means no limit, until the child process ends.
2.maxBuffer - Specify the maximum number of bytes allowed to be output by the stdout stream and stderr stream. If the maximum value is reached, the child process will be killed. The default value is 200*1024.
3.killSignal - A termination signal sent to the child process when it times out or the output buffer reaches its maximum size. The default value is "SIGTERM", which will send a termination signal to the child process. This orderly approach is usually used to end processes. When using the SIGTERM signal, the process can process it or override the default behavior of the signal processor after receiving it. If the target process needs it, you can pass other signals (such as SIGUSR1) to it at the same time. You can also choose to send a SIGKILL signal, which will be processed by the operating system and force the child process to end immediately. In this case, any cleanup operations of the child process will not be performed.
1.evn - Specifies the environment variables passed to the child process. The default is null, which means that the child process will inherit the environment variables of all parent processes before it is created.
Note: Using the killSignal option, you can send a signal to the target process in the form of a string. Signals exist in the form of strings in Node. The following is a list of UNIX signals and corresponding default operations:
You may want to provide the child process with an expandable set of parent environment variables. If you modify the process.env object directly, you will change the environment variables of all modules in the Node process, which will cause a lot of trouble. The alternative is to create a new object and copy all parameters in process.env, see Example 8-2:
LISTING 8-2: Use parameterized environment variables to execute commands (source code: chapter8/02_env_vars_augment.js)
envCopy['CUSTOM ENV VAR1'] = 'some value';
envCopy['CUSTOM ENV VAR2'] = 'some other value';
exec(‘ls –la’,{env: envCopy}, function(err,stdout,stderr){
If(err){ throw err; }
console.log(‘stdout:’, stdout);
console.log(‘stderr:’,stderr);
}
The above example creates an envCopy variable to save environment variables. It first copies the environment variables of the Node process from process.env, then adds or replaces some environment variables that need to be modified, and finally uses envCopy as the environment. Variable arguments are passed to the exec function and the external command is executed.
Remember that environment variables are passed between processes through the operating system, and all types of environment variable values reach the child process in the form of strings. For example, if the parent process contains the number 123 as an environment variable, the child process will receive "123" as a string.
The following example will create two Node scripts in the same directory: parent.js and child.js. The first script will call the second one. Let’s create these two files:
LISTING 8-3: Parent process sets environment variables (chapter8/03_environment_number_parent.js)
exec('node child.js', {env: {number: 123}}, function(err, stdout, stderr) {
if (err) { throw err; }
console.log('stdout:n', stdout);
console.log('stderr:n', stderr);
});
Save this code to parent.js. The following is the source code of the child process, and save them to child.js (see Example 8-4)
Example 8-4: Child process parsing environment variables (chapter8/04_environment_number_child.js)
console.log(typeof(number)); // → "string"
number = parseInt(number, 10);
console.log(typeof(number)); // → "number"
After you save this file as child.js, you can run the following command in this directory:
You will see the following output:
string
number
stderr:
As you can see, although the parent process passes a numeric environment variable, the child process receives it as a string (see the second line of output). On the third line, you parse the string into a number.
Generate child process
As you can see, you can use the child_process.exec() function to start an external process and call your callback function when the process ends. This is very simple to use, but it also has some disadvantages:
1. In addition to using command line parameters and environment variables, exec() cannot communicate with child processes
2. The output of the child process is cached, so you can't stream it, it may run out of memory
Fortunately, Node's child_process module allows more fine-grained control of the starting, stopping, and other general operations of child processes. You can start a new child process in the application, and Node provides a two-way communication channel that allows the parent process and the child process to send and receive string data to each other. The parent process can also perform some management operations on the child process, send signals to the child process, and force the child process to close.
Create child process
You can use the child_process.spawn function to create a new child process, see Example 8-5:
Example 8-5: Spawn a child process. (chapter8/05_spawning_child.js)
var spawn = require('child_process').spawn;
// Generate a child process to execute the "tail -f /var/log/system.log" command
var child = spawn('tail', ['-f', '/var/log/system.log']);
The above code generates a subprocess for executing the tail command, and takes "-f" and "/bar/log/system.log" as parameters. The tail command will monitor the /var/log/system.og file (if it exists) and output any appended new data to the stdout standard output stream. The spawn function returns a ChildProcess object, which is a pointer object that encapsulates the access interface of the real process. In this example we assign this new descriptor to a variable called child.
Listen to data from child processes
Any child process handle that contains the stdout attribute will use the standard output stdout of the child process as a stream object. You can bind the data event to this stream object, so that whenever a data block is available, the corresponding callback function, see the example below:
child.stdout.on(‘data’,function(data){
console.log(‘tail output: ‘ data);
});
Whenever the child process outputs data to stdout, the parent process will be notified and print the data to the console.
In addition to standard output, the process has another default output stream: the standard error stream. This stream is usually used to output error information.
In this example, if the /var/log/system.log file does not exist, the tail process will output a message similar to the following: "/var/log/system.log: No such file or directory", by monitoring stderr stream, the parent process will be notified when such an error occurs.
The parent process can listen to the standard error stream like this:
console.log('tail error output:', data);
});
The stderr attribute, like stdout, is also a read-only stream. Whenever the child process outputs data to the standard error stream, the parent process will be notified and output the data.
Send data to child process
In addition to receiving data from the output stream of the child process, the parent process can also write data to the standard input of the child process through the childPoces.stdin attribute to send data to and from the child process.
The child process can monitor the standard input data through the process.stdin read-only stream, but note that you must first resume the standard input stream because it is in a paused state by default.
Example 8-6 will create a program that contains the following functions:
1. 1 Application: A simple application that can receive integers from standard input, add them, and then output the added result to the standard output stream. As a simple computing service, this application simulates the Node process as an external service that can perform specific work.
2. Test the client of 1 application, send random integers, and then output the results. Used to demonstrate how the Node process spawns a child process and then lets it perform specific tasks.
Create a file named plus_one.js using the code in Example 8-6 below:
Example 8-6: 1 application (chapter8/06_plus_one.js)
In the above code, we wait for data from the stdin standard input stream. Whenever data is available, we assume it is an integer and parse it into an integer variable, then add 1 and output the result to the standard Output stream.
You can run this program with the following command:
After running, the program starts waiting for input. If you enter an integer and press Enter, you will see a number added by 1 displayed on the screen.
You can exit the program by pressing Ctrl-C.
A test client
Now you need to create a Node process to use the computing services provided by the previous "1 application".
First create a file named plus_one_test.js. The content is shown in Example 8-7:
Example 8-7: Test 1 application (chapter8/07_plus_one_test.js)
A subprocess used to run "1 application" is started from the first to the fourth line, and then the setInterval function is used to perform the following operations once every second:
1.. Create a new random number less than 10000
2. Pass this number as a string to the child process
3. Wait for the child process to reply with a string
4. Because you want to only receive the calculation result of 1 number at a time, you need to use child.stdout.once instead of child.stdout.on. If the latter is used, a callback function for the data event will be registered every 1 second. Each registered callback function will be executed when the stdout of the child process receives data, so you will find that the same calculation result will be output. Many times, this behavior is clearly wrong.
Receive notification when child process exits
When the child process exits, the exit event will be triggered. Example 8-8 shows how to listen for it:
Example 8-8: Listening to the exit event of the child process (chapter8/09_listen_child_exit.js)
// When the child process exits:
child.on('exit', function(code) {
console.log('child process terminated with code ' code);
});
In the last few lines of bolded code, the parent process uses the exit event of the child process to listen for its exit event. When the event occurs, the console displays the corresponding output. The exit code of the child process will be passed to the callback function as the first parameter. Some programs use a non-zero exit code to represent certain failure conditions. For example, if you try to execute the command "ls –al click filename.txt" but the file does not exist in the current directory, you will get an exit code of 1, see Example 8-9:
Example 8-9: Obtain the exit code of the child process (chapter8/10_child_exit_code.js)
In this example, the exit event triggers the callback function and passes the exit code of the child process to it as the first parameter. If the child process exits abnormally due to being killed by a signal, the corresponding signal code will be passed to the callback function as the second parameter, such as Example 8-10:
LISTING 8-10: Obtain the exit signal of the child process (chapter8/11_child_exit_signal.js)
In this example, a child process is started to perform the sleep operation for 10 seconds, but a SIGKILL signal is sent to the child process before 10 seconds, which will result in the following output:
Send signal and kill process
In this part, you will learn how to use signals to manage child processes. Signals are a simple way for a parent process to communicate with, or even kill, a child process.
Different signal codes represent different meanings. There are many signals, some of the most common ones are used to kill processes. If a process receives a signal that it does not know how to handle, the program will be interrupted abnormally. Some signals will be handled by child processes, while others can only be handled by the operating system.
Generally, you can use the child.kill method to send a signal to the child process. The SIGTERM signal is sent by default:
You can also send a specific signal by passing in a string identifying the signal as the only parameter of the kill method:
It should be noted that although the name of this method is kill, the signal sent does not necessarily kill the child process. If the child process handles the signal, the default signal behavior is overridden. Subprocesses written in Node can rewrite the definition of signal handlers as follows:
Now that you have defined the SIGUSR2 signal handler, when your process receives the SIGUSR2 signal again, it will not be killed, but will output the sentence "Got a SIGUSR2 signal". Using this mechanism, you can design a simple way to communicate with the child process and even command it. Although not as rich as using standard input, this method is much simpler.
Summary
In this chapter, we learned to use the child_process.exec method to execute external commands. This method does not use command line parameters, but passes the parameters to the child process by defining environment variables.
You also learned how to call external commands by calling the child_process.spawn method to spawn a child process. In this way, you can use input streams and output streams to communicate with the child process, or use signals to communicate with and kill the child process. process.