如何与 spawn() 子进程通信?

6

我希望使用spawn()创建一个子进程,并且让它拥有自己的终端。

parent.js:

const spawn = require('child_process').spawn;

console.log('started parent process...'); //this should be printed in the parent terminal

const child = spawn('start node', [`child.js`], {
    cwd:__dirname,
    shell: true,
    stdio: [null, null, null, 'pipe']
});

const Name = 'general kenobi';

child.stdio[3].write(Name);

child.stdio[3].on('data', (data) => {
    console.log('data=>', data.toString());
    child.kill();
});

child.js:

console.log('started child process...');

(async()=>{
    await new Promise(r=>setTimeout(r,3000));

    try{
        let net = require('net');
        let pipe = new net.Socket({ fd: 3 });

        pipe.on('data',(data)=>{
            pipe.write(`hello there ${data}`);
        });
    }catch(err){console.log(err)}
    await new Promise(r => setTimeout(r, 3000));
})();

预期的数据应该是 data=> hello there general kenobi,但在子终端中出现了此错误。
 throw new ERR_INVALID_FD_TYPE(type);
  ^

TypeError [ERR_INVALID_FD_TYPE]: Unsupported fd type: UNKNOWN
    at new NodeError (node:internal/errors:371:5)
    at createHandle (node:net:152:9)
    at new Socket (node:net:340:20)
    at Object.<anonymous> (C:\...\parent.js:4:12)
    at Module._compile (node:internal/modules/cjs/loader:1101:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:17:47 {
  code: 'ERR_INVALID_FD_TYPE'
}

我没有找到很多参考资料,因此我使用了这个视频作为指南。

(请在回答时尽量避免使用第三方包)


1
啊,是的,你想忽略子进程中的 console.log 吗? - Bergi
1
谢谢,但我担心那个问题并不相关,异常是在那行代码执行之前就被抛出了 :-) - Bergi
我认为你没有正确地传递选项。你漏了 [],改成 let pipe = new net.Socket([{ fd: 3 }]); - Snake_py
@Snake_py [net.Socket]不接受数组作为第一个参数,而是需要一个对象。 (参考链接:https://nodejs.org/api/net.html#class-netsocket) - Bergi
1
我认为您尝试做的事情不会奏效。您正在生成一个名为“start”的进程,它是与Node本身分离的命令。它是否传递标准输入/输出和其他文件描述符?看起来并非如此。您可能无法通过这种方式实现双向通信 - 您可能需要寻找另一种方式,例如在本地主机上使用网络套接字。 - Robert Kawecki
显示剩余12条评论
2个回答

2

请在parent.js文件中删除shell: true并将命令更改为“node”。

const child = spawn('node', [`child.js`], {
cwd:__dirname,
stdio: [null, null, null, 'pipe']
});

不要直接使用 spawn,而是在添加 shell: true 的同时,spawn 将使用系统的 shell来运行该命令。一般来说,我建议使用纯粹的 spawn 而不使用 shell。这样可以减少直接操作 shell 和管道数据的风险。


这怎么能打开一个新的终端?你读了评论吗?:) - user15573857
@cakelover 通常 child_processs(spawn,...)会在您的系统中打开新的 shell。我认为在 cluster 中使用消息传递应该比在 spawn 上进行数据管道传输更有帮助。集群消息传递使用标准 IPC 消息传递,非常容易使用。 - Hossein Najafi

0
首先,正如其他评论中提到的那样,“start node”应该只是“node”。 我比较了您链接的视频,并发现如果将“node”(spawn参数)更改为“process.execPath”,它就会神奇地工作!

parent.js:

const spawn = require('child_process').spawn;

console.log('started parent process...'); //this should be printed in the parent terminal

const child = spawn(process.execPath, [`child.js`], {
    //cwd:__dirname,
    //shell: true,
    stdio: [null, null, null, 'pipe']
});

const Name = 'general kenobi';

child.stdio[3].write(Name);

child.stdio[3].on('data', (data) => {
    console.log('data=>', data.toString());
    child.kill();
});

目前我不确定为什么会发生这种情况?可能只是一个错误吧!现在,我想也许只有修复才能帮助你。

如果您记录process.execPath并且如docs中所述,它返回到节点的绝对路径并解析所有符号链接。这可能是使用spawn的问题。

此外,如果您在选项中使用shell:true,则子进程不会被杀死。我不知道这是否符合您的预期行为。

更新:

我的Ubuntu上的问题是因为使用来自Snap软件包的节点,这可能是Snap尝试隔离进程的错误。

我在snapcraft中开了一个主题,这可能是关于文件描述符的已知错误。您可以在链接中阅读有关解决方法和错误的更多信息。

更新2:

另一种实现使用第二个命令行生成消息的方法。

parent.js:

const spawn = require('child_process').spawn;

console.log('started parent process...');

const child = spawn('node', ['child.js'], {
  cwd: __dirname,
  stdio: [null, null, null, 'ipc'],
});

const Name = 'general kenobi';

child.send(Name);
child.on('message', (data) => {
  console.log('data=>', data);
  // child.kill();
});

child.js:

const spawn = require('child_process').spawn;

console.log('started child process...');

process.on('message', (data) => {
  process.send(`hello there ${data}`);
  const cmd = spawn('start', ['cmd.exe', '/k', `echo hello there ${data}`], {
    cwd: __dirname,
    shell: true,
  });
});

我真的不明白这个回答如何解决问题,我的最终目标是在新终端中启动一个子进程同时仍然能够与父进程通信,而start命令无法实现这一点,但我想在问题中表达我需要的东西。 - user15573857
@cakelover 你是在使用snap包安装的Node吗?如果是,你可以从其他来源安装Node,或者使用我在链接中提到的解决方法。如果你目前正在使用snaps,那么你无法做到你想要的,因为这是一个错误! - Amir
我没有使用Snap软件包,而且我在Windows系统上。 - user15573857
@cakelover 使用 process.execPath 没有解决问题吗? - Amir
这个问题的整个意图是让子进程在一个新的终端中打开。 - user15573857
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接