PHP 中高效监听标准输入按键的正确实现方法

10次阅读

PHP 中高效监听标准输入按键的正确实现方法

本文介绍如何解决 php 使用非阻塞 `fgets()` 监听 stdin 时导致 cpu 占用率飙升至 100% 的问题,并提供基于 `time_nanosleep()` 的轻量级轮询方案及更优的替代实践。

在命令行 php 应用中,常需实时响应用户按键(如菜单导航、交互式工具),开发者常采用 fopen(“php://stdin”) 配合 stream_set_blocking(false) 实现非阻塞读取。但原始写法存在严重性能缺陷:

$stdin = fopen("php://stdin", "r"); stream_set_blocking($stdin, false); system("stty cbreak -echo");  while (true) {     if ($keypress = strtoupper(fgets($stdin))) {         break;     } }

循环会以极高速度反复调用 fgets() —— 当输入缓冲区为空时,fgets() 立即返回 false,CPU 在无休止的“检查→失败→再检查”中满载运行,造成 100% 占用,既耗电又影响系统响应。

推荐修复方案:引入微秒级休眠
通过 time_nanosleep(0, N) 在每次空读之后暂停指定纳秒数(如 5 毫秒 = 5,000,000 纳秒),可将 CPU 占用降至接近 0%,同时保持毫秒级响应灵敏度:

$stdin = fopen("php://stdin", "r"); stream_set_blocking($stdin, false); system("stty cbreak -echo");  $t = 1000000; // 1 微秒 = 1000 纳秒 → 此处单位为纳秒 while (!($keypress = strtoupper(trim(fgets($stdin))))) {     time_nanosleep(0, 5 * $t); // 休眠 5 毫秒 }  // 恢复终端设置(关键!) system("stty -cbreak echo"); stream_set_blocking($stdin, true); fclose($stdin);

⚠️ 注意事项

  • 必须调用 trim() 清除换行符,避免误判空行;
  • stty cbreak -echo 关闭回显并启用单字符输入,退出前务必恢复(如 stty -cbreak echo),否则终端可能异常;
  • time_nanosleep() 在 windows 下不可用,生产环境建议封装兼容逻辑或改用 usleep(5000)(精度略低但跨平台);
  • 若需更高可靠性,可结合 stream_select() 实现真正的 I/O 多路复用(需 PHP ≥ 7.4,且仅限 unix-like 系统):
$read = [$stdin]; $write = $except = []; if (stream_select($read, $write, $except, 0, 5000) > 0) {     $keypress = strtoupper(trim(fgets($stdin))); }

? 总结:非阻塞轮询必须配合适当延时,time_nanosleep() 是平衡响应性与资源消耗的简洁解法;长期项目建议抽象为可配置的 KeyReader 类,并增加超时、信号中断等健壮性处理。

立即学习PHP免费学习笔记(深入)”;

text=ZqhQzanResources