select(2) 和 ioctl(2) 在 stdin 有数据时返回 0
select(2) and ioctl(2) returning 0 while stdin has data
我正在尝试检测 stdin 上是否有数据供我读取。
具体来说,我使用 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);
}
}
抱歉代码太长了。发生的情况如下:
- 当您按下退出键时,它会成功检测到。
- 当您按下转义键然后快速按下其他键(如 'a')时,代码会成功检测到还有更多内容需要阅读,这很好;转义序列的特定检测超出范围。
- 当您生成转义序列时,例如通过按箭头键,
select(2)
和 ioctl(2)
return 没有什么可读的,虽然显然有;这可以通过在指定位置插入 printf("%c\n",getchar());
轻松检查。这将打印 [
(至少在箭头键的情况下)。
问题:案例(3)如何正确检测输入?
来自 getchar 手册页:
It is not advisable to mix calls to input functions from the
stdio library with low-level calls to read(2) for the file descriptor
associated with the input stream; the results will be undefined
and very probably not what you want.
不要混用缓冲和非缓冲输入函数。
select
必须与
结合使用
read(fileno(stdin), &c, 1);
而不是
c = getchar();
我正在尝试检测 stdin 上是否有数据供我读取。
具体来说,我使用 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);
}
}
抱歉代码太长了。发生的情况如下:
- 当您按下退出键时,它会成功检测到。
- 当您按下转义键然后快速按下其他键(如 'a')时,代码会成功检测到还有更多内容需要阅读,这很好;转义序列的特定检测超出范围。
- 当您生成转义序列时,例如通过按箭头键,
select(2)
和ioctl(2)
return 没有什么可读的,虽然显然有;这可以通过在指定位置插入printf("%c\n",getchar());
轻松检查。这将打印[
(至少在箭头键的情况下)。
问题:案例(3)如何正确检测输入?
来自 getchar 手册页:
It is not advisable to mix calls to input functions from the stdio library with low-level calls to read(2) for the file descriptor associated with the input stream; the results will be undefined and very probably not what you want.
不要混用缓冲和非缓冲输入函数。
select
必须与
read(fileno(stdin), &c, 1);
而不是
c = getchar();