【问题标题】:How to do a non-blocking read on a non-socket fd如何在非套接字 fd 上进行非阻塞读取
【发布时间】:2018-12-07 16:54:26
【问题描述】:

有没有办法在管道/终端/等上以非阻塞模式单个 read(),就像我可以在带有recv(MSG_DONTWAIT) 的套接字上那样做? p>

我需要这样做的原因是因为我无法保证文件描述符上的 read() 返回为可供select()poll() 读取的准备不会 阻塞。

我知道可以使用fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) 使文件描述符非阻塞,但这将全局更改该文件描述符的模式,而不仅仅是在调用线程/进程中。例如:

% perl -MFcntl=F_SETFL,F_GETFL,O_NONBLOCK -e 'fcntl STDIN, F_SETFL, fcntl(STDIN, F_GETFL, 0) | O_NONBLOCK; select undef, undef, undef, undef'
^Z # put it in the background
% cat
cat: -: Resource temporarily unavailable

这也将使 fd 对 读取和写入都非阻塞,这可能会混淆另一个进程在同一个 fd 上执行相反操作,如:

non_blocking_read | filter | blocking_write

我想到的一种方法是在启动和SIGCONT 上保存文件状态标志,并在退出和SIGTSTP 上恢复它们​​(就像使用 termios 设置完成的方式一样),但这非常有限,容易竞争,并且在程序异常退出的情况下会留下一团糟。

在每个read() 之前/之后使用fcntl() 进行保存/恢复也感觉丑陋和愚蠢,并且可能还有其他问题。在 read 之前的 ioctl(FIONREAD) 也是如此(我什至不确定它是否能可靠地与任何 fd 一起工作;不过,欢迎朝这个方向保证)。

即使使用特定于系统(例如 linux 或 bsd-only)的解决方案,我也会很高兴。

供参考,here 是关于在 linux 中修复它的讨论;不过,这个想法似乎没有成功。

【问题讨论】:

    标签: unix pipe nonblocking file-descriptor tty


    【解决方案1】:

    Linux 唯一的解决方案是通过以下方式重新打开文件描述符 "/dev/stdin"|"/dev/tty"|"/dev/fd/$fd".

    C 示例:

    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <stdio.h>
    int main()
    {
        int fd;
        char buf[8];
        int flags;
        if(0>(fd=open("/dev/stdin", O_RDONLY))) return 1;
        if(0>(flags = fcntl(fd,F_GETFL))) return 1;
        if(0>(flags = fcntl(fd,F_SETFL,flags|O_NONBLOCK))) return 1;
        sleep(3);
        puts("reading");
        ssize_t nr = read(fd, buf, sizeof(buf));
        printf("read=%zd\n", nr);
        return 0;
    }
    

    duplicated 文件描述符不同,重新打开的文件描述符将具有独立的文件状态标志。

    【讨论】:

    • 谢谢,这看起来很有希望,但是如果 fd 是从以另一个用户身份运行的进程传递下来的,open() 将失败,权限被拒绝;示例:su - other_user -c 'cat /dev/stdin' => ... Permission denied(与您的程序相同,如果您将第一个 return 1 更改为 err(1, "open")。与 /proc/self/fd/FD、管道等相同。
    • 这太糟糕了。 IMO,只要您不传递特定于套接字的标志,内核就不会让您将 recv 与任何文件一起使用,这既丑陋又愚蠢。还不如做下一个丑陋而愚蠢的事情并保存和恢复。您还可以捕获退出信号以确保即使使用 SIGTERM 或 SIGABRT 也能进行恢复。
    猜你喜欢
    • 2011-12-27
    • 2012-07-24
    • 1970-01-01
    • 2017-01-16
    • 2010-12-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-02
    相关资源
    最近更新 更多