2015-02-09 3 views
10

मैं एक छोटा सा हास्केल प्रोग्राम लिखने की कोशिश कर रहा हूं जो बाइनरी नेटवर्क प्रोटोकॉल से बात करता है, और मुझे आश्चर्य की एक बड़ी समस्या है।नेटवर्क पर कुशल बाइनरी I/O

ऐसा लगता है कि बाइनरी डेटा ByteString के रूप में संग्रहीत किया जाना चाहिए।

प्रश्न: क्या मुझे सिर्फ hGet/hPut व्यक्तिगत मल्टी-बाइट पूर्णांकों, या इसे और अधिक पूरी बात का एक बड़ा ByteString का निर्माण और उपयोग करने के लिए है कि performant है?

ऐसा लगता है कि binary पैकेज यहां उपयोगी होना चाहिए। हालांकि, binary केवल आलसीByteString मानों के साथ सौदे करता है।

प्रश्न: सख्ती से बाइट्स का एक आलसीByteString वास्तव में पढ़ा निर्धारित संख्या पर hGet करता है? या यह किसी प्रकार का आलसी I/O करने की कोशिश करता है? (मैं नहीं आलसी I/O चाहता हूं!)

प्रश्न: दस्तावेज़ीकरण यह निर्दिष्ट क्यों नहीं करता है?

कोड ऐसा लगता है कि इसमें "पूर्ण पूर्णांक प्राप्त करें, इस मान से इसकी तुलना करें, अगर कोई त्रुटि नहीं फेंकती है, अन्यथा अगले चरण पर जारी रखें ..." मुझे यकीन नहीं है कि कैसे स्पेगेटी कोड लिखने के बिना साफ रूप से संरचना करने के लिए।

संक्षेप में, मैं जो करने की कोशिश कर रहा हूं वह काफी सरल है, लेकिन मैं कोड को सरल बनाने के लिए एक तरीके से संघर्ष कर रहा हूं। शायद मैं कर रहा हूँ बस-सोच यह और लापता स्पष्ट कुछ ...

+2

