ある時点で、私はhaskellでパケットキャプチャプログラムを作成し、レイジーIOを使用してすべてのtcpパケットをキャッチしました。問題は、パケットが故障していることがあるため、finフラグを取得するまで、すべてのパケットをリストに挿入して、パケットを処理するために必要なすべてのパケットがあることを確認する必要がありました。ビデオのように本当に大きいので、私はそれらすべてをメモリに保持しなければなりませんでした。他の方法でそれを行うには、いくつかの難しい命令型コードが必要になります。
それで後で私は反復について学びました、そして私は自分自身を実装することに決めました。それがどのように機能するかというと、列挙者がいます。保持したいパケット数を指定します。パケットをプルすると、それらをソートし、指定した数に達するとフラッシュを開始しますが、そこにいくつかを残して、新しいチャンクがそのリストにソートされてから、さらにパケットがフラッシュされるようにします。アイデアは、チャンクがこの列挙子に到達する前にほぼ順番になり、ほとんどの小さな順序の問題を修正するというものです。EOFを取得すると、残りのすべてのパケットを送り返す必要があります。
だからそれはほとんど動作します。これらのいくつかは標準の列挙関数に置き換えることができると思いますが、それがどのように機能するかを理解するために自分で書きたかったのです。ここにいくつかのコードがあります:
Readlinesは、ファイルから一度に1行ずつ行を取得し、それをフィードします。PrintLinesは、各チャンクを印刷するだけです。numbers.txtは、行で区切られた数字のセットで、順序が少しずれています。一部の数字は、本来あるべき前後にいくつかのスペースがあります。Reorderは、n個の数値を保持し、新しい数値をアキュムレータリストに並べ替えてから、それらの数値の最後のn個を除くすべてを押し出す関数です。
import Prelude as P
import Data.Enumerator as E
import Data.Enumerator.List as EL
import Data.List (sort, insert)
import IO
import Control.Monad.Trans (lift)
import Control.Monad (liftM)
import Control.Exception as Exc
import Debug.Trace
test = run_ (readLines "numbers.txt" $$ EL.map (read ::String -> Int) =$ reorder 10 =$ printLines)
reorder :: (Show a, Ord a) => (Monad m) => Int -> Enumeratee a a m b
reorder n step = reorder' [] n step
where
reorder' acc n (Continue k) =
let
len = P.length
loop buf n' (Chunks xs)
| (n' - len xs >= 0) = continue (loop (foldr insert buf xs) (n' - len xs))
| otherwise =
let allchunx = foldr insert buf xs
(excess,store)= P.splitAt (negate (n' - len xs)) allchunx
in k (Chunks excess) >>== reorder' store 0
loop buf n' (EOF) = k (Chunks (trace ("buf:" ++ show buf) buf)) >>== undefined
in continue (loop acc n)
printLines :: (Show a) => Iteratee a IO ()
printLines = continue loop
where
loop (Chunks []) = printLines
loop (Chunks (x:xs)) = do
lift $ print x
printLines
loop (EOF) = yield () EOF
readLines :: FilePath -> Enumerator String IO ()
readLines filename s = do
h <- tryIO $ openFile filename ReadMode
Iteratee (Exc.finally (runIteratee $ checkContinue0 (blah h) s) (hClose h))
where
blah h loop k = do
x <- lift $ myGetLine h
case x of
Nothing -> continue k
Just line -> k (Chunks [line]) >>== loop
myGetLine h = Exc.catch (liftM Just (hGetLine h)) checkError
checkError :: IOException -> IO (Maybe String)
checkError e = return Nothing
私の問題は、並べ替えの未定義にあります。何が起こるかというと、リオーダーには10個のアイテムがスタックしていて、スタックの上位からEOFを受け取ります。したがって、k(チャンクそれらの10アイテム)になり、それを機能させるためにここに何を置くべきかわからないため、未定義があります。
何が起こるかというと、最後の10項目がプログラムの出力から切り取られます。トレースを見ると、その変数bufには残りのすべてのアイテムが含まれています。私は譲歩を試みましたが、何を譲るのか、あるいは譲るべきかどうかがわかりません。この機能を実現するために何を配置すればよいかわかりません。
編集:ループの未定義部分を次のように変更することで、並べ替えが修正されたことがわかります。
loop buf n' EOF = k (Chunks buf) >>== (\s -> yield s EOF)
ある時点でほぼ間違いなく持っていたのですが、正しい答えが得られなかったので、間違っていると思いました。
問題はprintLinesにありました。リオーダーは最後までチャンクを1つずつ送信していたので、ループごとに最初のチャンク以外のチャンクを破棄するというprintLinesの問題に気付くことはありませんでした。私の頭の中で、私はチャンクが持ち越されるか何かを考えました、それは愚かでした。
とにかく私はprintLinesをこれに変更しました:
printLines :: (Show a) => Iteratee a IO ()
printLines = continue loop
where
loop (Chunks []) = printLines
loop (Chunks xs) = do
lift $ mapM_ print xs
printLines
loop (EOF) = yield () EOF
そして今、それは機能します。どうもありがとうございました、私は答えが得られないのではないかと心配していました。