2013-03-12 28 views
6

Bối cảnh

Tôi đang cố gắng viết ứng dụng khách cho giao thức mạng nhị phân. Tất cả các hoạt động mạng được thực hiện qua một kết nối TCP duy nhất, vì vậy theo nghĩa đó, đầu vào từ máy chủ là một luồng liên tục các byte. Tuy nhiên, ở tầng ứng dụng, máy chủ gửi gói tin trên luồng và khách hàng tiếp tục đọc cho đến khi biết gói tin đã nhận được toàn bộ, trước khi gửi phản hồi của chính nó.Trộn ByteString phân tích cú pháp và mạng IO trong Haskell

Rất nhiều nỗ lực cần thiết để thực hiện công việc này liên quan đến phân tích cú pháp và tạo ra dữ liệu nhị phân , mà tôi đang sử dụng mô-đun Data.Serialize.

Sự cố

Máy chủ gửi cho tôi một "gói" trên luồng TCP. Các gói tin không nhất thiết phải chấm dứt bởi một dòng mới, cũng không phải là một kích thước được xác định trước . Nó không bao gồm số lượng trường được xác định trước và các trường thường bắt đầu với số 4 byte mô tả độ dài của trường đó. Với một số trợ giúp từ Data.Serialize, tôi đã có mã để phân tích phiên bản ByteString của gói này thành một loại dễ quản lý hơn.

Tôi rất muốn để có thể viết một số mã với các đặc tính này:

  1. Các phân tích được chỉ định nghĩa một lần, tốt nhất là trong trường hợp của tôi Serialize (s). Tôi không muốn phân tích thêm trong đơn nguyên IO để đọc số byte chính xác.
  2. Khi tôi cố gắng phân tích cú pháp một gói nhất định và không phải tất cả các byte đã đến, lười biếng IO sẽ chỉ đợi các byte bổ sung đến.
  3. Ngược lại, khi tôi cố gắng phân tích cú pháp gói đã cho và tất cả các byte của nó đến IO không chặn nữa. Tức là, tôi muốn đọc đủ luồng từ máy chủ để phân tích cú pháp loại của tôi và tạo thành phản hồi để gửi lại. Nếu khối IO ngay cả sau khi đủ byte đã đến để phân tích cú pháp loại của tôi, thì máy khách và máy chủ sẽ bị bế tắc, mỗi máy sẽ đợi nhiều dữ liệu hơn từ bên kia.
  4. Sau khi gửi phản hồi của riêng mình, tôi có thể lặp lại quy trình bằng cách phân tích cú pháp loại tiếp theo của gói tôi mong đợi từ máy chủ.

Vì vậy, trong ngắn gọn, là nó có thể tận dụng đang phân tích cú pháp ByteString hiện tại của tôi trong sự kết hợp với lười biếng IO để đọc chính xác số bên phải của byte ra khỏi mạng?

Những gì tôi đã cố gắng

Tôi cố gắng để sử dụng ByteStreams lười biếng trong sự kết hợp với dụ Data.Serialize của tôi, giống như vậy:

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

Điều này dường như thất bại chủ yếu vào điểm 3 nêu trên: nó vẫn bị chặn ngay cả sau khi số lượng đầy đủ byte đã đến để phân tích MyType.Tôi thật sự nghi ngờ điều này là do ByteStrings được đọc với kích thước khối nhất định cùng một lúc và L.hGetContents là chờ phần còn lại của khối này đến. Mặc dù thuộc tính này đọc một khối ảnh hiệu quả là hữu ích để thực hiện đọc hiệu quả từ đĩa, có vẻ như là đang cản đường tôi chỉ đọc đủ byte để phân tích cú pháp dữ liệu của tôi.

Trả lời

7

Đã xảy ra sự cố với trình phân tích cú pháp của bạn, quá háo hức. Nhiều khả năng nó cần byte tiếp theo sau tin nhắn vì một lý do nào đó. hGetContents từ bytestring không chặn chờ toàn bộ đoạn. Nó sử dụng hGetSome nội bộ.

Tôi đã tạo trường hợp thử nghiệm đơn giản. Các máy chủ sẽ gửi "hello" mỗi giây:

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 

Client sẽ đọc toàn bộ nội dung một cách lười biếng:

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 

Nếu bạn cố gắng chạy cả hai sau đó, bạn sẽ thấy việc in ấn của khách hàng "hello" mỗi giây. Vì vậy, hệ thống con mạng là ok, vấn đề là ở một nơi khác - rất có thể trong trình phân tích cú pháp của bạn.

+0

+1 Trường hợp thử nghiệm rất đẹp và thuyết phục. Thật kỳ lạ, nó không phải là một byte phụ tôi mong đợi ở phần cuối của gói, mà là một trường ở giữa mà dường như có vấn đề ... –

Các vấn đề liên quan