मैं आपके सभी प्रश्नों के जवाब कर सकते हैं नहीं है, लेकिन मैं नाली विश्वास करते हैं और पाइप आप से बचने के लिए मदद मिलेगी आलसी मैं/हे: बाहर [Data.Conduit.Binary] जाँच (https://hackage.haskell.org/ पैकेज/कंड्यूट-0.4.0/डॉक्स/डेटा-कंडिट-बाइनरी.html) और [पाइप्स.बेटस्ट्रिंग] (https://hackage.haskell.org/package/pipes-bytestring-2.1.1/docs/Pipes-ByteString ।एचटीएमएल) –

+0

मेरे पास मेरे पुराने मानक नहीं हैं, लेकिन मुझे याद है कि यह एक बड़ी 'बाइटस्ट्रिंग' बनाने और उसे भेजने के बजाय सीधे सीधे 'hPut' और' hGet' आपकी संख्या को सॉकेट पर अधिक कुशल था। गति में अंतर 'hPut' /' hGet' के लिए 5 गुना तेज हो सकता है। इस प्रकार, उदाहरण के लिए, सभी 'blaze- * 'पैकेजों को उनकी गति में सुधार कैसे मिलता है। –

+0

@ गैब्रियल गोंज़ालेज़, ऐसा इसलिए है क्योंकि 'एचपीयूटी' और 'एचजीएटी' उन कार्यों का उपयोग करते हैं जो पहले से ही अपने स्वयं के बफरिंग करते हैं? – dfeuer

उत्तर

2

पुन प्रश्न 1 ...

संभाल के साथ NoBuffering प्रत्येक hPutStr कॉल कॉन्फ़िगर किया गया है एक लिखने सिस्टम कॉल उत्पन्न होगा। बड़ी संख्या में छोटे लिखने के लिए यह एक बड़ा प्रदर्शन जुर्माना लगाएगा। , https://stackoverflow.com/a/28146677/866915

दूसरी ओर अगर संभाल बफरिंग सक्षम आप स्पष्ट रूप से संभाल फ्लश करने के लिए सुनिश्चित करें कि बफ़र डेटा नहीं भेजा जाता है की आवश्यकता होगी: देखो, उदाहरण के लिए, इस अतः कुछ बेंच मार्किंग के लिए जवाब।

मुझे लगता है कि आप टीसीपी जैसे स्ट्रीमिंग प्रोटोकॉल का उपयोग कर रहे हैं। यूडीपी के साथ आपको स्पष्ट रूप से प्रत्येक संदेश को परमाणु इकाई के रूप में बनाना और भेजना होगा।

पुन प्रश्न # 2 ...

कोड ऐसा लगता है कि आलसी bytestrings के लिए hGetdefaultChunkSize की मात्रा में संभाल है जो 32k के बारे में पढ़ा होगा पढ़ना।

अद्यतन: ऐसा प्रतीत होता है कि एचजीईटी इस मामले में आलसी आईओ प्रदर्शन नहीं करता है। इसका परीक्षण करने के लिए यहां कुछ कोड दिया गया है। फ़ीड:

#!/usr/bin/env perl 
$| = 1; 
my $c = 0; 
my $k = "1" x 1024; 
while (1) { 
    syswrite(STDOUT, $k); 
    $c++; 
    print STDERR "wrote 1k count = $c\n"; 
} 

Test.hs:

import qualified Data.ByteString.Lazy as LBS 
import System.IO 

main = do 
    s <- LBS.hGet stdin 320000 
    let s2 = LBS.take 10 s 
    print $ ("Length s2 = ", s2) 

रनिंग perl feed | runhaskell Test.hs यह स्पष्ट है कि हास्केल प्रोग्राम पर्ल कार्यक्रम भले ही यह केवल पहले 10 बाइट्स का उपयोग करता है से सभी 320k मांग करती है।

3

टीसीपी की आवश्यकता है कि एप्लिकेशन अपना संदेश सीमा मार्कर प्रदान करे। संदेश सीमाओं को चिह्नित करने के लिए एक सरल प्रोटोकॉल डेटा के एक हिस्से की लंबाई, डेटा का हिस्सा, और शेष संदेश हैं जो एक ही संदेश का हिस्सा हैं। संदेश सीमा की जानकारी रखने वाले शीर्षलेख के लिए इष्टतम आकार संदेश आकारों के वितरण पर निर्भर करता है।

अपना खुद का छोटा संदेश प्रोटोकॉल विकसित करना, हम अपने शीर्षकों के लिए दो बाइट्स का उपयोग करेंगे। बाइट्स से सबसे महत्वपूर्ण बिट (Word16 के रूप में माना जाता है) संदेश में शेष भाग हैं या नहीं, यह धारण करेगा। शेष 15 बिट्स बाइट्स में संदेश की लंबाई रखेंगे। यह ठेठ टीसीपी पैकेट की तुलना में 32k तक की चंक आकारों की अनुमति देगा। यदि संदेश आमतौर पर बहुत छोटे होते हैं, तो विशेष रूप से यदि वे 127 बाइट से छोटे होते हैं तो दो बाइट हेडर कम से कम इष्टतम होंगे।

हम अपने कोड के नेटवर्किंग हिस्से के लिए network-simple का उपयोग करने जा रहे हैं। हम binary पैकेज के साथ संदेशों को क्रमबद्ध या deserialize करेंगे जो encode एस और decode एस आलसी ByteString एस से और उसके लिए है।

import qualified Data.ByteString.Lazy as L 
import qualified Data.ByteString as B 

import Network.Simple.TCP 
import Data.Bits 
import Data.Binary 
import Data.Functor 
import Control.Monad.IO.Class 

पहले उपयोगिता हम की आवश्यकता होगी सख्त ByteString रों में Word16 हेडर लिखने और उन्हें वापस बाहर फिर से पढ़ने की क्षमता है। हम उन्हें बड़े एंडियन क्रम में लिखेंगे। वैकल्पिक रूप से, इन्हें Binary उदाहरण Word16 के संदर्भ में लिखा जा सकता है।

writeBE :: Word16 -> B.ByteString 
writeBE x = B.pack . map fromIntegral $ [(x .&. 0xFF00) `shiftR` 8, x .&. 0xFF] 

readBE :: B.ByteString -> Maybe Word16 
readBE s = 
    case map fromIntegral . B.unpack $ s of 
     [w1, w0] -> Just $ w1 `shiftL` 8 .|. w0 
     _  -> Nothing 

मुख्य चुनौती भेजने के लिए और आलसी ByteString रों द्विआधारी पैकेज से हम पर मजबूर प्राप्त करने के लिए किया जाएगा। चूंकि हम एक समय में केवल 32k बाइट्स भेजने में सक्षम होंगे, इसलिए हमें rechunk भाग में आलसी बाइटस्ट्रिंग करने में सक्षम होना चाहिए, जो कुल मिलाकर लंबाई से अधिक नहीं है। एक सिंगल हिस्सा पहले से कहीं अधिक हो सकता है; कोई भी हिस्सा जो हमारे नए हिस्सों में फिट नहीं होता है, कई हिस्सों में विभाजित होता है।

rechunk :: Int -> [B.ByteString] -> [(Int, [B.ByteString])] 
rechunk n = go [] 0 . filter (not . B.null) 
    where 
     go acc l []  = [(l, reverse acc)] 
     go acc l (x:xs) = 
      let 
       lx = B.length x 
       l' = lx + l 
      in 
       if l' <= n 
       then go (x:acc) l' xs 
       else 
        let (x0, x1) = B.splitAt (n-l) x 
        in (n, reverse (x0:acc)) : go [] 0 (x1:xs) 

recvExactly बाइट्स हम अनुरोध के सभी जब तक इच्छा पाश प्राप्त किया गया है।

recvExactly :: MonadIO m => Socket -> Int -> m (Maybe [B.ByteString]) 
recvExactly s toRead = go [] toRead 
    where 
     go acc toRead = do 
      body <- recv s toRead 
      maybe (return Nothing) (go' acc toRead) body 
     go' acc toRead body = 
      if B.length body < toRead 
      then go (body:acc) (toRead - B.length body) 
      else return . Just . reverse $ acc 

एक आलसी ByteString भेजा जा रहा है एक आकार हम जानते हैं कि हम भेज सकते हैं के टुकड़ों में यह तोड़ने और हैडर आकार धारण और है कि क्या वहाँ किसी भी अधिक हिस्सा हैं के साथ प्रत्येक हिस्सा भेजने के होते हैं।

एक आलसी ByteString प्राप्त दो बाइट हैडर पढ़ने, आकार हैडर ने संकेत का एक हिस्सा पढ़ने, और जब तक पढ़ने के लिए के रूप में शीर्ष लेख संकेत दिया अधिक मात्रा देखते हैं जारी होते हैं।

recvLazyBS :: (MonadIO m, Functor m) => Socket -> m (Maybe L.ByteString) 
recvLazyBS s = fmap L.fromChunks <$> go [] 
    where 
     go acc = do 
      header <- recvExactly s 2 
      maybe (return Nothing) (go' acc) (header >>= readBE . B.concat) 
     go' acc h = do 
      body <- recvExactly s . fromIntegral $ h .&. 0x7FFF 
      let next = if h .&. 0x8000 /= 0 
         then go 
         else return . Just . concat . reverse 
      maybe (return Nothing) (next . (:acc)) body  

भेजा जा रहा है या है कि एक Binary उदाहरण सिर्फ एक encode घ आलसी ByteString भेजना या इसे ing आलसी ByteString और decode प्राप्त कर रहा है एक संदेश प्राप्त करने।

sendBinary :: (MonadIO m, Binary a) => Socket -> a -> m() 
sendBinary s = sendLazyBS s . encode 

recvBinary :: (MonadIO m, Binary a, Functor m) => Socket -> m (Maybe a) 
recvBinary s = d . fmap decodeOrFail <$> recvLazyBS s 
    where 
     d (Just (Right (_, _, x))) = Just x 
     d _      = Nothing 
संबंधित मुद्दे