【问题标题】:read() from stdin从标准输入读取()
【发布时间】:2012-02-17 00:46:05
【问题描述】:

考虑以下代码行:

while((n = read(STDIN_FILENO, buff, BUFSIZ)) > 0)

据我了解,read/write 函数是非缓冲 I/O 的一部分。那么这是否意味着read() 函数每次从 stdio 调用只会读取一个字符?或者换句话说,n 的值将是

    -1  in case of error
n =  0  in case of EOF
     1  otherwise

如果不是这样,上面的read()函数什么时候会返回,为什么?

注意:我还认为read() 会等到它成功从标准输入中读取BUFSIZ 的字符数。但是如果可读取的字符数少于BUFSIZ,会发生什么情况?读取会永远等待还是直到 EOF 到达(unix 上的Ctrl + D 或 Windows 上的Ctrl + Z)?

另外,假设 BUFSIZ = 100stdin = ACtrl+D(即 EOF 紧跟在单个字符之后)。现在while loop 将迭代多少次?

【问题讨论】:

  • 作为一个参考,我相信你可以从0read,这是标准输入,但只能使用stdinfread()
  • 是的,你说得对……谢谢!!由于标准输入是FILE * 类型,它不能与read 一起使用
  • STDIN_FILENO 如果您想贴标签。
  • @Dave,那个,我不知道。谢谢!

标签: c linux unix io


【解决方案1】:

read() 的行为方式取决于正在读取的内容。对于常规文件,如果您要求 N 个字符,则如果它们可用,则获得 N 个字符,如果文件结尾介入,则小于 N。

如果 read() 在规范/熟模式下从终端读取,则 tty 驱动程序一次提供一行数据。因此,如果您告诉 read() 获取 3 个字符或 300 个字符,read 将挂起,直到 tty 驱动程序看到换行符或终端定义的 EOF 键,然后 read() 将返回该行中的字符数或您请求的字符数,以较小者为准。

如果 read() 在非规范/原始模式下从终端读取,read 将立即访问按键。如果您要求 read() 获取 3 个字符,它可能会返回 0 到 3 个字符,具体取决于输入时间和终端的配置方式。

read() 在面对信号时会有不同的行为,返回的字符数少于请求的字符数,如果在任何字符到达之前信号中断了读取,则返回 -1 并将 errno 设置为 EINTR。

如果描述符已配置为非阻塞 I/O,

read() 的行为会有所不同。如果没有立即可用的输入,read() 将返回 -1,并将 errno 设置为 EAGAIN 或 EWOULDBLOCK。这适用于套接字。

如您所见,当您调用 read() 时,您应该准备好迎接惊喜。你不会总是得到你请求的字符数,你可能会得到像 EINTR 这样的非致命错误,这意味着你应该重试 read()。

【讨论】:

    【解决方案2】:

    您的代码如下:

    while((n = read(0, buff, BUFSIZ) != 0))
    

    这是有缺陷的 - 括号表示它被解释为:

    while ((n = (read(0, buff, BUFSIZ) != 0)) != 0)
    

    在赋值之前评估布尔条件,因此n 只会获得值 0(条件不成立)和 1(条件成立)。

    你应该写:

    while ((n = read(0, buff, BUFSIZ)) > 0)
    

    这会在 EOF 或读取错误时停止,n 让您知道您遇到了哪种情况。


    显然,上面的代码是问题中的错字。

    无缓冲 I/O 最多可读取您读取的字符数(但不会更多)。由于 EOF 或错误,它可能会读得更少。它也可能读得更少,因为在通话时可用的更少。考虑一个终端;通常,这只会读到行尾,因为没有比这更多的可用了。考虑一个管道;如果馈送过程产生了 128 个未读字节,那么如果 BUFSIZ 为 4096,则您只能从读取中获得 128 个字节。非阻塞文件描述符可能会返回,因为没有可用的;一个套接字可能会返回更少的字节,因为还没有更多可用的信息;磁盘读取可能会返回更少的字节,因为执行读取时文件中剩余的字节数少于请求的字节数。

    不过,一般来说,如果您请求多个字节,read() 不会只返回一个字节。

    【讨论】:

    • 那是一个错字...我已经修复它...现在回到实际问题。
    【解决方案3】:

    正如read() 手册页所述:

    返回值

    成功时,返回读取的字节数(零表示文件结束),文件位置提前该数字。如果此数字小于请求的字节数,则不是错误;这可能会发生,例如因为现在实际可用的字节数较少(可能是因为我们接近文件结尾,或者因为我们正在从管道或终端读取),或者因为 read() 被中断信号。出错时,返回 -1,并适当设置 errno。在这种情况下,未指定文件位置(如果有)是否更改。

    因此,每个read() 将读取最多指定字节数;但它可能读得更少。 “非缓冲”表示如果指定read(fd, bar, 1),read 将只读取一个字节。缓冲 IO 尝试读取BUFSIZ 的数量,即使您只需要一个字符。这听起来可能很浪费,但它避免了系统调用的开销,因此速度很快。

    【讨论】:

    • 你的回答太笼统了......请更具体地解决问题
    【解决方案4】:
    1. 尝试读取请求的所有字符。
    2. 如果 EOF 发生在所有请求的字符都可以返回之前,它会返回它得到的 完成此操作后,下一次读取返回 -1,让您知道文件结束。

    当它尝试读取并且没有任何内容时会发生什么涉及称为阻塞的事情。您可以调用 open 来读取文件阻塞或非阻塞。 “阻塞”的意思是等到有东西返回。

    这是您在等待输入的 shell 中看到的内容。它坐在那里。直到你按下回车键。

    非阻塞意味着如果没有数据,读取将不返回任何字节。取决于许多其他因素会使您无法使用完全正确的答案,read 会将 errno 设置为 EWOULDBLOCK 之类的东西,这可以让您知道为什么您的 read 返回零字节。这不一定是致命错误。

    您的代码可以测试减号以查找 EOF 或错误

    【讨论】:

      【解决方案5】:

      当我们说read 没有缓冲时,这意味着在从底层打开文件描述中提取数据后,您的进程级别不会发生缓冲,这是一个潜在的共享资源。如果stdin 是一个终端,那么可能至少有两个额外的缓冲区在起作用,但是:

      1. 终端缓冲区,在离线之前大概可以保存 1-4k 的数据。
      2. 内核的cooked/canonical 模式缓冲区用于终端上的行输入/编辑,允许用户在行上执行原始编辑(退格、退格、擦除行等),直到它被提交(到上述缓冲区)按回车键。

      read 将提取已经提交的任何内容,直到您传递给它的最大读取长度,但它无法从行编辑缓冲区中提取任何内容。如果你想禁用这个额外的缓冲层,你需要查找如何使用tcsetattr 等为终端禁用熟/规范模式。

      【讨论】: