node中spawn启动exe程序(exec所在问题)

目的: 开发electron桌面应用,服务端(c++开发的exe程序)随着打包客户端一并打包。想要在用户打开客户端的时候,同时将桌面应用的服务端启动,并使服务端exe程序隐藏运行。并且服务端程序要随着客户端的关闭而关闭。

做法: 启动客户端的同时,使用node来启动服务端exe程序。在node.js中,需要使用child_process模块来启动一个子进程(服务端exe程序),阅读文档发现可以使用child_process中的execspawn来实现打开服务端exe程序

execspawn的区别?

官方文档:child_process 子进程 | Node.js v20 文档 (nodejs.cn)

  1. 运行命令参数:

    1. exec 参数为要运行的command命令,参数以空格分隔
    2. spawn 第一个参数为要运行command命令,参数使用字符串数组(string[])的形式传入第二个参数当中
  2. 配置项:

    1. exec的配置项使用对象的形式传入第二个参数,部分配置:

      配置项 类型 原理
      timeout <number> 运行超时时间。默认值:0
      maxBuffer <number> 标准输出或标准错误上允许的最大数据量(单位:字节)。如果超过,则子进程将终止并截断任何输出,默认1024*1024
      windowsHide <number> 隐藏通常在 Windows 系统上创建的子进程控制台窗口。默认值:false
    2. spawn的配置项使用对象的形式传入第三个参数,部分配置:

      配置项 类型 原理
      detached <boolean> 准备子进程独立于其父进程允许
      windowsHide <boolean> 隐藏通常在 Windows 系统上创建的子进程控制台窗口。默认值:false
      signal <AbortSignal> 允许使用 AbortSignal 中止子进程

启动服务端exe程序:

针对打开客户端的时候运行服务端,对execspawn的第一个参数传入正确的服务端路径就可以成功打开exe服务端程序

javascript 复制代码
const { spawn } = require('child_process')
const path = require('path')
// 服务端路径
const CPLUSPLUS_EXECUTABLE_PATH = path.join(__dirname, '..', 'grpc_server', 'FileTransGRPCServer.exe')
let childProcess = spawn(CPLUSPLUS_EXECUTABLE_PATH)

此时就可以成功的启动指定路径的FileTransGRPCServer.exe程序,我已将该服务文件,放入electron客户端开发目录下。

配置子进程:

  1. exe程序在Windows上隐藏运行,使用到了spawn的配置项windowsHide: true传入spawn的第三个配置项中
  2. detached设为true让子进程独立于父进程运行
  3. 第二个参数为空,则使用[]占位
javascript 复制代码
const { spawn } = require('child_process')
const path = require('path')
// 服务端路径
const CPLUSPLUS_EXECUTABLE_PATH = path.join(__dirname, '..', 'grpc_server', 'FileTransGRPCServer.exe')
// 配置项
const EXECUTION_OPTIONS = {
  windowsHide: true,
  detached: true, // 让子进程独立于父进程运行
}
let childProcess = spawn(CPLUSPLUS_EXECUTABLE_PATH, [], EXECUTION_OPTIONS)

此时可以在启动客户端时,在 Windows系统上隐藏并且独立运行exe服务端程序。

关闭客户端同时关闭服务端:

使用spawn创建的子进程,会返回一个ChildProcess,使用变量接收。

关闭子进程的三种方法:

  1. spawn创建子进程中配置signal来允许使用 AbortSignal中止子进程。

    1. 需要安装npm i abort-controller 使用

    javascript 复制代码
    // 1. 导入
    const { AbortController } = require('abort-controller')
    // 2. 创建实例化对象
    const controller = new AbortController();
    // 3. 解构 spawn 使用的配置项  signal
    const { signal } = controller;
    // 4. 配置 signal
    let childProcess = spawn(CPLUSPLUS_EXECUTABLE_PATH, [], { signal })
    // 5. 此时停止子进程
    controller.abort();
  2. 使用命令关闭进程:

    1. 指定pid关闭进程,taskkill /pid 10802 -f,使用childProcess.pid获取进程pid(exec创建的进程除外后续讲到)
    2. 指定进程名称关闭进程,taskkill /im 进程名称 -f
  3. 使用ChildProcess.kill()来关闭子进程


上述为使用spawn创建的子进程并且关闭子进程的操作。

