提示信息

C语言非阻塞式键盘监听,用户不输入数据程序也能继续执行

岁月无声 已关注

在C语言中,通过使用特定的函数来实现非阻塞式键盘监听。可以使用下面的示例代码来监听键盘输入,即使用户没有输入数据,程序也可以继续执行:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <termios.h>
  4. #include <unistd.h>
  5. #include <fcntl.h>
  6. #include <sys/select.h>
  7. int main() {
  8. struct termios oldattr, newattr;
  9. unsigned char buffer[1];
  10. // 获取当前终端属性
  11. tcgetattr(STDIN_FILENO, &oldattr);
  12. newattr = oldattr;
  13. // 设置终端属性为非规范模式(禁用行缓冲)
  14. newattr.c_lflag &= ~(ICANON | ECHO);
  15. // 设置终端属性为非阻塞模式
  16. newattr.c_cc[VMIN] = 0;
  17. newattr.c_cc[VTIME] = 0;
  18. // 应用新的终端属性
  19. tcsetattr(STDIN_FILENO, TCSANOW, &newattr);
  20. // 设置标准输入为非阻塞模式
  21. int flags = fcntl(STDIN_FILENO, F_GETFL, 0);
  22. fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
  23. while (1) {
  24. fd_set set;
  25. FD_ZERO(&set);
  26. FD_SET(STDIN_FILENO, &set);
  27. // 使用select函数等待标准输入的可读状态
  28. int ret = select(STDIN_FILENO + 1, &set, NULL, NULL, NULL);
  29. if (ret == -1) {
  30. perror("select");
  31. break;
  32. }
  33. else if (ret == 0) {
  34. // 没有键盘输入,继续执行其他任务
  35. printf("No input. Continuing...\n");
  36. }
  37. else {
  38. // 从标准输入读取键盘输入
  39. ssize_t len = read(STDIN_FILENO, buffer, sizeof(buffer));
  40. if (len > 0) {
  41. printf("Key pressed: %c\n", buffer[0]);
  42. }
  43. }
  44. // 其他任务的执行
  45. // ...
  46. // 模拟延时以减少CPU的负担
  47. usleep(100000);
  48. }
  49. // 还原终端属性
  50. tcsetattr(STDIN_FILENO, TCSANOW, &oldattr);
  51. return 0;
  52. }

上面的代码中使用了select函数来等待标准输入的可读状态,当用户输入了数据时,通过read函数读取键盘输入。如果select返回值为0,表示没有键盘输入,程序可以继续执行其他任务。使用usleep函数来模拟延时以减少CPU的负担。最后,在程序退出之前,需要还原终端的属性。

请注意,由于在非阻塞模式下,read函数可能会返回-1(错误)或者0(无可用数据),在实际使用中,请根据实际需求进行错误处理。

    遇到难题? "AI大模型GPT4.0、GPT" 是你的私人解答专家! 点击按钮去提问......
岁月无声 关注 已关注

最近一次登录:2023-10-09 16:24:26   

暂时还没有签名,请关注我或评论我的文章

神经兮兮
10月12日

使用select函数为非阻塞式输入提供了简便的方法,代码示例易于理解和应用。

李文: @神经兮兮

在处理非阻塞式输入时,select函数确实是相当实用的选择。除了简单的输入检测,select还允许多个文件描述符的监控,这样就可以同时处理多个输入源。以下是一个简单的示例,来演示如何使用select函数实现非阻塞式键盘输入:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/select.h>
#include <string.h>

int main() {
    int fd_stdin = fileno(stdin);
    struct timeval tv;
    fd_set readfds;

    while (1) {
        FD_ZERO(&readfds);
        FD_SET(fd_stdin, &readfds);

        tv.tv_sec = 1;  // 设置1秒的超时
        tv.tv_usec = 0;

        // 检测标准输入是否有数据
        int retval = select(fd_stdin + 1, &readfds, NULL, NULL, &tv);

        if (retval == -1) {
            perror("select()");
            exit(EXIT_FAILURE);
        } else if (retval) {
            char buffer[100];
            if (fgets(buffer, sizeof(buffer), stdin) != NULL) {
                printf("输入: %s\n", buffer);
            }
        } else {
            printf("没有输入,程序继续执行...\n");
        }
    }
    return 0;
}

