1

有一次,我在 haskell 中编写了一个数据包捕获程序,它使用惰性 IO 捕获所有 tcp 数据包。问题是有时数据包是无序的,所以我必须将它们全部插入一个列表,直到我得到一个 fin 标志,以确保我拥有对它们做任何事情所需的所有数据包,如果我正在嗅探某些东西真的很大,就像一个视频,我不得不记住所有这些。要做到这一点,任何其他方式都需要一些困难的命令式代码。

所以后来我了解了迭代器,我决定实现自己的。它是如何工作的,有一个枚举对象。您向它提供您希望它保存的数据包数量。当它拉入数据包时,它会对它们进行排序,然后一旦达到您指定的数量,它就会开始刷新,但会在其中留下一些,以便在刷新更多数据包之前将新块排序到该列表中。这个想法是,在遇到这个枚举对象之前,块几乎是有序的,它将解决大多数小订单问题。当它收到 EOF 时,它应该将所有剩余的数据包发送回去。

所以它几乎可以工作。我意识到其中一些可以被标准枚举器函数替换,但我想自己编写它们以了解它如何更好地工作。这是一些代码:

Readlines 只是一次一行地从文件中获取行并提供它。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

我的问题是未定义的重新排序。发生的情况是 reorder 有 10 个项目卡在其中,然后它从堆栈中接收到 EOF。所以它去了 k (Chunks those10items) 然后有一个未定义的,因为我不知道放在这里让它工作。

发生的情况是最后 10 项从程序的输出中删除。您可以看到跟踪,该变量 buf 包含所有剩余的项目。我尝试过让步,但我不确定该让步或是否应该让步。我不确定要放什么来完成这项工作。

编辑:原来通过将循环的未定义部分更改为:

loop buf n' EOF = k (Chunks buf) >>== (\s -> yield s EOF)

我几乎肯定曾经有过,但我没有得到正确的答案,所以我认为这是错误的。

问题出在 printLines 上。由于 reorder 一次发送一个块,直到它到达最后,我从未注意到 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

现在它可以工作了。非常感谢,我怕我得不到答案。

4

1 回答 1

1

怎么样

loop buf n' (EOF) = k (Chunks buf) >>== (\s -> yield s EOF)

(想法取自 EB.isolate)。

根据您要执行的操作,您的 printLines 可能还需要修复;Chunks (x:xs) 的情况丢弃了 xs。就像是

loop (Chunks (x:xs)) = do
  lift $ print x
  loop (Chunks xs)

可能(或可能不是)是您想要的。

于 2011-04-21T20:44:15.407 回答