2013-03-12 15 views
6

Hintergrund

Ich versuche, ein Client für ein binäres Netzwerkprotokoll zu schreiben. Alle Netzwerkoperationen werden über eine einzige TCP-Verbindung ausgeführt, so dass in diesem Sinne die Eingabe vom Server ein kontinuierlicher Bytestrom ist. Auf der Anwendungsschicht jedoch sendet der Server konzeptionell ein Paket auf dem -Datenstrom, und der Client liest weiter, bis er das Paket in seiner Gesamtheit erhält, bevor er eine eigene Antwort sendet.Mixing ByteString Parsing und Netzwerk-IO in Haskell

Eine Menge Aufwand, um diese Arbeit zu machen, umfasst das Analysieren und Generieren von Binärdaten, für die ich das Data.Serialize-Modul verwende.

Das Problem

Der Server ein "Paket" auf dem TCP-Stream sendet mir. Das Paket wird nicht unbedingt durch eine neue Zeile beendet, noch ist es eine vorbestimmte Größe. Es does bestehen aus einer vorgegebenen Anzahl von Feldern, und Felder beginnen im Allgemeinen mit einer 4-Byte-Nummer, die die Länge dieses Feldes beschreibt. Mit etwas Hilfe von Data.Serialize, habe ich bereits den Code, um eine ByteString Version dieses Pakets in einen besser verwaltbaren Typ zu analysieren.

Ich würde gerne einige Codes mit diesen Eigenschaften in der Lage sein zu schreiben:

ist
  1. Das Parsen nur einmal definiert, vorzugsweise in meiner Serialize-Instanz (en). Ich würde lieber nicht extra analysieren in der IO-Monade, um die richtige Anzahl von Bytes zu lesen.
  2. Wenn ich versuche, ein gegebenes Paket zu analysieren und nicht alle Bytes sind noch angekommen, faul IO wird nur auf die zusätzlichen Bytes warten, um anzukommen. Im Gegensatz dazu
  3. , wenn ich versuche, ein bestimmtes Paket zu analysieren und alle Bytes haben angekommen IO nicht mehr blockieren. Das heißt, ich möchte gerade genug vom Stream vom Server lesen, um meinen Typ zu analysieren und eine Antwort zum Zurücksenden zu bilden. Wenn das IO blockiert, selbst nachdem genug Bytes angekommen sind, um meinen Typ zu parsen, werden der Client und der Server festgefahren, wobei jeder auf weitere Daten von dem anderen wartet.
  4. Nachdem ich meine eigene Antwort gesendet habe, kann ich den Vorgang wiederholen, indem ich den nächsten Typ des Pakets analysiere, das ich vom Server erwarte.

Also kurz gesagt, ist es möglich, meinen aktuellen ByteString Parsing-Code in Kombination mit faulem IO nutzt genau die richtige Anzahl von Bytes aus dem Netzwerk zu lesen?

Was habe ich versucht,

ich faul Bytestreams in Kombination mit meinem Data.Serialize Beispiel wie so zu nutzen versucht:

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... 

Dies scheint zu scheitern meist am Punkt 3 oben: Es bleibt blockiert, selbst nachdem eine ausreichende Anzahl von Bytes eingetroffen ist, um MyType zu parsen.Ich vermute stark, dass ByteStrings mit einer gegebenen Blockgröße zu einer Zeit gelesen werden, und L.hGetContents ist warten auf den Rest dieses Blocks zu kommen. Während diese Eigenschaft eine effiziente Blockgröße zu lesen ist hilfreich für die effiziente Lesevorgänge von der Festplatte scheint es bekommen in meine Art und Weise zum Lesen gerade genug Bytes, um meine Daten zu analysieren.

Antwort

7

Etwas stimmt nicht mit Ihrem Parser, es ist zu eifrig. Höchstwahrscheinlich benötigt es aus irgendeinem Grund das nächste Byte nach der Nachricht. hGetContents von bytestring blockiert nicht warten auf den ganzen Brocken. Es verwendet hGetSome intern.

Ich erstellte einfachen Testfall. Der Server sendet „Hallo“ jede Sekunde:

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 

Der Client liest der gesamte Inhalt lazily:

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 

Wenn Sie versuchen, beide dann laufen, werden Sie den Client-Druck „Hallo“ sehen jede Sekunde. Also, das Netzwerk-Subsystem ist in Ordnung, das Problem ist woanders - am wahrscheinlichsten in Ihrem Parser.

+0

+1 Sehr schöner und überzeugender Testfall. Seltsamerweise ist es kein zusätzliches Byte, das ich am Ende des Pakets erwarte, sondern ein Feld in der Mitte, das problematisch zu sein scheint ... –