Nodejs 第十五章 child_process

child_process 子进程

child_process 模块是 Node.js 的一个核心模块,可以从 Node.js 应用程序中创建和管理子进程。子进程是从父进程(你的 Node.js 应用)中派生出来的新进程,它可以执行系统命令、运行其他应用或执行 JavaScript 文件。使用子进程,可以实现多任务处理,提高应用的性能和效率

什么是子进程

  • 子进程是操作系统中由其他进程(父进程)创建的进程
    • 使用子进程时需要注意安全问题,特别是当执行的命令包含来自用户输入的部分时,需要防止注入攻击。
    • 过多的子进程可能会耗尽系统资源,需要合理管理子进程的数量

使用场景

  1. 执行系统命令 :从 Node.js 应用中执行 Shell 命令时,如 ls, grep, mkdir 等。
  2. 运行其他应用程序:在 Node.js 应用中运行其他外部应用程序,比如 Python 脚本、Ruby 程序或其他可执行文件。
  3. 并行处理:需要并行执行多个任务以提高应用性能时,可以使用子进程。这在处理 CPU 密集型任务时尤其有用。
  4. 资源密集型操作:对于一些资源密集型或长时间运行的操作,使用子进程可以避免阻塞 Node.js 的事件循环。

创建子进程

  • Nodejs创建子进程共有7个常用的API:

    有加Sync后缀的是同步API 没加的是异步API

    • 并且后面的所有Node的API都是这个规则
    • 异步一般会给我们提供一个回调函数,返回一个buffer(Buffer相当于是一个字节的数组,数组中的每一项对应一个字节的大小),可以帮我们执行shell命令,或者跟软件交互
  1. spawn 执行命令
  2. exec 执行命令
  3. execFile 执行可执行文件
  4. fork 创建node子进程
  5. execSync 执行命令 同步执行
  6. execFileSync 执行可执行文件 同步执行
  7. spawnSync 执行命令 同步执行

exec 执行命令

  • 我们在终端可以输入类似node -v的命令来获取当前node的版本,那在代码中,我们要怎么获取?
    • 答案是通过exec执行命令啦,然后会返回对应的内容,我们要用回调函数接收一下

      1. 回调的有三个参数,意思分别都写在下图的注释中了,需要注意的是输出的是buffer,我们要能看懂,需要使用toString()进行转化
    • stdstdout 中代表的是 "standard" 的缩写,意为"标准"。因此,stdout 完整的英文是 "standard output",指的是标准输出流。同理,stdin 表示 "standard input"(标准输入流),而 stderr 表示 "standard error"(标准错误流)。这些术语来自 UNIX 和类 UNIX 系统的传统,现在被广泛应用于各种操作系统和编程环境中

  • 上面是异步的写法,那其实还有同步的写法,会更简单一些,也更常用
js 复制代码
const { execSync, spawn, fork } = require('child_process');

const node_version = execSync('node -v').toString()
console.log(node_version);
  • 所以执行较小的shell命令,想要立马拿到结果的shell命令,就适合使用execSync去进行。

    • exec是有字节上限的,不能超过200kb,否则会报错
    • 只要是操作系统里面有的操作命令都可以执行
    • 有返回值的话,就可以用变量接收一下,如果没有返回值就直接输出就ok了
    js 复制代码
    const node_version = execSync('node -v').toString()//有返回值用变量接收
    execSync('mkdir test')//没返回值就直接使用(创建test文件夹)

execSync案例

  • 通过这个命令,其实我们就可以灵活进行运用。

    • 在日常使用电脑的时候,作为程序员,我使用的软件是很多的。如果每天重新开机电脑,我都需要一个个打开软件,就会非常麻烦繁琐。那我能不能一次性打开我们想要使用的软件呢?
    • 其实是有办法的,通过我们的execSync命令就可以实现
    js 复制代码
    execSync('软件地址')//这样就可以打开我的软件
  • 进行功能封装:

js 复制代码
const { execSync } = require('child_process');

function openSoftwares(softwares) {
  for (const [name, path] of Object.entries(softwares)) {
    console.log(`打开了软件: ${name}...`);
    execSync(path);
  }
}

// 示例用法
const softwareMap = {
  "记事本": "notepad.exe", // Windows上的记事本
  "计算器": "calc" // Windows上的计算器
  // 在这里添加更多的软件和它们的路径
};

openSoftwares(softwareMap);
  • 如果再进行前端页面的可视化输入页面,我们就真的实现了一个小工具了,就像下面一样,这其实很简单,但很有成就感
  • execSync用的还是比较多的,是个需要着重记忆的API

spawn

  • spawnSync 的这个同步方法用得比较少
  • spawn 没有字节上限的限制,因为返回的是一个流(buffer),实时返回

[!TIP]

netstat(network statistics)是一个命令行工具,用于显示网络连接、路由表、接口统计等网络相关信息、帮助用户监控和问题定位网络连接

