乐趣区

关于node.js:node中childProcess库spawn底层流程

对应 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 办法的实现

  1. 通过 getValidStdio 来生成 pipe(管道)创立管道实例;有三个别离是:输出管道、输入管道、error 管道;没有创立socket 通信;
  2. normalizeSpawnArgs进行参数的校验
  3. this._handle.spawn创立子过程
  4. 调用 createSocket 办法,将下面创立好的 pipe 和子过程当中的 socket 进行绑定;每一个 pipe 对应一个 socket,用于父子过程通信;写入就用写入的 pipe,读取就用读取的 pipe,有异样存在就用异样的 pipe;
退出移动版