我是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:资源消失(断管)。”谢谢你的帮助。
答案 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 10
。 take 10
将尝试评估列表的前10个元素并返回它们,如果列表中的元素少于10个,则只返回所有元素。在take 10
之后,我们putStrLn
和unlines
都与懒惰完全兼容。
现在让我们说客户端发送的输入只有6行,然后开始等待响应。我们的服务器懒洋洋地接收内容并尝试打印前10行。首先,take 10
函数愉快地消耗前6行并将它们传递给putStrLn . unlines
,然后会发生什么? take 10
无法完成输出,因为绝对没有迹象表明它已经结束了。句柄仍处于打开状态,字节仍然可以从客户端浮动到服务器,因此它只是等待更多输入。
运行以下内容可以观察到此行为:
nc localhost 5002
并手动输入10行。输入将在您键入时逐行显示在服务器上。键入第10行后,服务器将以#34; Hello&#34;消息。
P.S:我想你所描述的行为是因为你的网页浏览器发送了6到9行的请求。
要测试,调试和分析此类低级别服务器,您应使用nc
和curl
之类的简单工具,而不是网络浏览器。
答案 1 :(得分:2)
当您在句柄上启动惰性读取时,您放弃了对句柄执行任何其他操作的权限,直到内容字符串被完全强制,或者您手动关闭句柄(此时尝试强制执行此操作)内容字符串将导致错误行为或错误。)
这不是懒惰I / O适合的情况。适合在套接字上进行惰性读取的情况可能会在零指上计算。如果您愿意,可以使用常规严格I / O,或者conduit
或pipes
,或者像Yesod或Scotty或其他竞争对手那样的Haskell Web框架。
答案 2 :(得分:0)
调用hGetContents
会使句柄处于“半封闭”状态。在该点之后,您不应对句柄执行任何操作。您应该只使用从hGetContents
返回的字符串。
简单地说,不要在这里使用懒惰的I / O.您需要一次一个地手动读取和写入单个字符串,因为时间很重要。
一般来说,懒惰的I / O有点整洁,但除了玩具示例之外,它还不能很好地工作。