8

笔者近几月某公司WiFi测试平台项目中,了解到他们测试WiFi一直是人工手动测试,因此在老师提议下,决定采用puppeteer去实现页面数据的爬取和更改的自动化,在实施过程中,发现--路由管理页面--登陆后只有cookie加密,并且页面的数据和操作通过向同一接口传递不同参数实现,因此可以通过promise-request方法快速的实现上述功能。
本文主要记录、总结:


1. node.js子进程的创建

node创建子进程的方法可以参考上述官方文档,里面有详细的配置和说明。在本文中,笔者着重介绍笔者使用到的child_process.spawn()方法和child_process.fork()方法。

  • child_process.spawn()方法:
    Master进程代码:

    const child_process = require('child_process');//引入child_process模块
    const {recLog} = require("./utils/log");//由于执行父进程,子进程无法在控制台打印信息,因此封装了log4js方法。
    
    const gChildProcessNumber = 3;//创建子进程数量-全局
    creatProcess();
    
    async function creatProcess() {
    for (let i = 0; i < gChildProcessNumber; i += 1) {
      const workerProcess = child_process.spawn('node', ['./Request_Test/ChildProcess.js']);
    }
    }

    Child进程代码:

    const {recLog} = require("./utils/log");
    recLog(`子进程创建成功,执行ChildProcess.js文件,进程pid为:${process.pid}`)

    执行结果:
    image.png
    注意:这里使用child_process.spawn()方法,由于笔者子进程运行的文件为js文件,因此直接用node运行,如果运行的是其他文件,比如笔者之前运行的×××.e2e.js文件时,笔者运行指令为npm test ×××.e2e.js,就需要写成如下:

    const workerProcess = child_process.spawn('npm.cmd', ['test', './Request_Test/request.e2e.js'], {
      });

    因为在Windows上,当我们执行npm 时,我们实际执行的是npm. cnd批处理,所以一定要显示的执行npm.cmd否则会出现Error: spawn npm ENOENT的错误。

  • child_process.fork()方法:
    Master进程代码:

    const child_process = require('child_process');
    const {recLog} = require("./utils/log");
    const gChildProcessNumber = 3;
    creatProcess();
    
    async function creatProcess() {
    for (let i = 0; i < gChildProcessNumber; i += 1) {
      const workerProcess = child_process.fork('./Request_Test/ipcChildProcess.js', {
        silent: true,
      });
     }
    }

    Child进程代码:

    const {recLog} = require("./utils/log");
    recLog('子进程Ready!')

    执行结果如下:
    image.png


2. fork和spawn的区别与联系

Spawn是一个旨在运行系统命令的命令。当您运行spawn时,您将向其发送一个系统命令,该命令将在其自己的进程上运行,但不会在节点进程中执行任何其他代码。您可以为已生成的进程添加侦听器,以允许您的代码与生成的进程交互,但不会创建新的V8实例(除非当然您的命令是另一个Node命令,但在这种情况下应该使用fork!)和在处理器上只有一个节点模块副本处于活动状态。
Fork是一个特殊的spawn实例它运行一个新的V8引擎实例。意思是,您可以创建多个工作程序,在完全相同的Node代码库上运行,或者为特定任务使用不同的模块。这对创建工作池最有用。虽然节点异步事件模型允许机器的单个核心被相当有效地使用,但它不允许节点进程使用多核机器。最简单的方法是在单个处理器上运行相同程序的多个副本。

下面我们在看看官方文档中,对fork的解释:

child_process.fork() 方法是 child_process.spawn() 的特例,专门用于衍生新的 Node.js 进程。 与 child_process.spawn() 一样,返回 ChildProcess 对象。 返回的 ChildProcess 将有额外的内置通信通道,允许消息在父进程和子进程之间来回传递。 详见 subprocess.send()。
请记住,衍生的 Node.js 子进程独立于父进程,除了两者之间建立的 IPC 通信通道每个进程都有自己的内存,具有自己的 V8 实例。 由于需要额外的资源分配,不建议衍生大量子 Node.js 进程。

