Linux中select函数在循环中使用注意事项

本文探讨了在循环中调用select函数时遇到的问题,即第一次超时后,后续调用立即退出。通过分析发现原因是select函数会更新超时参数。解决办法是在每次循环开始时重置超时时间。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在学习select函数时遇到一个问题,在循环中调用select函数,并设置了超时退出。循环第一次超时退出,第二次瞬间退出。使用select的部分代码如下:


int maxfd = -1;
    fd_set read_fd;

    struct timeval tv;//设置时间
    tv.tv_sec = 1;
    tv.tv_usec = 500000;
    char buf[10] = {0};
    while(1)
    {
        FD_ZERO(&read_fd);//清空集合
        FD_SET(0,&read_fd);//将标准输入放入集合
      
        switch(select(1,&read_fd,NULL,NULL,&tv))
        {
            case -1:
                printf("select error\n");
                break;
            case 0:
                printf("timeout\n");
                break;
            default: 
            if(FD_ISSET(0,&read_fd))
            {
                printf("reading!...");
                read(0,buf,9);
                printf("buf = %s\n",buf);
                memset(buf,0,sizeof(buf));
            }
            break;
        }
    }

从逻辑上看,并没有什么问题,但在实际运行中,却只有第一次是超时退出的,后面就出现了timeout刷屏的情况。开始以为程序逻辑问题,进行一番修改最终还是没能解决。于是在网上查找了一番,最终定位了问题所在。用man手册查了一下select的描述,全是英文也没怎么看懂。百度翻译了一下,大致明白了意思。
DESCRIPTION
       select() and pselect() allow a program to monitor multiple file descriptors, waiting until one or
       more of the file descriptors become "ready" for some class of I/O operation (e.g.,  input  possi‐
       ble).   A  file descriptor is considered ready if it is possible to perform the corresponding I/O
       operation (e.g., read(2)) without blocking.
       The operation of select() and pselect() is identical, with three differences:
       (i)    select() uses a timeout that is a struct timeval (with seconds  and  microseconds),  while
              pselect() uses a struct timespec (with seconds and nanoseconds).
       (ii)   select()  may  update  the timeout argument to indicate how much time was left.  pselect()
              does not change this argument.
       (iii)  select() has no sigmask argument, and behaves as pselect() called with NULL sigmask.

第二条指出:(ii)select()可更新超时参数以指示剩下多少时间。pselect()不更改此参数。
大致意思就是说每次调用select,超时参数会改变,即timeout是上一次调用后剩余的时间。如果上一次是超时退出的,那么下一次时间就为0,这就导致上面说的第二次调用瞬间退出的造成timeout刷屏的现象。所以,在循环中需要这样修改一下:
 while(1)
    {
        FD_ZERO(&read_fd);//清空集合
        FD_SET(0,&read_fd);//将标准输入放入集合
       //每调用一次select,timeout是上一次的剩余时间,即第二次都是为0,会导致频繁调用造成CPU高 
       //故在此每次需要重制timeout
        tv.tv_sec = 1;
        tv.tv_usec = 500000;
        switch(select(1,&read_fd,NULL,NULL,&tv))
        {
            case -1:
                printf("select error\n");
                break;
            case 0:
                printf("timeout\n");
                break;
            default: 
            if(FD_ISSET(0,&read_fd))
            {
                printf("reading!...");
                read(0,buf,9);
                printf("buf = %s\n",buf);
                memset(buf,0,sizeof(buf));
            }
            break;
        }
    }