这个示例会每秒检查一次标准输入是否有数据。如果有,则读取并打印输入;如果没有,就继续执行下一步。对于需要响应输入或者执行其他操作的应用来说,这是一种灵活的处理方式。值得参考的资料可以查看Linux的man select文档,以获取更深入的信息和实例:Linux man pages - select

昨天 回复 举报
入眠
10月22日

对终端属性的操作比较新颖,特别是禁止行缓冲和回显,这样处理键盘输入很实用。

青涩春天: @入眠

很有意思的思路,禁用行缓冲和回显确实在处理非阻塞式键盘输入时非常有效。可以考虑结合 select() 函数来实现更灵活的输入监听。这不仅能让程序在等待输入时继续执行其他任务,还能处理多路输入。

下面的示例展示了如何使用 select() 函数进行非阻塞输入监听:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/select.h>
#include <termios.h>

void setNonBlockingMode() {
    struct termios newt;

    // 获取当前属性
    tcgetattr(STDIN_FILENO, &newt);
    // 禁用回显
    newt.c_lflag &= ~(ECHO | ICANON);
    // 应用新的属性
    tcsetattr(STDIN_FILENO, TCSANOW, &newt);
}

int main() {
    fd_set readfds;
    struct timeval tv;
    char buf[256];

    setNonBlockingMode();

    while (1) {
        FD_ZERO(&readfds);
        FD_SET(STDIN_FILENO, &readfds);

        // 设置超时时间
        tv.tv_sec = 1;  // 1秒
        tv.tv_usec = 0;

        // 监听输入
        int retval = select(STDIN_FILENO + 1, &readfds, NULL, NULL, &tv);

        if (retval == -1) {
            perror("select()");
            exit(EXIT_FAILURE);
        } else if (retval) {
            read(STDIN_FILENO, buf, 1);
            printf("用户输入: %c\n", buf[0]);
        } else {
            printf("未检测到输入,继续执行其他操作...\n");
        }
    }

    return 0;
}

这个示例展示了如何使用 select() 在每次循环中检查用户输入,但程序不会在等待输入时被阻塞。推荐参考 Advanced Programming in the UNIX Environment 以获得更深入的理解。这样的方法能够帮助处理复杂的用户交互场景和其他并发任务。

7天前 回复 举报
-▲ 悸动
10月24日

使用fcntl设置O_NONBLOCK为标准输入创建非阻塞特性值得推荐,小注释解释还算详细。

阿一哥: @-▲ 悸动

使用fcntl设置O_NONBLOCK的确是实现非阻塞输入的一个有效方法,这可以让程序在等待用户输入时不被阻塞,从而继续执行其他任务。下面是一个简单的示例,展示如何在C语言中使用该方法来创建一个非阻塞式的键盘监听。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>

void set_nonblocking_mode() {
    int flags = fcntl(STDIN_FILENO, F_GETFL, 0);
    fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
}

int main() {
    char input[100];
    set_nonblocking_mode();

    while (1) {
        int bytes_read = read(STDIN_FILENO, input, sizeof(input) - 1);
        if (bytes_read > 0) {
            input[bytes_read] = '\0';  // 以null结尾
            printf("用户输入: %s\n", input);
        } else {
            // 用户未输入任何数据,继续执行其他任务
            printf("程序在运行中...\n");
            sleep(1);  // 模拟其他工作
        }
    }

    return 0;
}

这个示例中的set_nonblocking_mode函数通过fcntl将标准输入设置为非阻塞模式。在main循环中,无论用户是否输入数据,程序都能继续执行,并且在每次循环中都可以处理用户输入。

这种方法在处理实时数据或不断运行的任务时非常有用。如果想深入了解更多关于异步I/O的内容,可以参考linux man pages: fcntl

3天前 回复 举报
狂世才子
11月03日

建议增加错误处理演示,比如read函数中加上错误判断以及如何通过errno诊断问题。这会让代码更全面。

沙砾: @狂世才子

