简体   繁体   English

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

[英]Why can't I use hPutStr after printing the result of hGetContents?

I'm new to stackoverflow so forgive me if I do something wrong. 我是stackoverflow的新手,请原谅我,如果我做错了什么。 I trying to understand how a simple server would work in Haskell. 我试图了解一个简单的服务器如何在Haskell中工作。 I think I'm missing something very simple or fundamental about how hGetContents works. 我想我错过了一些关于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

After running the program, I navigate via web browser to http://localhost:5002 . 运行程序后,我通过Web浏览器导航到http:// localhost:5002 The problem seems to be that, depending on how much I've parsed the handle contents, I eventually am unable to send a response. 问题似乎是,根据我解析了句柄内容的程度,我最终无法发送响应。 I'd like to be able to parse the request before I send a response. 我希望能够在发送回复之前解析请求。 I've commented in the code the cases that work and the cases that don't. 我在代码中评论了有效的案例和没有的案例。 Hoogle says that for hGetContents (lazy) the handle is "semi-closed" as it is being read. Hoogle说,对于hGetContents(懒惰),句柄在被阅读时是“半封闭的”。 Am I misunderstanding the laziness or should I consider the handle closed once I begin parsing its contents? 我是否误解了懒惰,或者一旦开始解析其内容,我应该考虑关闭句柄吗?

The error I get is "hPutChar: resource vanished (Broken pipe)." 我得到的错误是“hPutChar:资源消失(断管)。” Thanks for any help. 谢谢你的帮助。

I tried to reproduce your problem. 我试图重现你的问题。 For that I executed your code and send it a request using nc: 为此,我执行了您的代码并使用nc向它发送请求:

printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11" | nc localhost 5002

As expected the server (code from your question) printed out first 10 lines and exited without any error. 正如预期的那样,服务器(来自您的问题的代码)打印出前10行并退出而没有任何错误。 The client (nc) printed: 客户(nc)打印:

HTTP/1.0 200 OK
Content-Length: 5
Hello!

and also exited without an error. 并且也没有错误地退出。

So, at first I couldn't understand what's your problem, but then I tried to send a smaller request: 所以,起初我无法理解你的问题是什么,但后来我尝试发送一个较小的请求:

printf "1\n2\n3\n4\n5\n6\n" | nc localhost 5002

The server printed first 6 lines and didn't exit. 服务器打印前6行并没有退出。 The client also didn't exit, so I interrupted it with Ctrl-C and after that the server exited with "resource vanished" error. 客户端也没有退出,所以我用Ctrl-C打断它,之后服务器退出“资源消失”错误。

I took some thinking and it started making sense to me. 我采取了一些思考,它开始对我有意义。 I don't understand lazy IO too good, so if my explanation isn't clear or correct it would be helpful if someone with better understanding would improve it. 我不太了解懒惰的IO太好了,所以如果我的解释不清楚或不正确,如果有更好理解的人会改进它会有所帮助。

Let's follow your code. 让我们按照你的代码。 First: 第一:

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

You open a handle and read it's content. 你打开一个手柄并阅读它的内容。 Note that the handle is lazy and the content that you get is also lazy. 请注意,句柄是惰性的,您获得的内容也是惰性的。 When we say that something is lazy we mean that it can be passed around without being evaluated (it's often referred as 'call by name' vs 'call by value'). 当我们说某些东西是懒惰的时候,我们的意思是它可以在没有被评估的情况下传递(它通常被称为“按名称调用”和“按值调用”)。

Now: 现在:

putStrLn $ unlines $ take 10 $ lines c

Here it is, you pass your lazy, unevaluated content to another function take 10 . 在这里,你将懒惰的,未评估的内容传递给另一个函数take 10 take 10 will try to evaluate first 10 elements of a list and return them, if there are less than 10 elements in the list it would simply return all of them. take 10将尝试评估列表的前10个元素并返回它们,如果列表中的元素少于10个,它将简单地返回所有元素。 After take 10 we have putStrLn and unlines which both perfectly compatible with laziness. take 10我们putStrLnunlines ,它们都与懒惰完全兼容。

