Cet article présente principalement les bases de Node.js Le module child_process a une certaine valeur de référence pour apprendre la communication entre les processus parent et enfant. Les amis intéressés peuvent s'y référer
Le module child_process fournit la même méthode que popen(3) pour générer des auto-processus. cette fonction est principalement fournie via la fonction child_process.spawn :
const spawn = require('child_process').spawn; const ls = spawn('ls', ['-lh', '/usr']); ls.stdout.on('data', (data) => { console.log(`stdout: ${data}`); }); ls.stderr.on('data', (data) => { console.log(`stderr: ${data}`); }); ls.on('close', (code) => { console.log(`child process exited with code $[code]`); });
Par défaut, les canaux stdin, stdout et stderr entre le processus Node.js et le processus enfant sont déjà Oui, normalement, cette méthode peut transmettre des données de manière non bloquante (notez que certains programmes utilisent des E/S tamponnées en ligne en interne. Puisque cela n'affecte pas Node.js, cela signifie transmettre des données. Les données fournies au processus enfant peuvent. ne pas être consommé immédiatement)
La méthode spawn de chid-process est générée à partir du processus de manière asynchrone, elle ne bloquera donc pas la EventBoucle de Node.js , cependant, la méthode child-process.spawnSync est synchrone, elle bloquera la boucle d'événements uniquement jusqu'à ce que le processus généré se termine ou se termine : Générez un client shell, puis utilisez le shell pour exécuter le programme. à la
fonction de rappel
child_process.execFile : similaire à exec, mais il ne générera pas de shell immédiatement
child_process .fork : génère un nouveau processus Node.js et exécute un module spécifique pour générer un canal IPC pour transférer des données entre le processus parent et le processus enfant
child_process.execSync : La différence avec exec est. qu'il bloque la boucle d'événements de Node.js. Cependant, child-process
child_process.execFileSync : La différence avec execFile Cela bloquera la boucle d'événements de Node.js. Cependant, dans certains cas particuliers, tels que les scripts shell automatisés, la méthode synchrone peut être plus utile dans la plupart des cas, la méthode synchrone aura un impact important sur les performances car elle bloquera la boucle d'événements
child_process.spawn(), child_process.fork(), child_process.exec() et child_process.execFile() sont tous des
API
objet implémente l'API EventEmitter de Node.js, afin que le processus parent puisse enregistrer une fonction d'écoute et être appelé lorsqu'un événement spécifique du processus enfant est déclenché. child_process.exec() et child_process.execFile() peuvent spécifier une fonction de rappel facultative qui est appelée à la fin du processus enfant.
Exécutez .bat et .cmd sur la plateforme Windows :
La différence entre child_process.exec et child_process.execFile peut varier selon la plateforme Différent et différent. Sur la plateforme Unit/
Linux
Ou vous pouvez également utiliser la méthode suivante :
const spawn = require('child_process').spawn; const bat = spawn('cmd.exe', ['/c', 'my.bat']);//使用shell方法指定一个shell选项 bat.stdout.on('data', (data) => { console.log(data); }); bat.stderr.on('data', (data) => { console.log(data); }); bat.on('exit', (code) => { console.log(`Child exited with code $[code]`); });
const exec = require('child_process').exec;//产生exec,同时传入.bat文件 exec('my.bat', (err, stdout, stderr) => { if (err) { console.error(err); return; } console.log(stdout); });
Le paramètre maxBuffer dans les options représente la quantité maximale de données autorisée par stdout/stderr Si la quantité de données est dépassée, le processus enfant sera tué. La valeur par défaut est 200*1024 bits ; '. La fonction de rappel est appelée à la fin du processus et les paramètres sont error, stdout et stderr. Cette méthode renvoie un objet ChildProcess.
Le code ci-dessus génère un shell, puis utilise ce shell pour exécuter des commandes et met en cache les résultats. L'attribut error.code dans la fonction de rappel représente le code de sortie du processus enfant, error.signal représente le signal pour terminer le processus et tout code non 0 représente une erreur. Les valeurs par défaut des paramètres d'options sont les suivantes :
const exec = require('child_process').exec; const child = exec('cat *.js bad_file | wc -l', (error, stdout, stderr) => { console.log(`stdout: ${stdout}`); console.log(`stderr: ${stderr}`); if (error !== null) { console.log(`exec error: ${error}`); } });
Si le délai d'attente est différent de 0, alors le processus parent enverra un signal. Ce signal est spécifié par killSignal. La valeur par défaut est "SIGTERM" à. mettre fin au processus enfant. Si le processus enfant dépasse le délai spécifié par timeout. Remarque : Contrairement à l'appel de la méthode exec sur les systèmes POSIX, child_process.exec ne remplace pas le thread actuel, mais utilise un shell pour exécuter la commande
{ encoding: 'utf8', timeout: 0, maxBuffer: 200*1024,//stdout和stderr允许的最大的比特数据,超过她子进程就会被杀死 killSignal: 'SIGTERM', cwd: null, env: null }
child_process.execFile(file[, args][ , options ][, rappel])
où file représente le fichier qui doit être exécuté. child_process.execFile() est très similaire à exec, mais cette méthode ne génère pas de shell. Le fichier exécutable spécifié générera immédiatement un nouveau thread, son efficacité est donc supérieure à celle de child_process.exec.
const execFile = require('child_process').execFile; const child = execFile('node', ['--version'], (error, stdout, stderr) => { if (error) { throw error; } console.log(stdout); });
因为不会产生shell,一些I/O redirection和file globbing这些行为不支持
child_process.fork(modulePath[, args][, options])
其中modulePath表示要在子线程中执行的模块。其中options中的参数silent如果设置为true,那么子进程的stdin, stdout, stderr将会被传递给父进程,如果设置为false那么就会从父进程继承。execArgv默认的值为process.execArgv,execPath表示用于创建子进程的可执行文件。这个fork方法是child_process.spawn的一种特殊用法,用于产生一个Node.js的子进程,和spawn一样返回一个ChildProcess对象。返回的ChildProcess会有一个内置的传输通道用于在子进程和父进程之间传输数据(用ChildProcess的send方法完成)。我们必须注意,产生的Node.js进程和父进程之间是独立的,除了他们之间的IPC传输通道以外。每一个进程有独立的内存,有自己独立的V8引擎。由于产生一个子进程需要其他额外的资源分配,因此产生大量的子进程不被提倡。默认情况下,child_process.fork会使用父进程的process.execPath来产生一个Node.js实例,options中的execPath允许指定一个新的路径。通过指定execPath产生的新的进程和父进程之间通过文件描述符(子进程的环境变量NODE_CHANNEL_FD)来通信。这个文件描述符上的input/output应该是一个JSON对象。和POSIX系统调用fork不一样的是,child_process.fork不会克隆当前的进程
最后,我们来看看子进程和父进程之间是如何通信的:
服务器端的代码:
var http = require('http'); var cp = require('child_process'); var server = http.createServer(function(req, res) { var child = cp.fork(dirname + '/cal.js'); //每个请求都单独生成一个新的子进程 child.on('message', function(m) { res.end(m.result + '\n'); }); //为其指定message事件 var input = parseInt(req.url.substring(1)); //和postMessage很类似,不过这里是通过send方法而不是postMessage方法来完成的 child.send({input : input}); }); server.listen(8000);
子进程的代码:
function fib(n) { if (n < 2) { return 1; } else { return fib(n - 2) + fib(n - 1); } } //接受到send传递过来的参数 process.on('message', function(m) { //console.log(m); //打印{ input: 9 } process.send({result: fib(m.input)}); });
child_process.spawn(command[, args][, options])
其中options对象的stdio参数表示子进程的stdio配置;detached表示让子进程在父进程下独立运行,这个行为与平台有关;shell如果设置为true那么就会在shell中执行命令。这个方法通过指定的command来产生一个新的进程,如果第二个参数没有指定args那么默认就是一个空的数组,第三个参数默认是如下对象,这个参数也可以用于指定额外的参数:
{ cwd: undefined, //产生这个进程的工作目录,默认继承当前的工作目录 env: process.env//这个参数用于指定对于新的进程可见的环境变量,默认是process.env }
其中cwd用于指定子进程产生的工作目录,如果没有指定表示的就是当前工作目录。env用于指定新进程的环境变量,默认为process.env。下面的例子展示了使用ls -lh/usr来获取stdout,stderr以及exit code:
const spawn = require('child_process').spawn; const ls = spawn('ls', ['-lh', '/usr']); ls.stdout.on('data', (data) => { console.log(`stdout: ${data}`); }); ls.stderr.on('data', (data) => { console.log(`stderr: ${data}`); }); ls.on('close', (code) => { console.log(`child process exited with code $[code]`); });
下面是一个很详细的运行"ps ax|grep ssh"的例子:
const spawn = require('child_process').spawn; const ps = spawn('ps', ['ax']); const grep = spawn('grep', ['ssh']); ps.stdout.on('data', (data) => { grep.stdin.write(data); }); ps.stderr.on('data', (data) => { console.log(`ps stderr: ${data}`); }); ps.on('close', (code) => { if (code !== 0) { console.log(`ps process exited with code $[code]`); } grep.stdin.end(); }); grep.stdout.on('data', (data) => { console.log(`${data}`); }); grep.stderr.on('data', (data) => { console.log(`grep stderr: ${data}`); }); grep.on('close', (code) => { if (code !== 0) { console.log(`grep process exited with code $[code]`); } });
用下面的例子来检查错误的执行程序:
const spawn = require('child_process').spawn; const child = spawn('bad_command'); child.on('error', (err) => { console.log('Failed to start child process.'); });
options.detached:
在windows上,把这个参数设置为true的话,这时候如果父进程退出了那么子进程还会继续运行,而且子进程有自己的console window。如果把子进程设置了这个为true,那么就不能设置为false了。在非window平台上,如果把这个设置为true,子进程就会成为进程组合和session的leader,这时候子进程在父进程退出以后会继续执行,不管子进程是否detached。可以参见setsid(2)。
默认情况下,父进程会等待detached子进程,然后退出。要阻止父进程等待一个指定的子进程可以使用child.unref方法,这个方法会让父进程的事件循环不包括子进程,这时候允许父进程独立退出,除非在父进程和子进程之间有一个IPC通道。下面是一个detach长期运行的进程然后把它的输出导向到一个文件:
const fs = require('fs'); const spawn = require('child_process').spawn; const out = fs.openSync('./out.log', 'a'); const err = fs.openSync('./out.log', 'a'); const child = spawn('prg', [], { detached: true,//依赖于父进程 stdio: [ 'ignore', out, err ] }); child.unref();//允许父进程单独退出,不用等待子进程
当使用了detached选项去产生一个长期执行的进程,这时候如果父进程退出了那么子进程就不会继续执行了,除非指定了一个stdio配置(不和父进程之间有联系)。如果父进程的stdio是继承的,那么子进程依然会和控制终端之间保持关系。
options.stdio
这个选项用于配置父进程和子进程之间的管道。默认情况下,子进程的stdin,stdout,stderr导向了ChildProcess这个对象的child.stdin,child.stdout,child.stderr流,这和设置stdio为['pipe','pipe','pipe']是一样的。stdio可以是下面的任何一个字符串:
'pipe':相当于['pipe','pipe','pipe'],为默认选项
'ignore':相当于['ignore','ignore','ignore']
'inherit':相当于[process.stdin,process.stdout,process.stderr]或者[0,1,2]
一般情况下,stdio是一个数组,每一个选项对应于子进程的fd。其中0,1,2分别对应于stdin,stdout,stderr。如果还设置了多于的fds那么就会用于创建父进程和子进程之间的额外的管道,可以是下面的任何一个值:
'pipe':为子进程和父进程之间创建一个管道。父进程管道的末端会作为child_process对象的ChildProcess.stdio[fd]而存在。fds0-2创建的管道在ChildProcess.stdin,ChildProcess.stdout,ChildProcess.stderr也是存在的
'ipc':用于创建IPC通道用于在父进程和子进程之间传输消息或者文件描述符。ChildProcess对象最多有一个IPC stdio文件描述符,使用这个配置可以启用ChildProcess的send方法,如果父进程在文件描述符里面写入了JSON对象,那么ChildProcess.on("message")事件就会在父进程上触发。如果子进程是Node.js进程,那么ipc配置就会启用子进程的process.send(), process.disconnect(), process.on('disconnect'), and process.on('message')方法。
'ignore':让Node.js子进程忽视文件描述符。因为Node.js总是会为子进程开启fds0-2,设置为ignore就会导致Node.js去开启/dev/null,同时把这个值设置到子进程的fd上面。
'strem':和子进程之间共享一个可读或者可写流,比如file,socket,pipe。这个stream的文件描述符和子进程的文件描述符fd是重复的。注意:流必须有自己的文件描述符
正整数:表示父进程的打开的文件描述符。和stream对象可以共享一样,这个文件描述符在父子进程之间也是共享的
null/undefined:使用默认值。stdio的fds0,1,2管道被创建(stdin,stdout,stderr)。对于fd3或者fdn,默认为'ignore'
const spawn = require('child_process').spawn; // Child will use parent's stdios //使用父进程的stdios spawn('prg', [], { stdio: 'inherit' }); //产生一个共享process.stderr的子进程 // Spawn child sharing only stderr spawn('prg', [], { stdio: ['pipe', 'pipe', process.stderr] }); // Open an extra fd=4, to interact with programs presenting a // startd-style interface. spawn('prg', [], { stdio: ['pipe', null, null, null, 'pipe'] });
注意:当子进程和父进程之间建立了IPC通道,同时子进程为Node.js进程,这时候开启的具有IPC通道的子进程(使用unref)直到子进程注册了一个disconnect的事件处理句柄process.on('disconnect'),这样就会允许子进程正常退出而不会由于IPC通道的打开而持续运行。
Class: ChildProcess
这个类的实例是一个EventEmitters,用于代表产生的子进程。这个类的实例不能直接创建,必须使用 child_process.spawn(), child_process.exec(), child_process.execFile(), or child_process.fork()来完成
'close'事件:
其中code表示子进程退出的时候的退出码;signal表示终止子进程发出的信号;这个事件当子进程的stdio stream被关闭的时候触发,和exit事件的区别是多个进程可能共享同一个stdio streams!(所以一个进程退出了也就是exit被触发了,这时候close可能不会触发)
'exit'事件:
其中code表示子进程退出的时候的退出码;signal表示终止子进程发出的信号。这个事件当子进程结束的时候触发,如果进程退出了那么code表示进程退出的exit code,否则没有退出就是null。如果进程是由于收到一个信号而终止的,那么signal就是这个信号,是一个string,默认为null。
注意:如果exit事件被触发了,子进程的stdio stream可能还是打开的;Node.js为SUGUBT,SIGTERM创建信号处理器,而且Node.js进程在收到信号的时候不会马上停止。Node.js会进行一系列的清理工作,然后才re-raise handled signal。见waitpid(2)
'disconnect'事件:
在子进程或者父进程中调用ChildProcess.disconnect()方法的时候会触发。这时候就不能再发送和接受信息了,这是ChildProcess.connected就是false了
'error'事件:
当进程无法产生的时候,进程无法杀死的时候,为子进程发送消息失败的时候就会触发。注意:当产生错误的时候exit事件可能会也可能不会触发。如果你同时监听了exit和error事件,那么就要注意是否会无意中多次调用事件处理函数
'message'事件:
message参数表示一个解析后的JSON对象或者初始值;sendHandle可以是一个net.Socket或者net.Server对象或者undefined。当子进程调用process.send时候触发
child.connected:
调用了disconnect方法后就会是false。表示是否可以在父进程和子进程之间发送和接受数据,当值为false就不能发送数据了
child.disconnect()
关闭子进程和父进程之间的IPC通道,这时候子进程可以正常退出如果没有其他的连接使得他保持活动。这时候父进程的child.connected和子进程的process.connected就会设置为false,这时候不能传输数据了。disconnect事件当进程没有消息接收到的时候被触发,当调用child.disconnect时候会立即触发。注意:当子进程为Node.js实例的时候如child_process.fork,这时候process.disconnect方法就会在子进程中调用然后关闭IPC通道。
child.kill([signal])
为子进程传入消息,如果没有指定参数那么就会发送SIGTERM信号,可以参见signal(7)来查看一系列信号
const spawn = require('child_process').spawn; const grep = spawn('grep', ['ssh']); grep.on('close', (code, signal) => { console.log( `child process terminated due to receipt of signal ${signal}`); }); // Send SIGHUP to process grep.kill('SIGHUP');
ChildProcess对象在无法传输信号的时候会触发error事件。为一个已经退出的子进程发送信号虽然无法报错但是可能导致无法预料的结果。特别的,如果这个PID已经被分配给另外一个进程那么这时候也会导致无法预料的结果。
child.pid:
返回进程的PID值
const spawn = require('child_process').spawn; const grep = spawn('grep', ['ssh']); console.log(`Spawned child pid: ${grep.pid}`); grep.stdin.end();//通过grep.stdin.end结束
child.send(message[, sendHandle][, callback])
当父子进程之间有了IPC通道,child.send就会为子进程发送消息,当子进程为Node.js实例,那么可以用process.on('message')事件接收
父进程为:
const cp = require('child_process'); const n = cp.fork(`${dirname}/sub.js`); n.on('message', (m) => { console.log('PARENT got message:', m); }); n.send({ hello: 'world' });
子进程为:
process.on('message', (m) => { console.log('CHILD got message:', m); }); process.send({ foo: 'bar' });
子进程使用process.send方法为父进程发送消息。有一个特例,发送{cmd: 'NODE_foo'}。当一个消息在他的cmd属性中包含一个NODE_前缀会被看做使用Node.js核心(被Node.js保留)。这时候不会触发子进程的process.on('message')。而是使用process.on('internalMessage')事件,同时会被Node.js内部消费,一般不要使用这个方法。sendHandle这个用于给子进程传入一个TCP Server或者一个socket,为process.on('message')回调的第二个参数接受。callback当消息已经发送,但是子进程还没有接收到的时候触发,这个函数只有一个参数成功为null否则为Error对象。如果没有指定callback同时消息也不能发送ChildProcess就会触发error事件。当子进程已经退出就会出现这个情况。child.send返回false如果父子进程通道已经关闭,或者积压的没有传输的数据超过一定的限度,否则这个方法返回true。这个callback方法可以用于实现流控制:
下面是发送一个Server的例子:
const child = require('child_process').fork('child.js'); // Open up the server object and send the handle. const server = require('net').createServer(); server.on('connection', (socket) => { socket.end('handled by parent'); }); server.listen(1337, () => { child.send('server', server); });
子进程接受这个消息:
process.on('message', (m, server) => { if (m === 'server') { server.on('connection', (socket) => { socket.end('handled by child'); }); } });
这时候server就被子进程和父进程共享了,一些连接可以被父进程处理,一些被子进程处理。上面的例子如果使用dgram那么就应该监听message事件而不是connection,使用server.bind而不是server.listen,但是当前只在UNIX平台上可行。
下面的例子展示发送一个socket对象(产生两个子进程,处理normal和special优先级):
父进程为:
const normal = require('child_process').fork('child.js', ['normal']); const special = require('child_process').fork('child.js', ['special']); // Open up the server and send sockets to child const server = require('net').createServer(); server.on('connection', (socket) => { // If this is special priority if (socket.remoteAddress === '74.125.127.100') { special.send('socket', socket); return; } // This is normal priority normal.send('socket', socket); }); server.listen(1337);
子进程为:
process.on('message', (m, socket) => { if (m === 'socket') { socket.end(`Request handled with ${process.argv[2]} priority`); } });
当socket被发送到子进程的时候那么父进程已经无法追踪这个socket什么时候被销毁的。这时候.connections属性就会成为null,因此我们建议不要使用.maxConnections。注意:这个方法在内部JSON.stringify去序列化消息
child.stderr:
一个流对象,是一个可读流表示子进程的stderr。他是child.stdio[2]的别名,两者表示同样的值。如果子进程是通过stdio[2]产生的,设置的不是pipe那么值就是undefined。
child.stdin:
一个可写的流对象。注意:如果子进程等待读取输入,那么子进程会一直等到流调用了end方法来关闭的时候才会继续读取。如果子进程通过stdio[0]产生,同时不是设置的pipe那么值就是undefined。child.stdin是child.stdio[0]的别名,表示同样的值。
const spawn = require('child_process').spawn; const grep = spawn('grep', ['ssh']); console.log(`Spawned child pid: ${grep.pid}`); grep.stdin.end();//通过grep.stdin.end结束
child.stdio:
一个子进程管道的稀疏数组,是 child_process.spawn()函数的stdio选项,同时这个值被设置为pipe。child.stdio[0], child.stdio[1], 和 child.stdio[2]也可以通过child.stdin, child.stdout, 和 child.stderr访问。下面的例子中只有子进程的fd1(也就是stdout)被设置为管道,因此只有父进程的child.stdio[1]是一个流,其他的数组中对象都是null:
const assert = require('assert'); const fs = require('fs'); const child_process = require('child_process'); const child = child_process.spawn('ls', { stdio: [ 0, // Use parents stdin for child 'pipe', // Pipe child's stdout to parent fs.openSync('err.out', 'w') // Direct child's stderr to a file ] }); assert.equal(child.stdio[0], null); assert.equal(child.stdio[0], child.stdin); assert(child.stdout); assert.equal(child.stdio[1], child.stdout); assert.equal(child.stdio[2], null); assert.equal(child.stdio[2], child.stderr);
child.stdout:
一个可读流,代表子进程的stdout。如果子进程产生的时候吧stdio[1]设置为除了pipe以外的任何数,那么值就是undefined。其值和child.stdio[1]一样
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!