You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux下C++开发不阻塞循环检测键盘按键输入的最优方法

Linux下C++结合popen实现运行时键盘检测的最优方案

核心实现逻辑

Linux终端默认处于规范模式,所有键盘输入会缓存到行缓冲区,直到用户按下回车才会提交给程序,要实现实时按键检测,首先要修改终端属性为非规范模式,再通过IO多路复用同时监听popen子进程的输出和标准输入的按键事件,避免阻塞等待。


具体实现步骤

  • 保存原始终端属性,修改终端为非规范模式,关闭回显(可选,根据是否需要显示用户输入决定),设置输入不需要按回车直接上报
  • 调用popen启动目标程序,通过fileno()接口获取popen返回的FILE指针对应的文件描述符
  • 初始化select/poll的监听集合,同时添加popen的文件描述符和STDIN_FILENO(标准输入文件描述符)到监听队列
  • 循环监听事件:
    • 若popen的文件描述符可读:读取子进程输出内容,读到EOF则说明子进程运行结束,退出循环
    • 若标准输入可读:读取用户按键,存储用户选择标记(继续/暂停),可根据需求选择是否丢弃输入内容
  • 调用pclose回收子进程资源,根据之前存储的用户选择标记,执行后续逻辑:继续处理文件/暂停程序等待二次确认
  • 程序退出前必须恢复原始终端属性,避免终端出现输入不显示、回车无效等异常

示例代码

#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <termios.h>
#include <sys/select.h>

int main() {
    // 保存原始终端属性
    termios old_tio, new_tio;
    tcgetattr(STDIN_FILENO, &old_tio);
    new_tio = old_tio;
    // 关闭规范模式和回显
    new_tio.c_lflag &= ~(ICANON | ECHO);
    new_tio.c_cc[VMIN] = 0;
    new_tio.c_cc[VTIME] = 0;
    tcsetattr(STDIN_FILENO, TCSANOW, &new_tio);

    // 启动子进程
    FILE* fp = popen("sleep 5 && echo 'sub process done'", "r");
    if (!fp) {
        tcsetattr(STDIN_FILENO, TCSANOW, &old_tio);
        perror("popen failed");
        return 1;
    }
    int sub_fd = fileno(fp);

    bool user_want_pause = false;
    char buf[1024];
    bool sub_running = true;

    while (sub_running) {
        fd_set read_fds;
        FD_ZERO(&read_fds);
        FD_SET(sub_fd, &read_fds);
        FD_SET(STDIN_FILENO, &read_fds);
        // 阻塞等待事件,也可以设置超时时间避免卡死
        int ret = select(std::max(sub_fd, STDIN_FILENO) + 1, &read_fds, nullptr, nullptr, nullptr);
        if (ret < 0) break;

        if (FD_ISSET(sub_fd, &read_fds)) {
            ssize_t n = read(sub_fd, buf, sizeof(buf)-1);
            if (n <= 0) {
                sub_running = false;
            } else {
                buf[n] = '\0';
                // 这里处理子进程输出,不需要可以丢弃
                printf("sub process output: %s", buf);
            }
        }

        if (FD_ISSET(STDIN_FILENO, &read_fds)) {
            char key;
            ssize_t n = read(STDIN_FILENO, &key, 1);
            if (n > 0) {
                // 示例:按p标记需要暂停,其他键继续
                if (key == 'p' || key == 'P') {
                    user_want_pause = true;
                    printf("\nuser select pause after process done\n");
                }
            }
        }
    }

    pclose(fp);

    // 处理后续逻辑
    if (user_want_pause) {
        printf("\nprocess done, press any key to continue...\n");
        // 等用户按任意键
        while (read(STDIN_FILENO, buf, 1) <= 0);
    } else {
        printf("\nprocess done, continue handling file...\n");
    }

    // 恢复终端属性
    tcsetattr(STDIN_FILENO, TCSANOW, &old_tio);
    return 0;
}

注意事项

  • 建议用RAII封装终端属性的修改和恢复,避免程序异常退出时终端属性无法复原导致终端异常
  • 如果不需要读取子进程的输出,也可以将标准输入设为非阻塞模式,循环调用waitpid的非阻塞接口检测子进程状态,同时检测按键输入,但是IO多路复用的方案CPU占用更低、响应更及时
  • select的最后一个参数可以设置为timeval结构体实现超时检测,避免子进程卡死导致主程序一直阻塞

内容的提问来源于stack exchange,提问作者Chronigan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 21:48:01