2013-03-12 54 views
6

背景

我想爲二進制網絡協議編寫客戶端。 所有的網絡操作都是通過一個TCP連接來完成的,所以從這個意義上說,服務器的輸入是連續的字節流。然而,在應用層,服務器概念上在 流上發送數據包,並且在發送其自己的響應之前,客戶端繼續讀取,直到它知道數據包完整地收到了 。在Haskell中混合ByteString解析和網絡IO

做這項工作需要付出很多努力,包括解析和生成二進制數據,爲此我使用Data.Serialize模塊。

問題

服務器發送給我一個TCP數據包上的「數據包」。 數據包不一定以換行符結尾,也不是預定的大小。 它確實由預定數量的字段組成,並且字段通常以描述該字段長度的4字節數字開始 。 在Data.Serialize的一些幫助下,我已經有了將此數據包的ByteString 版本解析爲更易於管理的類型的代碼。

我很想能夠寫一些代碼,使這些屬性:

  1. 解析只被定義一次,最好在我的序列化實例(S)。 我寧願不要在IO單元中做額外的解析來讀取正確的字節數。
  2. 當我試圖解析一個給定的數據包並且並不是所有的字節都已經到達時,IO只會等待額外的字節到達。
  3. 相反,當我嘗試解析給定的數據包並且其所有字節已到達 IO不再阻止。也就是說,我想從服務器讀取足夠的流 來解析我的類型並形成回送。如果IO 即使在足夠的字節到達以解析我的類型之後仍然阻塞,那麼客戶端 和服務器將變爲死鎖,每個等待來自另一個的更多數據。
  4. 發送我自己的回覆後,我可以通過解析我期望從服務器獲得的數據包的下一個類型 來重複此過程。

所以簡而言之, 是否有可能利用我的當前字節字符串解析代碼組合 懶IO讀取準確斷網字節的正確數量?

我已經試過

我試圖用結合懶字節流與我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,它仍然保持阻塞狀態。我強烈懷疑這是因爲 ByteStrings一次讀取給定的塊大小,並且L.hGetContents是 正在等待此塊的其餘部分到達。儘管讀取 高效塊大小的這一屬性有助於從磁盤進行高效讀取,但似乎爲了讀取恰好足夠的字節來分析數據,我的方式是 。

回答

7

解析器出現問題,它太渴望了。出於某種原因,它很可能需要消息後面的下一個字節。 hGetContentsbytestring不會阻止等待整個塊。它在內部使用hGetSome

我創建了簡單的測試用例。服務器發送「你好」每一秒:

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 

如果你試圖同時運行的話,你會看到客戶端打印「你好」每秒鐘。所以,網絡子系統是好的,這個問題在別的地方 - 很可能在你的解析器中。

+0

+1非常好的和令人信服的測試用例。奇怪的是,它不是我在數據包末尾的額外字節,而是中間的一個字段,似乎有問題。 – 2013-03-13 02:31:23