在这之前,我使用的exec来去创建子进程,并且尝试关闭启动的子进程,发现无法关闭,发生错误。

关闭exec创建子进程的问题:

  1. 问题: 在使用上述方法来关闭进程时,发现报错无法关掉启动的进程。
  2. 原因: exec创建子进程返回的实例,不是真正启动的子进程,而是对子进程的套壳进程。获取的pid也为套壳进程的pid,而非子进程的pid。所以无法使用上述方法1,方法2.1,方法3来关闭子进程。

最终使用child_process下的spawn来创建子进程实现子进程(服务端exe程序)的启动和关闭!


到此,打包之后的客户端是无法访问到服务端来去启动服务端。因为在打包之后,electron客户端中的服务端被一并打包,需要在打包后的目录下暴露服务端程序,才可在打包后,启动客户端来正确的启动服务端。

暴露服务端:vue.config.js

javascript 复制代码
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  pluginOptions: {
    electronBuilder: {
      // *** 省略其他配置 ***
      builderOptions: {
        extraResources: [
          { "from": "grpc_server", "to": "./grpc_server" } // 打包后暴露服务端程序
        ]
      }
    }
  }
})

这样就成功了!


完整代码

附上启动和关闭服务端exe程序文件:

javascript 复制代码
const { spawn } = require('child_process');
const path = require('path');
const CPLUSPLUS_EXECUTABLE_PATH = path.join(__dirname, '..', 'grpc_server', 'FileTransGRPCServer.exe');
// 配置项
const EXECUTION_OPTIONS = {
  windowsHide: true,
  detached: true, // 让子进程独立于父进程运行
};

let childProcess;
// 启动服务端程序
function startCPlusPlusService() {
  childProcess = spawn(CPLUSPLUS_EXECUTABLE_PATH, [], EXECUTION_OPTIONS, (error, stdout, stderr) => {
    if (error) {
      console.error(`Error starting C++ service: ${error}`);
      return;
    }
    console.log(`C++ service stdout: ${stdout}`);
    console.error(`C++ service stderr: ${stderr}`);
  });
}
// 关闭服务端程序
function stopCPlusPlusService() {
  console.log(childProcess.pid)
  /**
   * 注意:想要关闭启动的进程,必须使用 spawn 来创建子进程,
   *      使用 exec 创建子进程无法使用 kill 方法关闭子进程:
   *      因为 exec 创建子进程的实例实际上不是子进程,而是套壳进程,
   *      无法获取子进程的 pid,而是在子进程的外部套壳,获取的是套壳进程的 pid
   */
  /**
   * @param 1. childProcess.exitCode 标识子进程的退出代码。如果子进程仍在运行,则返回 null
   * @param 2. childProcess.kill() 通过 pid 来关闭子进程
   */
  if (childProcess.exitCode === null) {
    childProcess.kill();
    console.log('Attempting to gracefully shut down the C++ service...');
  } else {
    console.log('C++ service has already exited.');
  }
}
module.exports = { startCPlusPlusService, stopCPlusPlusService };

electorn桌面应用的客户端主进程中,

  1. 监听打开桌面应用的方法中调用该文件中的startCPlusPlusService方法来一并启动服务
  2. 监听桌面应用客户端关闭调用stopCPlusPlusService一并关闭服务

实现功能!

相关推荐
chxii12 小时前
前端与Node.js
前端·node.js
格鸰爱童话14 小时前
node.js学习(一)
node.js
亮子AI2 天前
【NestJS】在 nest.js 项目中,如何使用 Postgresql 来做缓存?
开发语言·缓存·node.js·nest.js
EndingCoder2 天前
Node.js 数据查询优化技巧
服务器·javascript·数据库·node.js·数据查询优化
芒果Cake2 天前
【Node.js】Node.js 模块系统
javascript·node.js
_光光2 天前
大文件上传服务实现(后端篇)
后端·node.js·express
一枚前端小能手2 天前
🚀 Node.js 25重磅发布!快来看看吧
前端·javascript·node.js
impossible19947272 天前
如何开发一个自己的包并发布到npm
node.js·1024程序员节
岁月宁静2 天前
用 Node.js 封装豆包语音识别AI模型接口:双向实时流式传输音频和文本
前端·人工智能·node.js
徐sir(徐慧阳)2 天前
搭建属于自己的网站HEXO静态页(二)发布网站到gihub
服务器·node.js·github·hexo