当 stdin 有数据时 select(2) 和 ioctl(2) 返回 0

tom*_*ing 5 c unix macos select ioctl

我正在尝试检测标准输入上是否有数据可供我读取。

具体来说,我已经使用 关闭了规范模式tcsetattr,因此我可以一次读取一个字符(阻塞)。我想检测转义序列,例如由箭头键产生的转义序列,但将它们与单独的转义键区分开来。此外,我想快速知道输入了哪些内容;我假设我的终端相当快,并且后面的转义序列的其余部分也^[相当快。

假设我已经知道(例如使用select(2))标准输入上有一些东西可以读。我使用 读取一个字符getchar(),它是^[, ASCII 27。现在我想知道在某个时间间隔内是否有更多字符出现,或者这个转义字符就是一切(这将表明按下了转义键)。用于select(2)此似乎不起作用,因为(我注意到,并在其他地方读到)其余字符已经被缓冲,所以select(2)没有什么可检测的了。所以我转向ioctl(2)使用FIONREAD,但这似乎也不起作用。

最小(非)工作示例:

#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <sys/select.h>
#include <sys/ioctl.h>
#include <assert.h>

struct termios tios_bak;

void initkeyboard(void){
    struct termios tios;
    tcgetattr(0,&tios_bak);
    tios=tios_bak;

    tios.c_lflag&=~ICANON;
    tios.c_cc[VMIN]=1; // Read one char at a time
    tios.c_cc[VTIME]=0; // No timeout on reading, make it a blocking read

    tcsetattr(0,TCSAFLUSH,&tios);
}

void endkeyboard(void){
    tcsetattr(0,TCSAFLUSH,&tios_bak);
}

int main(void){
    initkeyboard();
    atexit(endkeyboard);

    printf("Press an arrow key or the escape key, or the escape key followed by something else.\n");

    char c=getchar();
    if(c!=27){
        printf("Please input an escape sequence or key\n");
        exit(1);
    }

    // Now we use select(2) to determine whether there's anything more to read.
    // If it was a lone escape key, there won't be anything new in a while.
    fd_set rdset;
    FD_ZERO(&rdset);
    FD_SET(0,&rdset);
    struct timeval tv;
    tv.tv_sec=1; // Here we wait one second; this is just to illustrate. In a real environment
    tv.tv_usec=0; // I'd wait something like 100ms, since that's reasonable for a terminal.
    int ret=select(1,&rdset,NULL,NULL,&tv);

    assert(ret!=-1); // (Error checking basically omitted)
    if(ret==0){
        printf("select(2) returned 0.\n");
        int n;
        assert(ioctl(0,FIONREAD,&n)>=0);
        assert(n>=0);
        if(n==0){
            printf("ioctl(2) gave 0; nothing to read: lone escape key\n");
            // INSERT printf("%c\n",getchar()); HERE TO DEMONSTRATE THIS IS WRONG IN CASE OF ESCAPE SEQUENCE
        } else {
            c=getchar();
            printf("ioctl(2) says %d bytes in read buffer (first char=%c)\n",n,c);
        }
    } else {
        c=getchar();
        printf("select(2) returned %d: there was more to read (first char=%c)\n",ret,c);
    }
}
Run Code Online (Sandbox Code Playgroud)

抱歉代码很长。发生的情况如下:

  1. 当您只需按退出键时,它就会成功检测到。
  2. 当您按下退出键,然后快速按下其他键(例如“a”)时,代码会成功检测到还有更多内容需要阅读,这很好;对转义序列的具体检测超出了范围。
  3. 当您生成转义序列时,例如通过按箭头键,两者select(2)ioctl(2)返回都没有什么可读取的,而显然有;printf("%c\n",getchar());通过插入指定位置可以轻松检查这一点。这将打印[(至少在使用箭头键的情况下)。

问题:如何正确检测情况(3)中的输入?

Dav*_*eri 5

getchar手册页:

不建议将对 stdio 库中的输入函数的调用与对与输入流关联的文件描述符的 read(2) 的低级调用混合在一起;结果将是不确定的,并且很可能不是您想要的。

不要混合使用缓冲和非缓冲输入函数。

select必须与以下组合使用

read(fileno(stdin), &c, 1);
Run Code Online (Sandbox Code Playgroud)

代替

c = getchar();
Run Code Online (Sandbox Code Playgroud)

  • 谢谢,就是这样!还没有在其他地方看到这个要求的解释。 (2认同)