Now let's say that client sends an input that is only 6 lines long and then starts waiting for the respond. 现在让我们说客户端发送的输入只有6行,然后开始等待响应。 Our server lazily receives the content and tries to print first 10 lines. 我们的服务器懒洋洋地接收内容并尝试打印前10行。 First, take 10 function happily consumes the first 6 lines and passes them over to putStrLn . unlines 首先,快乐地take 10函数消耗前6行并将它们传递给putStrLn . unlines putStrLn . unlines , what happens then? putStrLn . unlines ,然后会发生什么? take 10 can't just finish it's output because there is absolutely no indication that it is the end. take 10不能只是完成它的输出,因为绝对没有迹象表明它是结束。 The handle is still open, bytes still can be floating from client to server, so it just waits for more input. 句柄仍然是打开的,字节仍然可以从客户端浮动到服务器,所以它只是等待更多的输入。

This behaviour can be observed by running: 运行以下命令可以观察到此行为:

nc localhost 5002

and manually typing there 10 lines. 并手动输入10行。 The input would appear on server line-by-line as you type. 输入将在您键入时逐行显示在服务器上。 After you will type the 10th line the server will respond with "Hello" message. 键入第10行后,服务器将以“Hello”消息响应。

PS: I guess that the behaviour that you described happens because you web browser sends 6 to 9 lines of something with the request. PS:我想你所描述的行为是因为你的网页浏览器发送了6到9行的请求。

To test, debug and analyze this kind of low level servers you should use simple tools like nc and curl instead of your web browser :) 要测试,调试和分析这种低级服务器,您应该使用简单的工具,如nccurl而不是Web浏览器:)

When you initiate a lazy read on a handle, you give up the right to do anything much else with the handle until the contents string is fully forced, or you close the handle manually (at which point attempting to force any more of the contents string will lead to bad behavior or an error). 当您在句柄上启动延迟读取时,您放弃了对句柄执行任何其他操作的权限,直到完全强制内容字符串,或者您手动关闭句柄(此时尝试强制执行更多内容字符串)会导致不良行为或错误)。

TL;DR TL; DR

This is not a situation where lazy I/O is appropriate. 这不是懒惰I / O适当的情况。 The situations where a lazy read on a socket is appropriate can probably be counted on zero fingers. 适合在套接字上进行惰性读取的情况可能会在零指上计算。 You can use regular strict I/O if you like, or conduit , or pipes , or some Haskell web framework like Yesod or Scotty or various other competitors. 如果您愿意,可以使用常规严格I / O,或conduit ,或pipes ,或者像Yesod或Scotty或其他各种竞争对手的某些Haskell Web框架。

Calling hGetContents puts the handle into a "semi-closed" state. 调用hGetContents会将句柄置于“半封闭”状态。 You should not perform any operations on the handle after that point. 在该点之后,您不应对句柄执行任何操作。 You should only use the string returned from hGetContents . 您应该只使用从hGetContents返回的字符串。

Put simply, don't use lazy I/O here. 简而言之,不要在这里使用懒惰的I / O. You need to manually read and write individual strings one at a time, since the timing matters. 您需要一次一个地手动读取和写入单个字符串,因为时间很重要。

In general, lazy I/O is kind of neat, but it doesn't work well for anything much beyond toy examples. 一般来说,懒惰的I / O有点整洁,但它不适用于玩具示例以外的任何东西。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 为什么不能将DownloadStringAsync的结果传递给变量? - Why can't I pass result of DownloadStringAsync to a variable? 为什么不能将“文件”用作http请求的“正文”? - Why can't I use a “file” as the “body” of an http request? 为什么我不能在 http package 在 Z5ACEBC7CB70DDBBAE074B0AC - Why can't I put a string in the http.post method after update http package in flutter 尽管发送了状态代码,为什么还会出现“错误:在发送标头后无法设置标头”? - Why do I get “Error: Can't set headers after they are sent” despite sending a status code? 为什么浏览器不能成功使用给定的cookie? - Why can't the browser successfully use the given cookie? 为什么在打印标题时在 CGI 脚本中使用 `\\n`? - Why do use `\n` in CGI script while printing header? 为什么我不能使用pyCurl发布到Django? - Why can't I POST to Django with pyCurl? 为什么我不能设置cookie和重定向? - Why can't I set a cookie and redirect? 为什么我无法在 Wireshark 中过滤 http? - Why can't I filter http in Wireshark? 为什么我不能通过Cloud9链接订阅Facebook Webhook? - Why I can't subscribe to the Facebook webhook with Cloud9 link?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM