NodeJS 跨语言子进程持续通讯

来源:互联网 发布:单片机uart是什么意思 编辑:程序博客网 时间:2024/05/01 20:41

有一个应用场景:用C/C++/Java等编译型语言做NodeJS服务器背后高性能计算的组件,那么应该如何实现?

比较好的方法是用Socket通讯,但这就需要双方都要进行套接字编程。

NodeJS倒是好说,但其他语言的套接字编程就不一定简单了。

所以本文中以NodeJS/C++为例,用管道来进行通讯,就像标准输入输出一样简单。

本文仅作演示,默认所有文件都在同一个目录。


高性能运算端

先以一个可以持续 输入/输出 循环的简单C++程序为例,使用阻塞输入可以很好地解决问题。

#include <iostream>using namespace std;int main() {    int n;    while(cin >> n) {        cout << n + 1 << endl;    }    return 0;}

使用 scanf, printf, cin, cout 可以凭自己喜好,经测试都是可以的。
是否使用 endl, flush, \n 都是可以的。

然后编译成 a(Windows:a.exe;Unix:a.out,不用在意这些细节)

NodeJS端

创建一个 i.js 的文件,并写入:

var path = require('path');var child = require('child_process').spawn(path.join(__dirname, 'a'));child.stdout.on('data', function (data) {    console.log('stdout: ' + data);});child.stderr.on('data', function (data) {    console.log('stderr: ' + data);});child.stdin.write('1\n'); // Attention!

注意child.stdin.write的格式要与C++程序设计地一致。
本例中如果不加入\n,C++中的输入不会结束,也就不会有输出,更不会触发child.stdoutdata事件。

调用 child.stdin.end() 之后,C++程序会接收到 EOF 输入,正常的程序就会退出。所以在持续运行的程序中不要使用child.stdin.end() 方法。

使用

$ node i.js

即可看到

stdout: 2

跨模块组织

显然我们需要在多个NodeJS模块使用同一个C++进程的。

使用同一个进程的好处是,C++程序的生命周期变长,可持久化的数据结构得以持续存在。

child.js

var path = require('path');var spawn = require('child_process').spawn;var child = spawn(path.join(__dirname, 'a')); module.exports = child;

Middleware 模式

使用 child.stdout.on('data', (data) => { ... }) 来设计 Middleware 模式,但数据不能通过中间件内部修改直接传递(因为参数 data 是 string 类型,不使用引用赋值),要另外封装一个变量来使用中间件。

Flash 模式

对于某个特定的输入,我们通常要用一个逻辑处理C++返回的输出。
caller1.js

var child = require('./child');function caller1(input) {    child.stdout.once('data', function (data) {        console.log('caller1 result: ' + data);    });    child.stdin.write(input);}module.exports = caller1;

可以类似地构造一个 caller2 ,分别调用 caller1 与 caller2 的时候,会只使用对应的回调函数处理。

有个坑点是:如果在过短的时间内调用 caller1 与 caller2 时,caller1, caller2接收到数据是两者的和。
因为他们的监听器在数据回收之前就被加入data事件的监听器列表里了。
解决方案是延迟后一个caller函数的执行时间,当前一个caller的once监听器执行之后再回调后面这个caller。
或者简单一些直接估计data 的反应时间(这不保险)

消息队列

Message Queue

为了解决之前提到的问题,我们不直接将消息写入child.stdin,也不直接将监听器加到child.stdout 中。

sender.js

var child = require('./child');var messages = []; // MQ Container// fired when child responsechild.stdout.on('data', function (data) {    // get handler and dequeue    var action = messages.shift();    // call handler if exists    action && action.handler && action.handler(data);    // send next message to child if exists    if (messages.length > 0) {        child.stdin.write(messages[0].msg);    }})// enqueue messagefunction send(msg, callbackFn) {    // if no message in queue, send the first message to child    if (messages.length === 0) {        child.stdin.write(msg);    }    // enqueue the message and handler    messages.push({        msg: msg,        handler: callbackFn    });}module.exports = send;

检查逻辑,你必须使得每次send都能得到C++程序的响应,不然就一直不会触发事件,导致MQ停滞,但NodeJS不会阻塞。

试用一下:
main.js

var sender = require('./sender');sender('1\n', (data) => console.log('func 1: ' + data));sender('3\n', (data) => console.log('func 2: ' + data));console.log('MQ Enqueue Completed');

运行结果:

MQ Enqueue Completedfunc 1: 2func 2: 4

很好,基本没有问题,关于工程化的封装以后再说。


参考资料

Child Process | node.js API docs v6.x

0 0