为什么在打印hGetContents的结果后我不能使用hPutStr?

时间:2015-04-17 02:13:33

标签: http haskell network-programming

我是stackoverflow的新手,请原谅我,如果我做错了什么。我试图了解一个简单的服务器如何在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

运行程序后,我通过Web浏览器导航到http://localhost:5002。问题似乎是,根据我解析了句柄内容的程度,我最终无法发送响应。我希望能够在发送回复之前解析请求。我在代码中评论了有效的案例和没有的案例。 Hoogle说,对于hGetContents(懒惰),句柄在被阅读时是“半封闭的”。我是否误解了懒惰,或者一旦开始解析其内容,我应该考虑关闭句柄吗?

我得到的错误是“hPutChar:资源消失(断管)。”谢谢你的帮助。

3 个答案:

答案 0 :(得分:5)

我试图重现你的问题。为此,我执行了您的代码并使用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打断它,然后服务器退出,资源消失了#34;错误。

我做了一些思考,开始对我有意义。我不太了解懒惰的IO,所以如果我的解释不清楚或纠正,如果有更好理解的人会改进它会有所帮助。

让我们关注您的代码。第一:

(h, _, _) <- accept socket
c <- hGetContents h

您打开手柄并阅读其内容。请注意,句柄是惰性的,您获得的内容也是惰性的。当我们说某些东西是懒惰的时候,我们的意思是它可以传递而不被评估(它通常被称为&#39;呼叫名称&#39; vs&#39;呼叫按价值&#39;)。

现在:

putStrLn $ unlines $ take 10 $ lines c

在这里,您将懒惰的,未评估的内容传递给另一个函数take 10take 10将尝试评估列表的前10个元素并返回它们,如果列表中的元素少于10个,则只返回所有元素。在take 10之后,我们putStrLnunlines都与懒惰完全兼容。

现在让我们说客户端发送的输入只有6行,然后开始等待响应。我们的服务器懒洋洋地接收内容并尝试打印前10行。首先,take 10函数愉快地消耗前6行并将它们传递给putStrLn . unlines,然后会发生什么? take 10无法完成输出,因为绝对没有迹象表明它已经结束了。句柄仍处于打开状态,字节仍然可以从客户端浮动到服务器,因此它只是等待更多输入。

运行以下内容可以观察到此行为:

nc localhost 5002

并手动输入10行。输入将在您键入时逐行显示在服务器上。键入第10行后,服务器将以#34; Hello&#34;消息。

P.S:我想你所描述的行为是因为你的网页浏览器发送了6到9行的请求。

要测试,调试和分析此类低级别服务器,您应使用nccurl之类的简单工具,而不是网络浏览器。

答案 1 :(得分:2)

当您在句柄上启动惰性读取时,您放弃了对句柄执行任何其他操作的权限,直到内容字符串被完全强制,或者您手动关闭句柄(此时尝试强制执行此操作)内容字符串将导致错误行为或错误。)

TL; DR

这不是懒惰I / O适合的情况。适合在套接字上进行惰性读取的情况可能会在零指上计算。如果您愿意,可以使用常规严格I / O,或者conduitpipes,或者像Yesod或Scotty或其他竞争对手那样的Haskell Web框架。

答案 2 :(得分:0)

调用hGetContents会使句柄处于“半封闭”状态。在该点之后,您不应对句柄执行任何操作。您应该只使用从hGetContents返回的字符串。

简单地说,不要在这里使用懒惰的I / O.您需要一次一个地手动读取和写入单个字符串,因为时间很重要。

一般来说,懒惰的I / O有点整洁,但除了玩具示例之外,它还不能很好地工作。

相关问题