バックグラウンド
バイナリネットワークプロトコル用のクライアントを作成しようとしています。すべてのネットワーク操作は単一のTCP接続を介して実行されるため、その意味で、サーバーからの入力はバイトの連続ストリームです。ただし、アプリケーション層では、サーバーは概念的にストリーム上でパケットを送信し、クライアントは、パケットが完全に受信されたことを認識するまで読み取りを続けてから、独自の応答を送信します。
この作業を行うために必要な多くの作業には、Data.Serializeモジュールを使用しているバイナリデータの解析と生成が含まれます。
問題
サーバーはTCPストリームで「パケット」を送信します。パケットは必ずしも改行で終了する必要はなく、所定のサイズでもありません。これは所定の数のフィールドで構成されており、フィールドは通常、そのフィールドの長さを表す4バイトの数字で始まります。Data.Serializeの助けを借りて、このパケットのByteStringバージョンをより管理しやすいタイプに解析するためのコードをすでに持っています。
これらのプロパティを使用してコードを記述できるようにしたいと思います。
- 解析は1回だけ定義され、できれば私のSerializeインスタンスで定義されます。正しいバイト数を読み取るために、IOモナドで余分な解析を実行したくありません。
- 特定のパケットを解析しようとして、まだすべてのバイトが到着していない場合、レイジーIOは余分なバイトが到着するのを待つだけです。
- 逆に、特定のパケットを解析しようとして、そのすべてのバイトが到着した場合、IOはもうブロックしません。つまり、サーバーからストリームを十分に読み取って、型を解析し、応答を作成して送り返したいと考えています。タイプを解析するのに十分なバイト数が到着した後でもIOがブロックされると、クライアントとサーバーがデッドロックになり、それぞれが他方からのデータを待機します。
- 自分の応答を送信した後、サーバーから期待する次のタイプのパケットを解析することで、プロセスを繰り返すことができます。
簡単に言う と、現在の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
、このブロックの残りが到着するのを待っているためだと強く思います。効率的なブロックサイズを読み取るこのプロパティは、ディスクから効率的に読み取るのに役立ちますが、データを解析するのに十分なバイトを読み取るのに邪魔になっているようです。