在这种情况下,exec命令会一直卡在这个输出网络连接信息,直到输出完才会停止。而spawn则是有当下有多少就返回多少(实时返回,返回流形态)

  • 通过上图用execSync进行输出,能看到内容其实一直没有加载出来,所以execSync这个命令就不适合执行这种实时获取类型的任务
js 复制代码
const { execSync, spawn, fork} = require('child_process');

const {stdout} = spawn('netstat')

stdout.on('data',(msg)=>{
  console.log(msg.toString());
})

stdout.on('close',(msg)=>{
  console.log("结束了");
})
  • 而通过spawn 就能够实时获取该部分信息,同时需要注意的是,我们是从spawn中解构出stdout这个方法,这个单词的意思在前面也有说过,是标准流输出的意思,所以输出的buffer流要变成我们能看懂的形式,就要记得加上**toString()**进行转化
  • spawn自带的就有close事件的,在结束的时候会抛出close事件监听,并返回状态码,通过状态码可以知道子进程是否顺利执行。exec是没有close事件的,只能通过返回的buffer去识别完成状态,识别起来较为麻烦

spawn和exec不同的传参方式

  • spawn 除了第一个参数是直接传入要执行的命令,还有第二个参数,第二参数是一个数组,用来执行我们额外的命令。这些参数是明确地分开传递的,有助于防止某些类型的注入攻击,并且让参数处理更加清晰
js 复制代码
const { spawn } = require('child_process');

//'ls' 是命令,['-lh', '/usr'] 是一个参数数组,它们将被传递给新的进程
const child = spawn('ls', ['-lh', '/usr']); // 命令: ls, 参数: ['-lh', '/usr']
  • exec 的传参方式与 spawn 不同,它通过单个字符串接收命令和参数。也就是前面说的手动将命令和参数拼接成一个字符串,在命令后面跟着继续写字符串
    • exec输出方式在前面已经有详细讲解,这里不在扩展

spawn和exec相同配置项

  • 里面都有一个options,该对象是用来配置子进程的各种行为,总结在下面,有需求直接用就行了
配置项 类型 默认值 描述
cwd string undefined 子进程的当前工作目录。如果未指定,则继承父进程的当前目录。
env Object process.env 为子进程指定环境变量。默认为父进程的环境变量。
shell booleanstring false 如果为 true,则在一个shell中运行命令。可以指定要使用的 shell。
stdio stringArray 'pipe' 配置子进程的标准输入、标准输出和标准错误流。可以是 'pipe'(管道到父进程)、'ignore'(忽略输出)、'inherit'(继承自父进程)或数组(指定为每个流)。
timeout number 0 子进程的最长执行时间,单位为毫秒。超时后将会杀死子进程。0 表示无超时限制。
uid number undefined 设置子进程的用户标识(UID),仅在 POSIX 平台上有效。
gid number undefined 设置子进程的组标识(GID),仅在 POSIX 平台上有效。
detached boolean false 如果为 true,则使子进程在父进程退出后继续运行。
windowsHide boolean false 在 Windows 平台上,如果设置为 true,可以隐藏子进程的窗口。

execFile

  • execFile 适合执行可执行文件,例如执行一个node脚本,或者shell文件(后缀.sh的文件,这是Mac的方式),windows可以编写cmd脚本,posix,可以编写sh脚本
  • 这个方法也有对应的Sync同步方法,但还是异步方法使用的更多一点

execFile用法

js 复制代码
const { execFile } = require('child_process');

execFile(file, [args], [options], callback);

//参数对应的意思
file:要运行的可执行文件的路径。
args(可选):一个数组,包含所有传递给可执行文件的命令行参数。
options(可选):一个可选的对象,用来配置执行环境及其它选项。
callback(可选):一个函数,当子进程停止时被调用,该函数接收参数 (error, stdout, stderr)。

参数详解

  • file :这是我们希望执行的可执行文件。例如,在 Windows 系统上可能是一个 .exe 文件,在 Unix-like 系统上可能是任何可执行的二进制文件。

  • args :一个字符串数组,包含将传递给执行文件的参数。例如 ['-l', '-a']

  • options

    :该参数有多种选项可以设置,例如:

    • cwd:子进程的当前工作目录。
    • env:环境变量键值对。
    • encoding:输出的编码(默认为 'buffer')。
    • timeout:超时时间,超时后将会杀死子进程。
    • maxBuffer:stdout 或 stderr 上允许的最大数据量(以字节为单位),默认为 1024 * 1024
    • killSignal:用于杀死子进程的信号(默认为 'SIGTERM')。
    • uidgid:设置进程的用户标识和群组标识。
  • callback :一个回调函数,它有三个参数:errorstdoutstderrerror 是如果进程遇到错误或退出码非 0 则会被设置。

execFile案例

  • 使用命令mkdir创建一个文件test.js,cd进入目录,echo输出一段内容在控制台,最后执行
shell 复制代码
# 输出"开始",标志脚本执行的开始
echo '开始'

