19

だから私はパケット盗聴アプリを書いています。基本的には、tcp セッションをスニッフィングし、それらを解析して http であるかどうかを確認し、http であるかどうか、適切なコンテンツ タイプを持っているかどうかなどをハード ドライブにファイルとして保存したかったのです。

だから、そのためには、効率的にしたかったのです。現在の http ライブラリは文字列ベースであり、大きなファイルを扱うことになるため、本当に必要なのは http 応答を解析することだけだったので、attoparsec で独自のものを作成することにしました。

プログラムを終了したとき、wav ファイルを含む 9 メガの http 応答を解析しているときに、プロファイリングしたときに、http 応答の本文を解析しようとしているときに、1 ギガのメモリを割り当てていることがわかりました。 . HTTP.prof を見ると、いくつかの行が表示されます。

httpBody メイン 362 1 0.0 0.0 93.8 99.3

 Data.Attoparsec.Internal 366 1201 0.0 0.0 93.8 99.3 を取る
     takeWithData.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
       プロンプト 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 応答がある場合、そのコンテンツの長さです。1 つも存在しない場合は、すべてを取得しようとします。

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)

コンビナトレントをやっている人のブログを読んでいて、彼も同じ問題を抱えていましたが、解決策については聞いたことがありません。以前にこの問題に遭遇したり、解決策を見つけた人はいますか?

編集:さて、私はこれを一日中放置しましたが、何も得られませんでした. 問題を調査した後、遅延バイト文字列アクセサーを 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で見つけることができます。

4

1 に答える 1

5

ここにコンビナトレントの男:)

メモリが機能する場合、 attoparsec の問題は、一度に少しずつ入力を要求し、最終的に連結される遅延バイト文字列を構築することです。私の「解決策」は、入力関数を自分でロールすることでした。つまり、ネットワーク ソケットから attoparsec の入力ストリームを取得し、メッセージで期待されるバイト数を知っています。基本的に、次の 2 つのケースに分けます。

  • メッセージは小さいです: ソケットから最大 4k を読み取り、そのバイト文字列を一度に少しずつ消費します (バイト文字列のスライスは高速で、使い果たされた後は 4k を破棄します)。

  • メッセージは「大きい」です (大きいとは、bittorrent で言えば約 16 キロバイトを意味します): 4k のチャンクがどれだけ満たすことができるかを計算し、基盤となるネットワーク ソケットにそれを埋めるように要求するだけです。 4k チャンクの残りの部分と大きなチャンクです。それらにはすべてのデータがあるため、それらを連結して解析することが私たちの仕事です。

    連結ステップを最適化できる場合があります。

TL;DR バージョン: 問題を回避するために attoparsec の外で処理し、ループをハンドロールします。

関連するcombinatorrentコミットはfc131fe24です。

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

詳細については。

于 2010-12-05T21:46:06.873 に答える