每次循环重置超时时间,这样就不会出现select函数再次调用秒退的情况。
<think>好的,我现在需要回答用户关于C语言中kbhit()函数的问题。首先,我得回忆一下这个函数的基本信息。kbhit()是一个用于检查键盘是否有按键被按下的函数,它属于非阻塞式的输入检测。这意味着程序可以在不暂停执行的情况下检查输入状态。 接下来,我应该考虑这个函数的所属头文件。我记得在Windows环境下,kbhit()通常位于<conio.h>头文件中,而Linux或Unix系统可能需要使用其他库,比如<curses.h>或者<ncurses.h>,或者通过系统调用来实现类似功能。这点需要明确指出,避免用户在不同平台使用时遇到问题。 然后,函数的返回值是关键。当有按键按下时,kbhit()返回非零值,否则返回0。这样用户就可以在循环使用它来实时检测输入,而不必等待输入阻塞程序。例如,在游戏开发中处理用户输入或实时监控按键时很有用。 不过,需要注意这个函数的标准性问题。C标准库中并没有kbhit(),它是编译器特定的扩展,比如在MSVC或Borland C中常见。在跨平台开发时,依赖这个函数可能导致兼容性问题,所以可能需要替代方案,比如在Linux使用select()或ncurses库的函数。 实际应用中,用户可能会在循环使用kbhit()配合getch()来获取具体的按键值。这时候要提醒用户及时处理输入缓冲区,防止多个按键堆积。此外,不同编译器或平台的实现可能有差异,需要查阅相关文档。 还需要考虑用户可能的后续问题,比如替代函数或示例代码。虽然用户现在没有问,但提前准备这些信息会让回答更全面。例如,提供一个简单的示例代码,展示如何在Windows下使用kbhit()检测按键,并退出循环。 最后,确保回答结构清晰,分点说明函数的作用、头文件、返回值、注意事项和示例,帮助用户逐步理解。同时,使用中文口语化表达,避免技术术语过于晦涩,让不同层次的用户都能理解。</think>在C语言中,`kbhit()` 是一个用于 **非阻塞式键盘输入检测** 的函数。以下是分步说明: --- ### 1. **功能作用** - **核心功能**:检测键盘是否有按键被按下(无需等待用户输入)。 - **特点**:非阻塞(non-blocking),即无论是否有输入,函数会立即返回结果,不会暂停程序执行。 --- ### 2. **所属头文件** - **Windows环境**:需包含 `<conio.h>`(非标准库,常见于MSVC、Borland等编译器)。 - **Linux环境**:原生不支持,需借助第三方库(如 `ncurses`)或系统调用。 --- ### 3. **函数原型** ```c int kbhit(void); ``` - **返回值**: - **非0值**:键盘缓冲区中有按键被按下。 - **0**:无按键按下。 --- ### 4. **典型使用场景** ```c #include <stdio.h> #include <conio.h> // Windows专用 int main() { while(1) { if (kbhit()) { // 检测到按键 char ch = getch(); // 获取按键值 printf("按下了: %c\n", ch); if (ch == 'q') break; // 按q退出 } // 其他任务(如循环内的计算) } return 0; } ``` --- ### 5. **注意事项** - **平台依赖性**:非C标准函数,Windows专用。Linux需用替代方案: - 使用 `select()` 检测输入(涉及文件描述符)。 - 使用 `ncurses` 库的 `nodelay()` 和 `getch()`。 - **缓冲区问题**:多次调用 `kbhit()` 后需及时用 `getch()` 清空键盘缓冲区。 --- ### 6. **替代方案(跨平台)** ```c // Linux示例(需包含 <termios.h> 和 <unistd.h>) int kbhit_linux() { struct termios oldt, newt; int ch, oldf; 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); ch = getchar(); tcsetattr(STDIN_FILENO, TCSANOW, &oldt); fcntl(STDIN_FILENO, F_SETFL, oldf); if (ch != EOF) { ungetc(ch, stdin); return 1; } return 0; } ``` --- ### 总结 - **用途**:实时检测键盘输入(如游戏控制、交互式命令行工具)。 - **局限**:依赖特定环境,跨平台需谨慎处理。 - **推荐**:优先使用标准输入函数(如 `fgets()`)或跨平台库(如 `ncurses`)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值