对应Node多过程exec办法执行流程源码剖析 文章,exec/execFile/fork办法都是执行的spwan办法;这篇文章也是重点去梳理spawn办法的设计思路
源码剖析
目前的源码是nodev12版本的;整体思路是差不多的,可供参考学习
core-modules/child_process.js
spawn办法
function spawn(file, args, options) {
const opts = .... // 标准化参数
const child = new ChildProcess();
....
child.spwan({
file: opts.file,
args: opts.args,
cwd: options.cwd,
....
});
return child;
}
internal/child_process.js
将依照执行流程,重点介绍这几个办法
ChildProcess.prototype.spawn办法
ChildProcess.prototype.spawn = function(options) {
let i =0;
if(options === null || typeof options !== 'object') { // options 如果不存在抛出异样
throw new ERR_INVALID_ARG_TYPE('options', 'Object', options)
}
let stdio = options.stdio || 'pipe'; // 规范的输入接口,通常采纳pipe(管道)
stdio = getValidStdio(stdio, false); // pipe(管道)在这创立的;上面有getValidStdio办法源码的阐明;
const ....
stdio = options.stdio = stdio.stdio;
// [
// {type: 'pipe', readable: true, writable: false, handle: Pipe},
// {type: 'pipe', readable: false, writable: true, handle: Pipe},
// {type: 'pipe', readable: false, wriatable: true, handle:Pipe}
// ];
....
const err = this._handle.spawn(options);
// 调用process_wrap进行实例化,创立子过程;err为0;示意子过程创立胜利。
// 谬误的err解决
if (err === UV_EACCES ||
err === UV_EAGAIN ||
.....
) {
process.nextTick(onErrorNT, this, err);
};
....
this.pid = this._handle.pid; // pid 为过程的id;
for (i = 0; i<stdio.length; i++) { // 建设父子之间的通信
const stream = stdio[i]; // 流,指的是每一条pipe;
...
if(stream.handle){
stream.scoket = creatSocket( this.pid !== 0 ? stream.handle:nunll, i > 0 );
// 失去socket实例;
};
};
};
...
// 输入流,输入流是i大于0
for (i>0 && this.pid!==0) {
...
stream.socket.on('close', ()=>{ // 绑定了close监听
maybeClose(this);
});
...
};
// 后续的谬误流也会绑定colse监听
...
// 别离 创立输出流、输入流、谬误流;上面是整顿好后的后果
// socket 通信被创立实现
this.stdin = stdio.length >= 1 && stdio[0].socket !== undefined ? stdio[0].socket : null;
this.stdout = stdio.length > 2 &&stdio[1].socket!== undefined ? stdio[1].socket : null;
this.stderr = stdio.length >= 3 && stdio[2].socket !== undefined ? stdio[2].socket : null;
// stdio对象:从输入输出流中拿到socket对象
// [
// {type: 'pipe', readable: true, writable: false, handle: Pipe, socket: Socket},
// {type: 'pipe', readable: false, writable: true, handle: Pipe, socket: Socket},
// {type: 'pipe', readable: false, wriatable: true, handle:Pipe, socket:Socket}
// ];
// 将数组中的每一项的socket拿进去赋值到stdin、stdout、stderr;在返回回调函数中,咱们会应用 stdout.on(‘data’, function
()=>{});".on"的形式来调用是因为,返回的是一个socket对象,socket对象的应用办法就是采纳on的形式。
...
this.stdio = [];
if (i=0; i<stdio.length; i++) { // 将三个流都放在stdio;别离是,输出流、输入流、谬误流;三个socket;
this.stdio.push(stdio[i].socket === undefined ? null : stdio[i].socket);
// fork,就会走进这里,创立两边通信的管道
if (ipc !== undefined) setUpChannel(this, ipc);
return err;
}
};
getValidStdio办法
function getValiStdio(stdio, sync) {
var ipc;
var ipcFd;
if(typeof stdio === 'string') {
stdio = stdioStringToArray(stdio); // 转化为数组 ['pipe', 'pipe', 'pipe'] [输出流、输入流、谬误流];
}else if(!Array.isArray(stdio)){
throw new ..... //谬误输入
}
}
while(stdio.length < 3)stdio.push(undefined);
stdio = stdio.reduce((acc, stdio, i) => {
if (stdio == null) {
...
};
if(stdio === 'ignore') {
// 传递的值是ignore,输入输出流不会创立,执行这个命令,不会收到反馈,静默形式去执行子过程。
...
}else if(stdio === 'pipe' || typeof stdio === 'number' && stdio < 0) {
// 对不同的管道进行不同的输出设置:
// 输出管道:子过程只能读不能写;
// 输入管道:子过程只能写不能读;
// 从这能够看出是单向的管道,不是双向的
var a = {
tyep:'pipe',
readable: i === 0,
writable: i !== 0
};
if(!sync) {
a.handle = new pipe (PipeConstants.SOCKET); // Pipe = internalBinding('pipe_warp);引入的c++源码;用于创立管道
}
....
// 以上形式在创立输入管道
return {
stdio, // Array;pipe的个性,如上增加的ver a ;输出和输入管道;
ipc, // fork中的双向通信管道,ipc通信;
ipcFd
};
})
createSocket
function createSocket (pipe, readable) { // 调用了net上面的Socket办法
return net.Socket({handle: pipe, readable, writable: !readable});
// 这个办法后续会在callback的时候具体详解,和C++中有交互;先看返回后果,一个socket实例、创立了callback函数;
}
总结
child.spawn办法的实现
- 通过
getValidStdio
来生成pipe(管道)创立管道实例;有三个别离是:输出管道、输入管道、error管道;没有创立socket
通信; normalizeSpawnArgs
进行参数的校验this._handle.spawn
创立子过程- 调用
createSocket
办法,将下面创立好的pipe
和子过程当中的socket
进行绑定;每一个pipe对应一个socket,用于父子过程通信;写入就用写入的pipe,读取就用读取的pipe,有异样存在就用异样的pipe;
发表回复