对于非阻塞式键盘监听的主题,补充错误处理的建议确实很有必要。使用 read 函数时,添加错误检测可以帮助我们及时捕捉并处理可能出现的问题,例如,EINTR 表示操作被信号中断,EAGAIN 表示没有数据可读。这些都可以通过检查 errno 变量来实现。

以下是一个简单的示例,展示如何进行错误处理:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

int main() {
    char buffer[100];
    int flags = fcntl(STDIN_FILENO, F_GETFL, 0);
    fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);

    while (1) {
        ssize_t bytesRead = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);

        if (bytesRead < 0) {
            if (errno == EAGAIN) {
                // 没有数据可读,继续执行其他操作
                continue;
            } else {
                perror("read error");
                break; // 处理其他错误
            }
        } else if (bytesRead > 0) {
            buffer[bytesRead] = '\0'; // 添加字符串结束符
            printf("Input: %s\n", buffer);
        }

        // 此处可以执行其他逻辑
    }

    return 0;
}

在上述代码中,错误检测被嵌入到 read 调用中,使得在没有数据可读时不会导致程序崩溃,尽管它会在出现其他错误时打印错误信息并退出。这种方式可以帮助你保持程序的健壮性。

建议参考 GNU C Library Documentation 以了解更多关于 errno 和错误处理的信息。这对于编写健壮的 C 语言程序很有帮助。

前天 回复 举报
造物弄人
11月09日

关于usleep用于减轻CPU负担的说明很有帮助,这种延时技巧在处理循环中非常重要。

小小雨: @造物弄人

在讨论非阻塞式键盘监听时,使用 usleep 减轻 CPU 负担的确是一个值得关注的技巧。这种方法在处理高频循环时尤为重要,可以有效防止程序过度占用 CPU 资源。例如,可以通过在循环中加入适当的延时来实现:

#include <stdio.h>
#include <unistd.h>
#include <termios.h>
#include <fcntl.h>

int kbhit(void) {
    struct termios oldt, newt;
    int oldf;
    char ch;
    int done;

    tcgetattr(STDIN_FILENO, &oldt);
    newt = oldt;
    newt.c_lflag &= ~(ICANON | ECHO);
    tcsetattr(STDIN_FILENO, TCSANOW, &newt);

    oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
    fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);

    done = read(STDIN_FILENO, &ch, 1);

    tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
    fcntl(STDIN_FILENO, F_SETFL, oldf);

    if(done == 1) {
        return 1;
    }
    return 0;
}

int main() {
    while (1) {
        if (kbhit()) {
            printf("Key pressed!\n");
            break; // 处理键盘输入后跳出循环
        }
        usleep(100000); // 延时100毫秒  
    }
    return 0;
}

在这个示例中,键盘监听的逻辑通过 kbhit 函数实现,如果没有键盘输入,则程序在每次循环中会通过 usleep 加入100毫秒的延时,从而有效减轻CPU的负担。

可能还可以考虑调整 usleep 的延时时长,以达到更好的平衡点。建议参考一些关于非阻塞输入和 I/O 的资料,例如 Linux Terminal I/O,能够更深入理解相关机制。

刚才 回复 举报
负面情绪
11月14日

代码清晰展示了如何通过termiosfcntl进行非阻塞式IO设置,适合初学者学习。

黯然离别: @负面情绪

这段代码展示了如何利用 termiosfcntl 设置非阻塞式IO,确实是一个很好的学习示例。为了让初学者更加明白这个过程,可以简单介绍一下代码的设置步骤:

首先,通过 tcgetattr 获取当前终端的设置,然后修改这些设置以使终端处于非阻塞模式。以下是一个简化的示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <string.h>

void setNonBlockingMode() {
    struct termios oldt, newt;
    tcgetattr(STDIN_FILENO, &oldt);
    newt = oldt;
    newt.c_lflag &= ~(ICANON | ECHO); // Disable canonical mode and echo
    tcsetattr(STDIN_FILENO, TCSANOW, &newt);
}

void resetTerminal() {
    struct termios oldt;
    tcgetattr(STDIN_FILENO, &oldt);
    oldt.c_lflag |= (ICANON | ECHO); // Enable canonical mode and echo
    tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
}

