Haskell默认的IO缓冲

16

昨天我为我的学生们写了一个小的xinetd练习:制作一个反转回声程序。

为了学习新知识,我尝试实现了Haskell的解决方案。简单的main = forever $ interact reverse是不起作用的。我查阅了这个问题,并制作了一个更正后的版本:

import Control.Monad
import System.IO
 
main = forever $ interact revLines
 
revLines = unlines . map (reverse) . lines 

但是这个更正的版本也不起作用。我阅读了缓冲文档,并尝试了各种设置。 如果我将 NoBuffering 或者 LineBuffering 设置上,程序就可以正确运行。最后我打印了stdin和stdout的默认缓冲模式。

import System.IO

main = do 
  hGetBuffering stdin >>= print 
  hGetBuffering stdout >>= print

如果我从xinetd运行我的程序(echo "test" | nc localhost 7),则使用BlockBuffering Nothing,但是如果我从cli运行,则使用LineBuffering

  • 就缓冲而言,xinetd tcp服务和cli程序有什么区别?
  • 如果我想编写同时适用于两种运行方法的工作程序,是否需要手动设置缓冲?

编辑:感谢所有提供有用答案的人。

我接受blaze给出的答案,他提示我使用isatty(3)。我重新查看了System.IO文档,并找到了hIsTerminalDevice函数,通过它我能够检查句柄的连接情况。

记录一下,这是我的最终程序:

{-# OPTIONS_GHC -W #-}

import System.IO
 
main = do
  hSetBuffering stdin LineBuffering
  hSetBuffering stdout LineBuffering
  
  interact revLines
 
revLines = unlines . map (reverse) . lines 

2
你不需要在这里使用forever。由于惰性,interact将继续等待输入,并且对于从stdin读取的每一行完整数据,它的倒序都会立即被打印出来。 - tempestadept
我认为这是与操作系统相关的,但通常如果您有一个面向行的程序,那么使用LineBuffering可能会得到最佳效果。 - J. Abrahamson
6
我经常抱怨有些问题很糟糕,所以我想表扬一下你。这是一个很棒的问题。代码简洁,提供了明确的重现问题的指令,清晰地证明了你已经在自己的问题上做出了一些进展,并且对于继续进展所需要知道的内容进行了简明的描述。非常好。 - Daniel Wagner
2个回答

12

这并不是Haskell特有的(例如标准C库也会这样做)。 传统上,如果文件描述符对应终端,则缓冲设置为行模式,否则设置为块模式。 可以通过isatty(3)函数检查文件描述符类型,但不确定是否导出到System.IO

是的,如果你依赖于它,你需要手动设置缓冲模式。

顺便说一下,你可以通过像cat | ./prog | cat这样在命令行中强制块缓冲来欺骗系统。


6
GHC运行时系统在选择默认缓冲时会尽力判断。如果看起来stdin和stdout直接连接到终端,则采用行缓冲。如果看起来它们连接到其他内容,则采用块缓冲。如果您想运行一个需要逐行输入但不直接来自终端的程序,则可能会出现问题。例如,我认为cat | your-programyour-program的行为不同。

如果我想编写一个可以同时使用两种运行方法的工作程序,我是否需要手动设置缓冲?

是的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接