非阻塞获取字符

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 上的所有 reads 转换为非阻塞模式,如果您想使用不同的文件描述符以便可以不理会 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 使用 fgetsfgetc 等)和 文件描述符(IO使用readwrite等)

【讨论】:

除了终端也会缓冲整行。 如果stdin 是通过管道输入的,则应该是 EOF。有人使用if (ngetc(c) == -1) Handle_EOF_or_IOError();之类的东西吗 @chux 不,你会使用if (ngetc (&amp;c) == 0) handle_EOF ();,因为readEOF 上返回0,但你会使用if (ngetc (&amp;c) &lt; 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() 只需要处理终端行缓冲区。 @JohnVulconshinz fgets 将消耗整行(将它们存储在缓冲区中并在后续读取时返回它们),而不仅仅是 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; 

【讨论】:

以上是关于非阻塞获取字符的主要内容,如果未能解决你的问题,请参考以下文章

通过阅读python subprocess源码尝试实现非阻塞读取stdout以及非阻塞wait

好好聊聊同步异步阻塞非阻塞

阻塞非阻塞,同步异步总结

同步异步阻塞和非阻塞

《linux设备驱动开发详解》笔记——8阻塞与非阻塞IO

PHP Mailparse 阻塞非 ASCII 字符