int main() {
    setNonBlockingMode();

    while (1) {
        char ch;
        if (read(STDIN_FILENO, &ch, 1) > 0) {
            printf("You pressed: %c\n", ch);
            // 可以在这里增加条件来退出循环
            if (ch == 'q') break;  // 输入 'q' 退出
        }
        // 继续执行其他任务
        printf("Working...\n");
        usleep(500000); // 模拟工作
    }

    resetTerminal();
    return 0;
}

以上代码不仅展示了如何进行非阻塞式输入,还能让程序在等待用户输入的同时,继续执行其他代码。这样的设计很适合需要在不干扰用户输入的情况下执行其他操作的场景。

为了更深入了解该主题,建议参考 这篇关于 C 语言终端控制的文章,可以提供更详细的背景知识和使用技巧。

刚才 回复 举报
本末倒置
11月16日

对终端属性的更改前后都进行了保存和还原,这是良好的编程习惯,有助于防止意外的终端行为。

燃烧的冰: @本末倒置

在讨论C语言的非阻塞式键盘监听时,保存和还原终端属性确实是一个非常重要的环节。这不仅可以避免程序在异常情况下导致的终端行为混乱,还能确保用户体验的一致性。在实现非阻塞读取键盘输入时,通过更改终端设置,可以实现所需的效果。

利用tcgetattrtcsetattr函数保存和恢复终端状态是一个有效的做法。下面是一个简单的示例,展示如何在非阻塞读取中管理终端属性:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <fcntl.h>

void enableNonBlockingMode(struct termios *oldt) {
    struct termios newt;
    // 获取当前终端属性
    tcgetattr(STDIN_FILENO, &newt);
    *oldt = newt; // 保存旧属性
    newt.c_lflag &= ~(ICANON | ECHO); // 修改属性
    tcsetattr(STDIN_FILENO, TCSANOW, &newt); // 应用新属性
}

void restoreTerminalMode(struct termios *oldt) {
    // 恢复终端属性
    tcsetattr(STDIN_FILENO, TCSANOW, oldt);
}

int kbhit(void) {
    struct termios oldt, newt;
    enableNonBlockingMode(&oldt);

    int bytesWaiting = 0;

    // 读取输入状态
    if (ioctl(STDIN_FILENO, FIONREAD, &bytesWaiting) < 0) {
        perror("ioctl");
    }

    restoreTerminalMode(&oldt);
    return bytesWaiting > 0;
}

int main() {
    while (1) {
        if (kbhit()) {
            char ch = getchar();
            printf("You pressed: %c\n", ch);
        } else {
            printf("No input, continue processing...\n");
            // 这里可以放置其他代码逻辑
            sleep(1); // 模拟处理延迟
        }
    }
    return 0;
}

这个示例展示了如何在非阻塞模式下监测键盘输入。当无输入时,程序能够继续执行而不会被阻塞。同时,正确地保存和恢复终端属性,有助于保持终端的原始状态,避免影响用户后续的操作体验。对这样的实践,有兴趣的开发者可以参考 GNU C Library Documentation 获取更多关于终端控制的细节和最佳实践。

刚才 回复 举报
浅怀感伤
11月19日

代码演示了非阻塞输入的基本逻辑,可以在需要实时响应用户输入的程序中加以应用。

韦福康: @浅怀感伤

在处理非阻塞式键盘监听时,简洁有效的实现方法确实能够提升程序的响应性。一个常见的技术是使用 select() 函数,它可以检测多个文件描述符是否有数据可读,从而避免阻塞。下面是一个简单的例子,展示如何使用 select() 来实现非阻塞输入:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/select.h>

int main() {
    fd_set readfds;
    struct timeval tv;
    char buffer[100];

    // 设置标准输入为非阻塞
    int flags = fcntl(STDIN_FILENO, F_GETFL, 0);
    fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);

    while (1) {
        FD_ZERO(&readfds);
        FD_SET(STDIN_FILENO, &readfds);

        tv.tv_sec = 1;  // 设置1秒超时
        tv.tv_usec = 0;

        int rv = select(STDIN_FILENO + 1, &readfds, NULL, NULL, &tv);
        if (rv == -1) {
            perror("select");
            exit(EXIT_FAILURE);
        } else if (rv == 0) {
            printf("No input within 1 second.\n");
        } else {
            if (FD_ISSET(STDIN_FILENO, &readfds)) {
                fgets(buffer, sizeof(buffer), stdin);
                printf("You entered: %s\n", buffer);
            }
        }
        // 继续执行其他任务
    }

    return 0;
}

