Javascript与Python之间的进程间通信


关于如何实现 Javascript/Node.js <--> Python IPC 的快速小教程。

为了促进 Javascript/Node.js Web 服务器和 Python AI 子进程之间的通信,可以使用标准输入 (stdin) 和标准输出 (stdout) 流建立进程间通信 (IPC)。

Javascript
虽然 Node.js 有一个称为 IPC 的 fanceh 消息传递系统,但在与不运行 Javascript/Node.js 的进程通信时,它实际上不起作用。为此,解决方案是使用子进程的标准输入 (stdin) 和标准输出 (stdout) 进行通信:

1、从 Node.js 生成 Python 子进程
让我们从编写父 Node.js 脚本开始。首先,我们需要生成 Python 子进程,因此我们这样做:

import { spawn } from 'child_process';
const python = spawn("path/to/child.py", {
    stdio: [
"pipe", "pipe", "inherit" ]
});

这将生成 Python 脚本作为子进程,并将 stdin 和 stdout 设置为“管道”模式以便进行交互,并将 stderr 设置为“继承”模式以共享父进程的错误流。

我们将 stdin 和 stdout 设置为pipe模式(这让我们可以与流交互),并将标准错误 (stderr) 设置为inherit模式,这允许它共享父进程的 stderr。这样,子进程中的错误就会向上传播,并最终出现在父进程将其输出发送到的同一个日志文件中。

2、发送数据到 Python 子进程
如果您需要向 Python 子进程发送一些数据,则必须等到它初始化后才能发送一些数据:

python.on(<code>spawn</code>, () => {
    console.log(<code>[node:data:out] Sending initial data packet</code>);
    python.stdin.write(<code>start\n</code>);
});

子进程初始化后,就可以通过python.stdin.write() 向其发送数据。

在调用 child_process.spawn 时设置一个环境变量,即在上述选项对象中设置 env: { key: "value" }。

3、读取 Python 子进程的响应
接下来,我们需要读取 Python 脚本的响应。让我们开始吧:

import nexline from 'nexline'; // Put this import at the top of the file

const reader = nexline({
    input: python.stdout,
})

for await(const line of reader) {
    console.log(<code>[node:data:in] ${line}</code>)
}

nexline 软件包用于通过 python.stdout 从 Python 子进程中高效地逐行读取响应。

最简单的方法是监听 python.stdout 上的数据事件,但这并不能保证到达的每个数据块实际上都是一行数据,因为进程间的数据不像在终端显示内容时那样采用行缓冲。

要解决这个问题,我建议使用我最喜欢的 npm 软件包之一:nexline。不管你信不信,以最小的缓冲来高效处理这个问题比听起来要困难得多,所以使用一个软件包来帮你解决这个问题会更容易。

通过一个漂亮的 for await...of 循环,我们可以高效地读取 Python 子进程的响应。

如果您真的要这样做,我建议将其封装在 EventEmitter(Node.js)/EventTarget(WHAT WG 浏览器规范,Node.js 也有)中。

Python 子进程
Python 脚本从 sys.stdin 读取输入,并将响应写入 sys.stdout。调用 sys.stdout.flush() 对于确保立即发送响应至关重要。

import sys

sys.stderr.write(f"[python] hai\n")
sys.stderr.flush()

count = 0
for line in sys.stdin:
    sys.stdout.write(f
"boop" + str(count) + "\n")
    sys.stdout.flush()
    count += 1

简单!我们只需遍历 sys.stdin,即可从父 Node.js 进程中读取数据。

我们可以写入 sys.stdout,将数据发送回父进程,但重要的是要调用 sys.stdout.flush()!Node.js 没有类似的命令,因为它很聪明,但在 Python 中,除非调用 .flush() 强制它发送响应,否则它可能直到不知道什么时候(如果有的话)才会发送响应。

结论
这种方法可在 Javascript 和 Python 之间实现高效的进程间通信,使应用程序能够充分利用两种语言的优势。

我们在这里处理的是纯文本消息,建议使用 JSON

  • JSON.stringify()
  • JSON.parse() (Javascript)
  • json.dumps() 
  • json.loads (Python)

 来序列化/反序列化消息,以确保稳健性。

JSON 默认不包含换行符,并且会将任何存在的字符转义为 \n,因此在这种情况下应该是安全的。

完整代码:
index.mjs:

#!/usr/bin/env node
"use strict";

import { spawn } from 'child_process';
import nexline from 'nexline';

///
// Spawn subprocess
///
const python = spawn(
"/tmp/x/child.py", {
    env: {  
// Erases the parent process' environment variables
       
"TEST": "value"
    },
    stdio: [
"pipe", "pipe", "inherit" ]
});

python.on(<code>spawn</code>, () => {
    console.log(<code>[node:data:out] start</code>);
    python.stdin.write(<code>start\n</code>);
});

child.py:

#!/usr/bin/env python3
import sys

sys.stderr.write(f"[python] hai\n")
sys.stderr.flush()

count = 0
for line in sys.stdin:
    # sys.stderr.write(f
"[python:data:in] {line}\n")
    # sys.stderr.flush()

    sys.stdout.write(f
"boop" + str(count) + "\n")
    sys.stdout.flush()
    count += 1