2010-11-11 52 views
19

所以我正在写一个数据包嗅探应用程序。基本上我希望它侦听tcp会话,然后解析它们,看它们是否是http,如果它们是,以及它们是否具有正确的内容类型等,则将它们保存为我的硬盘上的文件。Attoparsec在大量的'take'呼叫中分配一吨内存

所以,为此,我希望它是有效的。由于当前的http库是基于字符串的,我将处理大文件,而且我只需要解析http响应,所以我决定在attoparsec中推出自己的文件。

当我完成我的程序时,我发现当我解析一个9兆字节的http响应时,它带有一个wav文件,当我剖析它时,它正在分配一段内存,当它试图解析出身体的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的地方,采取的是所谓的1201次,造成500+(+++)字节串的级联,这会导致荒谬的内存分配量。

这是代码。 N只是http响应的内容长度,如果有的话。如果没有一个它只是试图采取一切。

我希望它返回一个包含1000个左右字符字节串的惰性字节串,但即使我将它改为只取n并返回一个严格的字节串,它仍然具有这些分配(并且它使用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) 

我正在读那个combinatorrent的人的博客,他有同样的问题,但我从来没有听说过一个决议。有没有人曾经遇到过这个问题或找到解决方案?

编辑:好的,那么我整整一天都离开了这里,一无所有。在研究了这个问题之后,我认为没有一种方法可以在不向attoparsec添加惰性字节串访问器的情况下实现。我还查看了所有其他库,它们缺少字节串或其他东西。

所以我找到了一个解决方法。如果你考虑一个http请求,它会去头,换行符,换行符,正文。由于主体是最后一个,并且解析返回一个包含你解析的和剩余字节串的元组,所以我可以跳过解析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数据结构的字符串进行折叠,然后在集合之间检查刚刚获得的结构的内容长度,从剩余的字符串中提取适当的数量,然后在剩下任何字符串时继续操作。

编辑:我实际完成了这个项目。奇迹般有效。我没有妥善封闭,但如果有人想查看整个资料来源,你可以在https://github.com/onmach/Audio-Sniffer找到它。

+0

什么是AC? (填充) – 2010-11-11 21:39:12

+0

使用[blaze-builder的碎片整理操作](http://lambda-view.blogspot.com/2010/11/defragmenting-lazy-bytestrings.html)是否解决了这个问题? – 2010-11-12 18:30:45

+0

AC适用于Data.Attoparsec.Char8 – 2010-11-13 02:05:08

回答

5

combinatorrent这里的人:)

如果没记错,与attoparsec的问题是,需要输入一点点的时间,建立一个懒惰的字节串是最终连接在一起。我的“解决方案”是自己滚动输入功能。也就是说,我从网络套接字获取attoparsec的输入流,并且知道消息中需要多少字节。基本上,我分成两种情况:

  • 的消息是小:读了从插座到4K,吃在某时刻字节串一点点(字节串的切片快,我们扔掉后的4K它已经耗尽)。

  • 该消息是“大”(这里的大意味着大约16千字节的bittorrent说):我们计算我们有4k块可以满足多少,然后我们只需要底层网络套接字来填充东西。现在有两个字节串,4k块的剩余部分和大块。他们拥有所有数据,所以将它们连接起来并解析它们就是我们所做的。

    您可能可以优化拼接步骤。

TL; DR版本:我在attoparsec之外处理它,然后手动循环以避免出现问题。

相关combinatorrent提交是fc131fe24,看到

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

的细节。