# 创建一个名为test的新目录
mkdir test 

# 改变当前工作目录到新创建的test目录
cd ./test

# 创建一个名为test.js的新js文件,并写入一行代码(通过>符号实现写入):console.log("xiaoyu666")
echo console.log("xiaoyu666") >test.js

# 控制台输出"结束",标志脚本执行的结束
echo '结束'

# 使用Node.js执行test.js文件,输出其结果
node test.js
  • 然后我们在使用execFile来执行这段cmd
js 复制代码
const path = require('path')
const {execFile} = require('child_process');

//process.cwd作用和__dirname差不多,用哪个都是一样的,前面章节有说
execFile(path.resolve(process.cwd(),'./bat.cmd'),null,(err,stdout)=>{
  console.log(stdout.toString())
})

底层实现顺序

  • exec -> execFile -> spawn(左边基于右边实现)

fork

child_process 模块的 fork 方法是一个特别的功能,它用于在新的 Node.js 进程中运行模块文件,是 spawn 函数的一个变体。fork 主要用于创建新的 Node.js 进程,并允许父子进程之间有一个通信通道,可以通过这个通道相互发送消息

主要特性

  • 创建 Node.js 子进程fork 直接运行 Node.js 模块(即 .js 文件),不需要像 spawnexec 那样指定 Node.js 可执行文件。
  • IPC 通信fork 启动的进程之间会自动建立一个 IPC(进程间通信)通道,允许父进程和子进程通过消息传递进行通信。
  • 隔离的 V8 实例 :每个通过 fork 创建的进程都有自己的 V8 实例和独立的事件循环。

fork用法

js 复制代码
const { fork } = require('child_process');

const child = fork(modulePath, [args], [options]);

//modulePath:要在子进程中运行的模块文件的路径。
//args(可选):一个字符串数组,包含传递给模块的参数。
//options(可选):一个配置对象,可以设置各种选项,如环境变量、工作目录等。

参数详解

  • modulePath:一个字符串,指向要运行的 Node.js 模块。

  • args:运行模块时要传递的参数数组。

  • options

    :可以包括如下几个选项:

    • cwd:子进程的当前工作目录。
    • env:环境变量键值对。
    • execPath:用于创建子进程的可执行文件路径。
    • execArgv:传递给可执行文件的字符串参数数组(通常用于传递 V8 选项)。
    • silent:如果设置为 true,子进程中的 stdinstdoutstderr 会被重定向到父进程。

事件和通信

  • 父进程和子进程可以通过 send() 方法和 message 事件来发送和接收消息。对此,我们可以做一个案例来实现一个
    • 但需要注意的是,只能接受js模块
    • 在需要多核处理能力的应用中,可以使用 fork 来利用多核CPU。
    • 当需要子进程处理一些耗时的计算时,使用 fork 可以避免阻塞主事件循环。
    • 在进行大量的异步 I/O 操作时,可以通过多个子进程来分散处理压力。
js 复制代码
//index.js文件(主线程)
const { fork } = require("child_process")

//和需要沟通的js文件建立联系
const indexProcess = fork('./xiaoyu01.js')
//发送消息
indexProcess.send('我是index')
js 复制代码
//xiaoyu01.js文件(子线程)
process.on('message', (msg) => {
  console.log("我是xiaoyu01,接收到了消息:", msg);
})

fork底层结构图

  • 下图是Node.js 中子进程如何通过 IPC(进程间通信)进行通信的基本结构
  • 主进程子进程:图表的顶部和底部分别表示主进程和子进程,它们需要相互通信。
  • IPC:位于图表中心,代表进程间通信机制,它是主进程与子进程或不同子进程之间交换数据的方式。
  • libuv:位于 IPC 下方,它是一个跨平台的异步I/O库,Node.js 用它来抽象不同操作系统之间的差异,包括提供 IPC 的实现。
  • Windows 和 POSIX :图表下方的两个分支代表了不同的操作系统平台。libuv 针对每个平台使用不同的技术来实现 IPC。
    • 在 Windows 平台上,IPC 是通过"命名管道"(named pipe)实现的。
    • 在 POSIX 兼容的系统上(如 Linux 和 macOS),IPC 是通过"Unix 域套接字"(unix domain socket)实现的。
相关推荐
一颗花生米。15 分钟前
深入理解JavaScript 的原型继承
java·开发语言·javascript·原型模式
学习使我快乐0119 分钟前
JS进阶 3——深入面向对象、原型
开发语言·前端·javascript
bobostudio199520 分钟前
TypeScript 设计模式之【策略模式】
前端·javascript·设计模式·typescript·策略模式
勿语&1 小时前
Element-UI Plus 暗黑主题切换及自定义主题色
开发语言·javascript·ui
黄尚圈圈1 小时前
Vue 中引入 ECharts 的详细步骤与示例
前端·vue.js·echarts
浮华似水2 小时前
简洁之道 - React Hook Form
前端
正小安4 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch6 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光6 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   6 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发