这种方式在频繁轮询用户输入时,能避免程序无谓的阻塞。同时,在设计实时响应的应用时,考虑用户体验非常重要。可以参考一些相关资料,深入了解更多实现细节,比如 Beej's Guide to Unix IPC 中也有一些关于非阻塞IO的内容,值得一读。

6天前 回复 举报
舞颜如玉
11月27日

这个方法避免了程序在等待用户输入时的阻塞现象,是提升用户体验和程序效率的重要实践。

沉默控: @舞颜如玉

对于非阻塞式键盘监听这种方法的应用,确实能够显著提升程序的用户体验。尤其是在需要实时响应其他任务的场景中,采用异步方式监听输入显得尤为重要。

一个常见的实现方式是使用线程结合 select 系统调用。在下面的示例中,通过创建一个子线程处理键盘输入,主线程可以继续执行其他任务:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <fcntl.h>

void* keyboard_listener(void* arg) {
    char buffer[100];
    while (1) {
        if (fgets(buffer, sizeof(buffer), stdin) != NULL) {
            printf("Input received: %s", buffer);
        }
    }
    return NULL;
}

int main() {
    pthread_t thread;
    pthread_create(&thread, NULL, keyboard_listener, NULL);

    while (1) {
        printf("Main loop is running...\n");
        sleep(1);
    }

    pthread_join(thread, NULL);
    return 0;
}

在这个例子中,keyboard_listener 函数在独立线程中运行,允许主线程持续执行其他逻辑。如此一来,程序能在用户输入时即时反馈,而不至于被输入阻塞。

更多关于非阻塞输入实现的参考,可以查看 POSIX Threads Programming 这份资源,里面详细介绍了线程的用法与输入输出的处理方法。希望这样的示例和参考资料能为实现非阻塞式键盘监听提供更明确的思路。

7天前 回复 举报
村上
12月03日

获取终端当前属性并将其设置为非规范模式,代码有效且不错,但可以扩展到其他设备的非阻塞输入。

等着你: @村上

对于非阻塞式输入的实现,可以考虑使用 select() 函数,这样就可以扩展到监测其他文件描述符,例如套接字或管道。这使得程序在等待输入时能够处理其他事件,例如网络请求或设备操作。以下是一个简单的示例,展示如何在C语言中使用 select() 来实现非阻塞式键盘监听:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/select.h>
#include <termios.h>

void set_non_canonical_mode() {
    struct termios newt;
    tcgetattr(STDIN_FILENO, &newt);
    newt.c_lflag &= ~(ICANON | ECHO); // 关闭规范模式和回显
    tcsetattr(STDIN_FILENO, TCSANOW, &newt);
}

int main() {
    set_non_canonical_mode();

    fd_set read_fds;
    struct timeval tv;
    int retval;

    while (1) {
        FD_ZERO(&read_fds);
        FD_SET(STDIN_FILENO, &read_fds);

        tv.tv_sec = 1; // 等待1秒
        tv.tv_usec = 0;

        retval = select(STDIN_FILENO + 1, &read_fds, NULL, NULL, &tv);

        if (retval == -1) {
            perror("select()");
            exit(EXIT_FAILURE);
        } else if (retval) {
            char buf;
            read(STDIN_FILENO, &buf, 1);
            printf("Received: %c\n", buf);
        } else {
            // 没有输入,继续执行其他操作
            printf("No input, continuing...\n");
        }
    }

    return 0;
}

在这个代码示例中,select() 函数允许我们在指定的文件描述符上进行监听,而在等待输入的同时,程序可以继续运行其他任务。这种方法的灵活性在于可以轻松地添加其他文件描述符进行监听。可以参考 man7.org 的 select(2) 文档 了解更多关于 select() 函数的信息和使用示例。

6天前 回复 举报
×
免费图表工具,画流程图、架构图