中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

詳解從Node.js的child_process模塊來學習父子進程之間的通信

發布時間:2020-10-16 05:21:36 來源:腳本之家 閱讀:834 作者:liangklfang 欄目:web開發

child_process模塊提供了和popen(3)一樣的方式來產生自進程,這個功能主要是通過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]`); 
}); 

默認情況下,Node.js進程和子進程之間的stdin,stdout,stderr管道是已經存在的。通常情況下這個方法可以以一種非阻塞的方式來傳遞數據。(注意,有些程序在內部使用line-buffered I/O。因為這也不會影響到Node.js,這意味著傳遞給子進程的數據可能不會馬上消費)。

chid-process的spawn方法是通過一種異步的方式來產生自進程的,因此不會阻塞Node.js的事件循環,然而child-process.spawnSync方法是同步的,他會阻塞事件循環只到產生的進程退出或者終止。

child_process.exec:產生一個shell客戶端,然后使用shell來執行程序,當完成的時候傳遞給回調函數一個stdout和stderr

child_process.execFile:和exec相似,但是他不會馬上產生一個shell

child_process.fork:產生一個新的Node.js進程,同時執行一個特定的模塊來產生IPC通道,進而在父進程和子進程之間傳輸數據

child_process.execSync:和exec不同之處在于會阻塞Node.js的事件循環,然而child-process

child_process.execFileSync:和execFile不同之處在于會阻塞Node.js的事件循環,然而child-process在一些特殊情況下,例如自動化shell腳本,同步的方法可能更加有用。多數情況下,同步的方法會對性能產生重要的影響,因為他會阻塞事件循環

child_process.spawn(), child_process.fork(), child_process.exec(), and child_process.execFile()都是異步的API。每一個方法都會產生一個ChildProcess實例,而且這個對象實現了Node.js的EventEmitter這個API,于是父進程可以注冊監聽函數,在子進程的特定事件觸發的時候被調用。 child_process.exec() 和 child_process.execFile()可以指定一個可選的callback函數,這個函數在子進程終止的時候被調用。

在windows平臺上執行.bat和.cmd:

child_process.exec和child_process.execFile的不同之處可能隨著平臺不同而有差異。在Unit/Linux/OSX平臺上execFile更加高效,因為他不會產生shell。在windows上,.bat/.cmd在沒有終端的情況下是無法執行的,因此就無法使用execFile(child_process.spawn也無法使用)。在window上,.bat/.cmd可以使用spawn方法,同時指定一個shell選項;或者使用child_process.exec或者通過產生一個cmd.exe同時把.bat/.cmd文件傳遞給它作為參數(child_process.exec就是這么做的)。

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); 
}); 

child_process.exec(command[, options][, callback])

其中options中的maxBuffer參數表示stdout/stderr允許的最大的數據量,如果超過了數據量那么子進程就會被殺死,默認是200*1024比特;killSignal默認是'SIGTERM'。其中回調函數當進程結束時候調用,參數分別為error,stdout,stderr。這個方法返回的是一個ChildProcess對象。

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}`); 
  } 
}); 

上面的代碼產生一個shell,然后使用這個shell執行命令,同時對產生的結果進行緩存。其中回調函數中的error.code屬性表示子進程的exit code,error.signal表示結束這個進程的信號,任何非0的代碼表示出現了錯誤。默認的options參數值如下:

{ 
 encoding: 'utf8', 
 timeout: 0, 
 maxBuffer: 200*1024,//stdout和stderr允許的最大的比特數據,超過她子進程就會被殺死 
 killSignal: 'SIGTERM', 
 cwd: null, 
 env: null 
} 

如果timeout非0,那么父進程就會發送信號,這個信號通過killSignal指定,默認是"SIGTERM"來終結子進程,如果子進程超過了timeout指定的時間。注意:和POSIX系統上調用exec方法不一樣的是,child_process.exec不會替換當前線程,而是使用一個shell去執行命令

child_process.execFile(file[, args][, options][, callback])

其中file表示需要執行的文件。 child_process.execFile()和exec很相似,但是這個方法不會產生一個shell。指定的可執行文件會馬上產生一個新的線程,因此其效率比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]一樣

 以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

衢州市| 察隅县| 萨迦县| 同江市| 雷波县| 青神县| 赤城县| 平阳县| 马关县| 建宁县| 甘泉县| 龙海市| 合山市| 浪卡子县| 三门县| 柳州市| 鄂托克旗| 荥阳市| 卓尼县| 佛学| 东丰县| 霍山县| 长葛市| 麻城市| 沂水县| 乐平市| 肇源县| 吴江市| 鲜城| 台南县| 会东县| 东海县| 卓尼县| 江达县| 封丘县| 龙南县| 班戈县| 宁德市| 商南县| 汝南县| 普定县|