如何在Node.js中实时读取spawn子进程的输出数据
解决Node.js中child_process实时读取nfc-poll输出的问题
这个问题我之前折腾过好一会儿!核心原因是nfc-poll这个工具在输出到管道(比如Node的child_process创建的子进程stdout)时,会启用块缓冲模式——它不会实时输出每一行内容,而是攒够一块数据或者进程退出时才一次性把缓冲区的内容吐出来,这就是为什么你只有拔卡(进程结束)时才收到data事件。
下面给你两个可行的解决思路:
方案1:强制nfc-poll使用行缓冲输出
我们可以借助stdbuf命令(大部分Linux/macOS系统都自带),强制nfc-poll的stdout使用行缓冲模式,这样每生成一行输出就会立刻发送给Node进程。
修改你的start_process方法:
const cp = require('child_process'); const events = require('events'); function NFCReader() { this.reader = new events.EventEmitter(); this.start_process(); } NFCReader.prototype = { start_process: function () { // 用stdbuf强制行缓冲输出nfc-poll的内容 this._process = cp.spawn('stdbuf', ['-oL', 'nfc-poll'], {}); this._process.on('close', this.restart_process.bind(this)); this._process.stdout.on('data', this.handle_data.bind(this)); this._process.stderr.on('data', this.handle_error.bind(this)); }, handle_data: function (data) { var _data = data.toString(); var uid_lines = _data .split('\n') .filter(function (line) {return line.search('UID') >= 0}); if (uid_lines.length !== 0) { // 可能一次data事件包含多行,遍历处理 uid_lines.forEach(line => { var card_id = line .trim() .split(':')[1].trim() .replace(/[ ]+/g, ':'); this.reader.emit('card', card_id); }); } }, // 记得补充restart_process和handle_error方法 restart_process: function() { console.log('进程重启'); this.start_process(); }, handle_error: function(err) { this.reader.emit('error', err.toString()); } };
这里stdbuf -oL的作用是:-o指定stdout,L代表行缓冲模式。这样nfc-poll每输出一行,就会立刻把内容推送给Node,你的handle_data就能实时触发了。
方案2:用readline模块逐行解析流
如果你的环境没有stdbuf,可以用Node自带的readline模块来逐行读取stdout流,它会帮你处理缓冲,实时捕获每一行输出。
修改后的代码示例:
const cp = require('child_process'); const events = require('events'); const readline = require('readline'); function NFCReader() { this.reader = new events.EventEmitter(); this.start_process(); } NFCReader.prototype = { start_process: function () { this._process = cp.spawn('nfc-poll', [], {}); // 创建readline接口,绑定到子进程的stdout const rl = readline.createInterface({ input: this._process.stdout, crlfDelay: Infinity }); // 监听每一行输出 rl.on('line', (line) => { if (line.includes('UID')) { var card_id = line .trim() .split(':')[1].trim() .replace(/[ ]+/g, ':'); this.reader.emit('card', card_id); } }); this._process.on('close', () => { rl.close(); this.restart_process.bind(this)(); }); this._process.stderr.on('data', this.handle_error.bind(this)); }, // 补充其他方法... restart_process: function() { console.log('进程重启'); this.start_process(); }, handle_error: function(err) { this.reader.emit('error', err.toString()); } };
readline会自动处理流的缓冲问题,只要子进程输出一行,line事件就会触发,这样你就能在放卡后立刻拿到UID了。
额外提示
你原来代码里的this._process.stdout.readableFlowing = true其实是多余的——当你绑定data事件到stdout流时,Node会自动把流切换到流动模式,不需要手动设置。
内容的提问来源于stack exchange,提问作者Loïc Faure-Lacroix
相关产品推荐
相关产品推荐

