为什么在打印hGetContents 的结果后不能使用hPutStr?
Why can't I use hPutStr after printing the result of hGetContents?
我是 Whosebug 的新手,所以如果我做错了什么请原谅我。我试图了解一个简单的服务器如何在 Haskell 中工作。我想我遗漏了一些关于 hGetContents 如何工作的非常简单或基本的东西。
import Network
import System.IO
main = withSocketsDo $ do
socket <- listenOn $ PortNumber 5002
(h, _, _) <- accept socket
c <- hGetContents h
-- putStrLn c -- doesn't work
-- putStrLn $ head $ lines c -- works!
-- putStrLn $ unlines $ take 2 $ lines c -- works!
-- putStrLn $ unlines $ take 3 $ lines c -- works!
-- putStrLn $ unlines $ take 6 $ lines c -- works!
putStrLn $ unlines $ take 10 $ lines c -- doesn't work
hPutStr h $ "HTTP/1.0 200 OK\r\nContent-Length: 5\r\n\r\nHello!\r\n"
hClose h
在 运行 程序之后,我通过网络浏览器导航到 http://localhost:5002。问题似乎是,根据我对句柄内容的解析程度,我最终无法发送响应。我希望能够在发送响应之前解析请求。我在代码中评论了有效的案例和无效的案例。 Hoogle 表示对于 hGetContents(惰性)句柄是 "semi-closed",因为它正在被读取。我是在误解懒惰还是在开始解析其内容后就应该认为句柄已关闭?
我得到的错误是"hPutChar: resource vanished (Broken pipe)."感谢您的帮助。
当您在句柄上启动惰性读取时,您将放弃对句柄执行任何其他操作的权利,直到内容字符串被完全强制执行,或者您手动关闭句柄(此时尝试强制任何更多内容字符串将导致不良行为或错误)。
TL;DR
这种情况不适合偷懒I/O。在套接字上进行惰性读取是合适的情况可能可以用零个手指来计算。如果愿意,您可以使用常规的严格 I/O,或 conduit
,或 pipes
,或一些 Haskell 网络框架,如 Yesod 或 Scotty 或其他各种竞争对手。
我试图重现你的问题。为此,我执行了你的代码并使用 nc:
向它发送了一个请求
printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11" | nc localhost 5002
正如预期的那样,服务器(您问题中的代码)打印出前 10 行并退出,没有任何错误。客户端(nc)打印:
HTTP/1.0 200 OK
Content-Length: 5
Hello!
并且也没有错误退出。
所以,起初我不明白你的问题是什么,但后来我尝试发送一个较小的请求:
printf "1\n2\n3\n4\n5\n6\n" | nc localhost 5002
服务器打印了前 6 行,但没有退出。客户端也没有退出,所以我用 Ctrl-C 中断了它,之后服务器退出并出现 "resource vanished" 错误。
我想了想,它开始对我有意义。我不太了解lazy IO,所以如果我的解释不清楚或不正确,如果有更好理解的人会改进它会有所帮助。
让我们按照您的代码进行操作。第一:
(h, _, _) <- accept socket
c <- hGetContents h
您打开句柄并阅读其内容。注意句柄是惰性的,得到的内容也是惰性的。当我们说某些东西是惰性的时,我们的意思是它可以在不被 评估 的情况下传递(它通常被称为 'call by name' 与 'call by value')。
现在:
putStrLn $ unlines $ take 10 $ lines c
在这里,您将惰性的、未评估的内容传递给另一个函数 take 10
。 take 10
将尝试 评估 列表的前 10 个元素和 return 它们,如果列表中的元素少于 10 个,它会简单地 return 他们都。在 take 10
之后,我们有 putStrLn
和 unlines
,它们都与懒惰完美兼容。
现在假设客户端发送了一个只有 6 行长的输入,然后开始等待响应。我们的服务器延迟接收内容并尝试打印前 10 行。首先,take 10
函数愉快地使用前 6 行并将它们传递给 putStrLn . unlines
,然后会发生什么? take 10
不能只是完成它的输出,因为绝对没有迹象表明它已经结束。句柄仍然打开,字节仍然可以从客户端浮动到服务器,所以它只是等待更多的输入。
此行为可以通过 运行:
观察到
nc localhost 5002
并在那里手动输入 10 行。输入将在您键入时逐行显示在服务器上。在您键入第 10 行之后,服务器将响应 "Hello" 消息。
P.S:我猜你描述的行为的发生是因为你的网络浏览器随请求发送了 6 到 9 行内容。
要测试、调试和分析这种低级服务器,您应该使用像 nc
和 curl
这样的简单工具,而不是您的网络浏览器:)
调用 hGetContents
将句柄置于 "semi-closed" 状态。在那之后,您不应该对句柄执行 any 操作。您应该只使用从 hGetContents
.
返回的字符串
简单地说,不要在这里使用惰性I/O。您需要一次一个地手动读取和写入单个字符串,因为时间很重要。
总的来说,lazy I/O 有点简洁,但它不适用于玩具示例以外的任何东西。
我是 Whosebug 的新手,所以如果我做错了什么请原谅我。我试图了解一个简单的服务器如何在 Haskell 中工作。我想我遗漏了一些关于 hGetContents 如何工作的非常简单或基本的东西。
import Network
import System.IO
main = withSocketsDo $ do
socket <- listenOn $ PortNumber 5002
(h, _, _) <- accept socket
c <- hGetContents h
-- putStrLn c -- doesn't work
-- putStrLn $ head $ lines c -- works!
-- putStrLn $ unlines $ take 2 $ lines c -- works!
-- putStrLn $ unlines $ take 3 $ lines c -- works!
-- putStrLn $ unlines $ take 6 $ lines c -- works!
putStrLn $ unlines $ take 10 $ lines c -- doesn't work
hPutStr h $ "HTTP/1.0 200 OK\r\nContent-Length: 5\r\n\r\nHello!\r\n"
hClose h
在 运行 程序之后,我通过网络浏览器导航到 http://localhost:5002。问题似乎是,根据我对句柄内容的解析程度,我最终无法发送响应。我希望能够在发送响应之前解析请求。我在代码中评论了有效的案例和无效的案例。 Hoogle 表示对于 hGetContents(惰性)句柄是 "semi-closed",因为它正在被读取。我是在误解懒惰还是在开始解析其内容后就应该认为句柄已关闭?
我得到的错误是"hPutChar: resource vanished (Broken pipe)."感谢您的帮助。
当您在句柄上启动惰性读取时,您将放弃对句柄执行任何其他操作的权利,直到内容字符串被完全强制执行,或者您手动关闭句柄(此时尝试强制任何更多内容字符串将导致不良行为或错误)。
TL;DR
这种情况不适合偷懒I/O。在套接字上进行惰性读取是合适的情况可能可以用零个手指来计算。如果愿意,您可以使用常规的严格 I/O,或 conduit
,或 pipes
,或一些 Haskell 网络框架,如 Yesod 或 Scotty 或其他各种竞争对手。
我试图重现你的问题。为此,我执行了你的代码并使用 nc:
向它发送了一个请求printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11" | nc localhost 5002
正如预期的那样,服务器(您问题中的代码)打印出前 10 行并退出,没有任何错误。客户端(nc)打印:
HTTP/1.0 200 OK
Content-Length: 5
Hello!
并且也没有错误退出。
所以,起初我不明白你的问题是什么,但后来我尝试发送一个较小的请求:
printf "1\n2\n3\n4\n5\n6\n" | nc localhost 5002
服务器打印了前 6 行,但没有退出。客户端也没有退出,所以我用 Ctrl-C 中断了它,之后服务器退出并出现 "resource vanished" 错误。
我想了想,它开始对我有意义。我不太了解lazy IO,所以如果我的解释不清楚或不正确,如果有更好理解的人会改进它会有所帮助。
让我们按照您的代码进行操作。第一:
(h, _, _) <- accept socket
c <- hGetContents h
您打开句柄并阅读其内容。注意句柄是惰性的,得到的内容也是惰性的。当我们说某些东西是惰性的时,我们的意思是它可以在不被 评估 的情况下传递(它通常被称为 'call by name' 与 'call by value')。
现在:
putStrLn $ unlines $ take 10 $ lines c
在这里,您将惰性的、未评估的内容传递给另一个函数 take 10
。 take 10
将尝试 评估 列表的前 10 个元素和 return 它们,如果列表中的元素少于 10 个,它会简单地 return 他们都。在 take 10
之后,我们有 putStrLn
和 unlines
,它们都与懒惰完美兼容。
现在假设客户端发送了一个只有 6 行长的输入,然后开始等待响应。我们的服务器延迟接收内容并尝试打印前 10 行。首先,take 10
函数愉快地使用前 6 行并将它们传递给 putStrLn . unlines
,然后会发生什么? take 10
不能只是完成它的输出,因为绝对没有迹象表明它已经结束。句柄仍然打开,字节仍然可以从客户端浮动到服务器,所以它只是等待更多的输入。
此行为可以通过 运行:
观察到nc localhost 5002
并在那里手动输入 10 行。输入将在您键入时逐行显示在服务器上。在您键入第 10 行之后,服务器将响应 "Hello" 消息。
P.S:我猜你描述的行为的发生是因为你的网络浏览器随请求发送了 6 到 9 行内容。
要测试、调试和分析这种低级服务器,您应该使用像 nc
和 curl
这样的简单工具,而不是您的网络浏览器:)
调用 hGetContents
将句柄置于 "semi-closed" 状态。在那之后,您不应该对句柄执行 any 操作。您应该只使用从 hGetContents
.
简单地说,不要在这里使用惰性I/O。您需要一次一个地手动读取和写入单个字符串,因为时间很重要。
总的来说,lazy I/O 有点简洁,但它不适用于玩具示例以外的任何东西。