综上所述:

  • child_process.fork() 方法是特殊的 child_process.spawn()方法,即spawn()方法包含了fork()方法。
  • child_process.fork() 方法默认运行node指令创建进程,该方法创建的子进程生成时便与父进程创建了IPC通信通道,且每个由该方法创建的子进程都具有自己的内存和V8实例

3. Node原生实现进程间通信

Master进程代码:

const child_process = require('child_process');
const {recLog} = require("./utils/log");


const gChildProcessNumber = 3;
creatProcess();

async function creatProcess() {
  for (let i = 0; i < gChildProcessNumber; i += 1) {
    const workerProcess = child_process.fork('./Request_Test/ipcChildProcess.js', {
      silent: true,
    });
    workerProcess.on('message', (Info) => {
      recLog(`父进程收到消息-state:${Info.state}`)
      workerProcess.send({command: 'init'});
    })
  }
}

Child进程代码:

const {recLog} = require("./utils/log");
process.send({state: 'action'});
recLog(`子进程${process.pid}发送action请求。`)
process.on("message", async (mess) => {
  recLog(`子进程${process.pid}收到消息-command:${mess.command}`)
})

Node原生支持通信结果:
image.png


4. WebSocket实现进程间通信

Master进程代码:

const child_process = require('child_process');
const ws = require('nodejs-websocket')
const {recLog} = require("./utils/log");

const gChildProcessNumber = 3;
creatProcess();

function OpenWebSocketServer(port) {
  ws.createServer((conn) => {
    conn.on('text', (data) => { // 收到客户端数据的回调方法
      recLog(`父进程收到子进程消息:${data}`);
      switch (data) {
        case 'connected':
          recLog('进程间通信链接建立成功!')
          conn.sendText('init')
          break;
        default:
          recLog(`父进程收到未定义进程间消息:${data}!`)
          break;
      }
    })
    conn.on('connection', (connection) => {
      recLog(connection)
    })
    conn.on('close', (e) => { // 关闭服务器的回调方法
      recLog(`${e}服务链接关闭`)
    })
    conn.on('error', (e) => { // 服务端连接异常的回调方法
      recLog(`${e}服务端异常`)
    })
  }).listen(port) // 监听8181端口,跟客户端连接端口对应
  recLog(`${port}号端口,服务端已开启`)
}

async function creatProcess() {
  for (let i = 0; i < gChildProcessNumber; i += 1) {
    const workerProcess = child_process.spawn('node', ['./Request_Test/WebSocketChildProcess.js']);
    OpenWebSocketServer(workerProcess.pid)
  }
}

Child进程代码:

const request = require('request');
const {recLog} = require("./utils/log");
global.WebSocket = require('ws');

const ws = new WebSocket(`ws://localhost:${process.pid}`)
ws.onopen = function () {
  recLog('连接服务器成功')
  ws.send('connected')
}

ws.onmessage = function (e) {
  recLog(`子进程收到父进程消息:${e.data}`)
  const {data} = e;
  switch (data){
    case 'init':
      recLog(`子进程收到init指令,执行init操作!`)
      break;
    default:
      recLog('子进程未收到')
  }
}
ws.onclose = function () {
  recLog('连接已关闭')
}

WebSocket通信结果如下:
image.png


补充:笔者在新的项目中遇到了需要模拟CMD按序执行一系列指令的方法,特此补充,有需要自取!

var nodeCmd = require('node-cmd');
const testCommand = ['cd C:\\Users\\Administrator\\Desktop\\teaching-platform-node-mpi\\triedges\\m02a0', 'mpiexec -n 4 ./mflowMPI_v40.5.exe', 'forceget_v40.5.exe'];

let commandStr = '';
commandStr = testCommand.join(' && ');
console.log(commandStr)
function runCmdTest() {
  nodeCmd.run(
    commandStr,
    function (err, data, stderr) {
      recLog(data)
    }
  );
}
runCmdTest()

参考文档:


很白的小白
145 声望125 粉丝