Node进程向PHP脚本传递Stdin时挂起问题排查
问题分析与解决方案
你的问题核心在于Node进程没有关闭PHP脚本的stdin管道,导致PHP一直认为可能还有输入会过来,从而无法退出while循环。下面具体解释原因和解决办法:
为什么会卡住?
当你在Node中向PHP的stdin写入数据后,如果不主动关闭stdin,PHP的stdin流会一直处于"打开"状态:
- 在阻塞模式下(默认):
fgets()会一直等待新的输入,直到收到EOF(流结束信号),所以循环会无限挂起。 - 在非阻塞模式下:
fgets()在没有数据时会立即返回false,但因为流没关闭,你的循环条件while(($line = fgets(...)) !== false)理论上应该终止,但实际中可能因为PHP的流状态处理、警告信息干扰,或者你误判了循环状态,导致脚本看起来一直卡在循环里。
而且你添加的换行符\n只是分隔输入行的标记,并不是EOF信号,PHP不会把换行当成输入结束的标志。
解决办法(两种可选)
方法1:Node端写完数据后关闭stdin(推荐)
这是最直接的方式,告诉PHP输入已经全部发送完毕,PHP会收到EOF信号,此时不管是阻塞还是非阻塞模式,fgets()都会返回false,循环正常终止。
修改你的run.js,在写完数据后调用stdin.end():
const { spawn } = require('child_process'); const php = spawn('php', ['test-stdin.php'], {}); php.stdin.write('some input'); php.stdin.write("\n"); // 关键:关闭stdin,发送EOF信号给PHP php.stdin.end(); php.stdout.on('data', (data) => { console.log(`stdout: ${data}`); }); php.on('close', (code) => { console.log(`child process exited with code ${code}`); });
同时PHP脚本可以恢复默认的阻塞模式(去掉非阻塞设置),这样代码更简洁:
$input_stream = fopen("php://stdin","r"); $text = ""; while(($line = fgets($input_stream, 4096)) !== false) { var_dump('Read from fgets: ', $line); $text .= $line; } fclose($input_stream); echo('Total input from stdin: ' . $text);
方法2:PHP端处理非阻塞模式的循环退出
如果你必须使用非阻塞模式,可以修改循环条件,同时判断是否读取到有效数据,并且添加短暂延迟避免CPU空转:
$input_stream = fopen("php://stdin","r"); stream_set_blocking($input_stream, 0); $text = ""; $startTime = time(); // 循环读取,直到没有数据超过2秒(可调整超时时间) while(true) { $line = fgets($input_stream, 4096); if($line !== false) { var_dump('Read from fgets: ', $line); $text .= $line; $startTime = time(); // 重置超时计时 } else { // 没有数据时,检查是否超时 if(time() - $startTime > 2) { break; // 超时退出循环 } usleep(100000); // 短暂休眠,减少CPU消耗 } } fclose($input_stream); echo('Total input from stdin: ' . $text);
但这种方法是基于超时判断,不如方法1可靠,因为无法准确判断输入是否真的结束。
总结
最可靠的解决方案是在Node端写完数据后关闭stdin,这样PHP能明确知道输入结束,循环自然终止,后续代码也能正常执行。
内容的提问来源于stack exchange,提问作者Gary Green
相关产品推荐
相关产品推荐

