6

バックグラウンド

バイナリネットワークプロトコル用のクライアントを作成しようとしています。すべてのネットワーク操作は単一のTCP接続を介して実行されるため、その意味で、サーバーからの入力はバイトの連続ストリームです。ただし、アプリケーション層では、サーバーは概念的にストリーム上でパケットを送信し、クライアントは、パケットが完全に受信されたことを認識するまで読み取りを続けてから、独自の応答を送信します。

この作業を行うために必要な多くの作業には、Data.Serializeモジュールを使用しているバイナリデータの解析と生成が含まれます。

問題

サーバーはTCPストリームで「パケット」を送信します。パケットは必ずしも改行で終了する必要はなく、所定のサイズでもありません。これ所定の数のフィールドで構成されており、フィールドは通常、そのフィールドの長さを表す4バイトの数字で始まります。Data.Serializeの助けを借りて、このパケットのByteStringバージョンをより管理しやすいタイプに解析するためのコードをすでに持っています。

これらのプロパティを使用してコードを記述できるようにしたいと思います。

  1. 解析は1回だけ定義され、できれば私のSerializeインスタンスで定義されます。正しいバイト数を読み取るために、IOモナドで余分な解析を実行したくありません。
  2. 特定のパケットを解析しようとして、まだすべてのバイトが到着していない場合、レイジーIOは余分なバイトが到着するのを待つだけです。
  3. 逆に、特定のパケットを解析しようとして、そのすべてのバイト到着した場合、IOはもうブロックしません。つまり、サーバーからストリームを十分に読み取って、型を解析し、応答を作成して送り返したいと考えています。タイプを解析するのに十分なバイト数が到着した後でもIOがブロックされると、クライアントとサーバーがデッドロックになり、それぞれが他方からのデータを待機します。
  4. 自分の応答を送信した後、サーバーから期待する次のタイプのパケットを解析することで、プロセスを繰り返すことができます。

簡単に言う と、現在のByteString解析コードをレイジーIOと組み合わせて利用して、ネットワークから正確に正しいバイト数を読み取ることは可能ですか?

私が試したこと

次のように、怠惰なByteStreamsをData.Serializeインスタンスと組み合わせて使用​​しようとしました。

import Network
import System.IO
import qualified Data.ByteString.Lazy as L
import Data.Serialize

data MyType

instance Serialize MyType

main = withSocketsDo $ do
  h <- connectTo server port
  hSetBuffering h NoBuffering
  inputStream <- L.hGetContents h
  let Right parsed = decodeLazy inputStream :: Either String MyType
  -- Then use parsed to form my own response, then wait for the server reply...

これは、上記のポイント3でほとんど失敗するようです。つまり、MyTypeを解析するのに十分なバイト数が到着した後でも、ブロックされたままになります。これは、ByteStringが一度に指定されたブロックサイズで読み取られL.hGetContents、このブロックの残りが到着するのを待っているためだと強く思います。効率的なブロックサイズを読み取るこのプロパティは、ディスクから効率的に読み取るのに役立ちますが、データを解析するのに十分なバイトを読み取るのに邪魔になっているようです。

4

1 に答える 1

7

パーサーに問題があります。熱心すぎます。ほとんどの場合、何らかの理由でメッセージの次のバイトが必要です。hGetContentsfrombytestringは、チャンク全体の待機をブロックしません。hGetSome内部で使用します。

簡単なテストケースを作成しました。サーバーは毎秒「hello」を送信します。

import Control.Concurrent
import System.IO
import Network

port :: Int
port = 1234

main :: IO ()
main = withSocketsDo $ do
  s <- listenOn $ PortNumber $ fromIntegral port
  (h, _, _) <- accept s

  let loop :: Int -> IO ()
      loop 0 = return ()
      loop i = do
        hPutStr h "hello"
        threadDelay 1000000
        loop $ i - 1
  loop 5

  sClose s

クライアントはコンテンツ全体を怠惰に読みます。

import qualified Data.ByteString.Lazy as BSL
import System.IO
import Network

port :: Int
port = 1234

main :: IO ()
main = withSocketsDo $ do
  h <- connectTo "localhost" $ PortNumber $ fromIntegral port
  bs <- BSL.hGetContents h
  BSL.putStrLn bs
  hClose h

その両方を実行しようとすると、クライアントが毎秒「hello」を出力するのがわかります。したがって、ネットワークサブシステムは問題ありません。問題は別の場所にあります。おそらく、パーサーにあります。

于 2013-03-12T08:29:10.047 に答える