非阻塞获取字符
Posted
技术标签:
【中文标题】非阻塞获取字符【英文标题】:Nonblocking Get Character 【发布时间】:2013-12-14 20:40:03 【问题描述】: 平台:Linux 3.2.0 x86 (Debian 7) 编译器:GCC 4.7.2 (Debian 4.7.2-5)如果字符已经存在于标准输入中,我正在编写一个从标准输入读取单个字符的函数。如果 stdin 为空,则该函数假定什么都不做并返回 -1。我搜索了非阻塞输入,并被指向poll() 或select()。首先我尝试使用 select() 但我无法让它工作,所以我尝试了 poll() 并得出了相同的结论。我不确定这些函数究竟做了什么,但根据我对 poll() 文档的理解,如果我这样称呼它:
struct pollfd pollfds;
pollfds = STDIN_FILENO;
pollfds.events = POLLIN;
poll(pollfds, 1, 0);
if(pollfds.revents & POLLIN) 如果“可以不阻塞地读取高优先级数据以外的数据”,则为真。但是 poll() 在我的测试情况下总是超时。我如何测试功能可能是问题,但我想要的功能正是我正在测试的。这是目前的功能和测试情况。
#include <poll.h>
#include <stdio.h>
#include <unistd.h>
int ngetc(char *c)
struct pollfd pollfds;
pollfds.fd = STDIN_FILENO;
pollfds.events = POLLIN;
poll(&pollfds, 1, 0);
if(pollfds.revents & POLLIN)
//Bonus points to the persons that can tell me if
//read() will change the value of '*c' if an error
//occurs during the read
read(STDIN_FILENO, c, 1);
return 0;
else return -1;
//Test Situation:
//Try to read a character left in stdin by an fgets() call
int main()
int ret = 0;
char c = 0;
char str[256];
//Make sure to enter more than 2 characters so that the excess
//is left in stdin by fgets()
fgets(str, 2, stdin);
ret = ngetc(&c);
printf("ret = %i\nc = %c\n", ret, c);
return 0;
【问题讨论】:
***.com/questions/3962263/… 你的函数永远不会返回 0 : if ( ... ) return errno;否则返回 EWOULDBLOCK; ;那么最后的回报有什么用? @philippelhardy 如果在读取过程中发生错误,该函数将返回 errno,但如果在读取过程中未发生错误,该函数将返回 0。该语句首先检查 stdin 中是否有数据如果没有数据,则调用 read() 之前的语句短路,但如果 stdin 中有数据,则调用 read(),如果 read() 返回 -1,则发生读取错误,因此返回 errno,否则如果未发生读取错误则条件为假,因此返回 0。 我的意思是:看起来最后一个 return 0 只是一个无法访问的代码,因为 if () return A;否则返回 B;模式。 哦,是的,我知道你的意思它看起来有点尴尬 【参考方案1】:您的 IO 操作不正确,POSIX 手册和所有其他相关文档都明确表示永远不要混合在 FILE *
s 和文件描述符上完成的 IO。你已经非常公然地违反了这条规则。这条规则是存在的,因为FILE *
s 使用 buffering 这意味着在调用fgets
之后,read
将没有任何东西可以获取,因为fgets
已经读取了所有待处理的数据放入保存在FILE *
结构中的缓冲区中。
因此,由于无法检查 ISO C IO 方法是否会阻塞,我们只能使用文件描述符。
既然我们知道STDIN_FILENO
只是数字0,我们可以使用
fcntl (0, F_SETFL, O_NONBLOCK);
这会将文件描述符 0 上的所有 read
s 转换为非阻塞模式,如果您想使用不同的文件描述符以便可以不理会 0,那么只需使用 dup
复制它。
这样你就可以完全远离poll
,实现ngetc
为
ssize_t
ngetc (char *c)
return read (0, c, 1);
或者更好的是,一个宏
#define ngetc(c) (read (0, (c), 1))
这样您就可以得到一个简单的实现来满足您的需求。
编辑:如果您仍然担心终端缓冲输入,您可以随时更改终端的设置,请参阅How to disable line buffering of input in xterm from program? 了解有关如何执行此操作的更多信息。
编辑:不能使用fgetc
代替read
的原因与使用fgets
不起作用的原因相同。当FILE *
IO 函数之一运行时,它会从关联的文件描述符中读取所有数据。但是一旦发生这种情况,poll
将永远不会返回,因为它正在等待一个始终为空的文件描述符,read
也会发生同样的事情。因此,我建议您遵循文档的建议,永远不要混合 streams(IO 使用 fgets
、fgetc
等)和 文件描述符(IO使用read
、write
等)
【讨论】:
除了终端也会缓冲整行。 如果stdin
是通过管道输入的,则应该是 EOF。有人使用if (ngetc(c) == -1) Handle_EOF_or_IOError();
之类的东西吗
@chux 不,你会使用if (ngetc (&c) == 0) handle_EOF ();
,因为read
在EOF
上返回0
,但你会使用if (ngetc (&c) < 0) handle_other_error ();
来检查任何其他类型的错误。
@randomusername 感谢您的回复。我只是有一些问题,您是说在 fgets() 以我所说的方式调用后,我将永远无法读取留在标准输入中的数据?此外,我尝试使用 fgetc() 代替 read() 并且也不起作用,您能向我解释为什么 fgetc() 也不起作用。
@JohnVulconshinz 是的,我是说你永远无法以你所说的方式使用fgets
。 ISO C 标准没有为您提供解决方法,而使用 POSIX 标准来解决它需要您使用 文件描述符 来代替。【参考方案2】:
你的代码有两个问题。
根据poll
的manual,给超时赋值0会立即返回
如果 timeout 的值为 0,poll() 将立即返回。如果 timeout 的值为 -1,poll() 将阻塞直到请求的事件发生或直到调用被中断。
fgets
不符合您的预期,它来自 stdio 库并将缓冲读取。假设你输入了 3 个字母并回车,fgets
之后的第三个字母将无法提供给poll
。
所以注释掉 fgets 行并将 -1 分配给 poll
中的超时,然后再次运行它以查看是否是您想要的。
【讨论】:
@dlutxx 感谢您的回复。它读取缓冲区是什么意思?我认为 fgets() 只需要处理终端行缓冲区。 @JohnVulconshinzfgets
将消耗整行(将它们存储在缓冲区中并在后续读取时返回它们),而不仅仅是 2 个字符【参考方案3】:
我没有得到上述答案的预期行为,实际上我还必须考虑this answer 它将 TTY 设置为非规范模式。
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <termios.h>
int main(int argc, char *argv[])
struct termios t;
tcgetattr(0, &t);
t.c_lflag &= ~ICANON;
tcsetattr(0, TCSANOW, &t);
fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
printf("Starting loop (press i or q)...\n");
for (int i = 0; ; i++)
char c = 0;
read (0, &c, 1);
switch (c)
case 'i':
printf("\niteration: %d\n", i);
break;
case 'q':
printf("\n");
exit(0);
return 0;
【讨论】:
以上是关于非阻塞获取字符的主要内容,如果未能解决你的问题,请参考以下文章