所以我正在写一个数据包嗅探应用。基本上,我希望它嗅探TCP会话,然后解析它们是否是HTTP,以及是否是HTTP,以及它们是否具有正确的内容类型等,请将它们保存为我的硬盘驱动器上的文件。

因此,为此,我希望它能有效。由于当前的HTTP库是基于字符串的,我将处理大型文件,而且我只需要解析HTTP响应,因此我决定在Attoparsec中滚动。

当我完成程序时,我发现当我用其中的WAV文件解析9兆HTTP响应时,当我对其进行介绍时,当它试图解析HTTP响应的正文时,它正在分配记忆的演出。当我查看http.prof时,我会看到一些行:

httpBody              Main                                                 362           1   0.0    0.0    93.8   99.3

 take                 Data.Attoparsec.Internal                             366        1201   0.0    0.0    93.8   99.3
     takeWith            Data.Attoparsec.Internal                             367        3603   0.0    0.0    93.8   99.3
      demandInput        Data.Attoparsec.Internal                             375         293   0.0    0.0    93.8   99.2
       prompt            Data.Attoparsec.Internal                             378         293   0.0    0.0    93.8   99.2
        +++              Data.Attoparsec.Internal                             380         586  93.8   99.2    93.8   99.2

因此,如您所见,在HTTPBody中的某个地方,Take称为1201次,导致500+(+++)字节串联,这会导致荒谬的内存分配。

这是代码。 n只是HTTP响应的内容长度,如果有。如果没有一个,它只是试图拿走一切。

我希望它能返回1000个左右字节的懒惰字节,但是即使我将其更改为n键并返回严格的bytestring,它仍然包含这些分配(并且使用14次记忆)。


httpBody n = do
  x <- if n > 0
    then AC.take n
    else AC.takeWhile (\_ -> True)
  if B.length x == 0
    then return Nothing
    else return (Just x)

我正在读这位做过组合的家伙的博客,他也遇到了同样的问题,但我从未听说过解决方案。有没有人以前遇到过这个问题或找到解决方案?

编辑:好吧,我一整天都离开了,什么也没得到。在研究了这个问题之后,我认为如果不在Attoparsec添加懒惰的bytestring访问者,就没有办法这样做。我还查看了所有其他图书馆,他们要么缺少字节或其他东西。

所以我找到了解决方法。如果您考虑HTTP请求,它将进入标题,Newline,Newline,Body。由于身体是最后的,并且解析既有元组,既可以用您解析的内容和剩下的bytestring,所以我可以跳过Attoparsec内部的身体,而是将身体直接从剩下的近测试中拔下。


parseHTTPs bs = if P.length results == 0
  then Nothing
  else Just results
  where results = foldParse(bs, [])

foldParse (bs,rs) = case ACL.parse httpResponse bs of
  ACL.Done rest r -> addBody (rest,rs) r
  otherwise ->  rs

addBody (rest,rs) http = foldParse (rest', rs')
  where
    contentlength = ((read . BU.toString) (maybe "0" id (hdrContentLength (rspHeaders http))))
    rest' = BL.drop contentlength rest
    rs' = rs ++ [http { rspBody = body' }]
    body'
      | contentlength == 0  = Just rest
      | BL.length rest == 0 = Nothing
      | otherwise           = Just (BL.take contentlength rest)
httpResponse = do
  (code, desc) <- statusLine
  hdrs <- many header
  endOfLine
--  body <- httpBody ((read . BU.toString) (maybe "0" id (hdrContentLength parsedHeaders)))

  return Response { rspCode = code, rspReason = desc, rspHeaders = parseHeaders hdrs,  rspBody = undefined }

这有点混乱,但最终它运作速度很快,只能分配我想要的。因此,基本上,您将折叠在收集HTTP数据结构的bytestring上,然后在收集之间,我检查了我刚刚获得的结构的内容长度,从剩余的bytestring中提取适当的数量,然后在剩下任何bytestring剩下的情况下继续进行。

编辑:我实际上完成了这个项目。奇迹般有效。我没有正确地被划定,但是如果有人想查看整个来源,您可以在 https://github.com/onmach/audio-sniffer.

有帮助吗?

解决方案

在这里组合的家伙:)

如果记忆有用,AttoparSec的问题是一次要求输入一点,建立一个懒惰的bytestring,最终被串联。我的“解决方案”是自己滚动输入函数。也就是说,我从网络插座获得了attoparsec的输入流,我知道消息中会有多少个字节。基本上,我分为两种情况:

  • 消息很小:从插座上读取多达4K,并且一次稍微吃一点(串行的切片很快,我们耗尽了4K)。

  • 消息是“大”(这里大的表示Bittorrent的大约16千键):我们计算我们可以实现的4K块,然后我们只是要求基础网络插座填充物品。我们现在有两个字节,有两个字节, 4K块和大块的其余部分。他们拥有所有数据,因此我们要做的是加入这些数据并将其解析。

    您也许可以优化串联步骤。

TL; DR版本:我在Attoparsec外处理并掌握循环以避免问题。

相关的组合提交是FC131FE24,请参阅

https://github.com/jlouis/combinatorrent/commit/fc131fe24207909dd980c674aae6aaba27b966666d4

有关细节。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top