.NET

2012-10-27 5 views
16

में नेटवर्कस्ट्रीम से पढ़ने का सही तरीका क्या है, मैं इसके साथ संघर्ष कर रहा हूं और मुझे कोई कारण नहीं मिला कि मेरा कोड एक टीसीपी सर्वर से ठीक से पढ़ने में असफल रहा है। मैं TcpClient वर्ग और इसकी GetStream() विधि का उपयोग कर रहा हूं लेकिन कुछ उम्मीद के अनुसार काम नहीं कर रहा है। या तो ऑपरेशन अनिश्चित काल तक ब्लॉक करता है (आखिरी पठन ऑपरेशन अपेक्षित समय सीमा नहीं देता है), या डेटा फसल हो जाता है (किसी कारण से रीड ऑपरेशन 0 देता है और लूप से बाहर निकलता है, शायद सर्वर पर्याप्त तेज़ी से प्रतिक्रिया नहीं दे रहा है)। ये इस समारोह को लागू करने पर तीन प्रयासों हैं:.NET

// this will break from the loop without getting the entire 4804 bytes from the server 
string SendCmd(string cmd, string ip, int port) 
{ 
    var client = new TcpClient(ip, port); 
    var data = Encoding.GetEncoding(1252).GetBytes(cmd); 
    var stm = client.GetStream(); 
    stm.Write(data, 0, data.Length); 
    byte[] resp = new byte[2048]; 
    var memStream = new MemoryStream(); 
    int bytes = stm.Read(resp, 0, resp.Length); 
    while (bytes > 0) 
    { 
     memStream.Write(resp, 0, bytes); 
     bytes = 0; 
     if (stm.DataAvailable) 
      bytes = stm.Read(resp, 0, resp.Length); 
    } 
    return Encoding.GetEncoding(1252).GetString(memStream.ToArray()); 
} 

// this will block forever. It reads everything but freezes when data is exhausted 
string SendCmd(string cmd, string ip, int port) 
{ 
    var client = new TcpClient(ip, port); 
    var data = Encoding.GetEncoding(1252).GetBytes(cmd); 
    var stm = client.GetStream(); 
    stm.Write(data, 0, data.Length); 
    byte[] resp = new byte[2048]; 
    var memStream = new MemoryStream(); 
    int bytes = stm.Read(resp, 0, resp.Length); 
    while (bytes > 0) 
    { 
     memStream.Write(resp, 0, bytes); 
     bytes = stm.Read(resp, 0, resp.Length); 
    } 
    return Encoding.GetEncoding(1252).GetString(memStream.ToArray()); 
} 

// inserting a sleep inside the loop will make everything work perfectly 
string SendCmd(string cmd, string ip, int port) 
{ 
    var client = new TcpClient(ip, port); 
    var data = Encoding.GetEncoding(1252).GetBytes(cmd); 
    var stm = client.GetStream(); 
    stm.Write(data, 0, data.Length); 
    byte[] resp = new byte[2048]; 
    var memStream = new MemoryStream(); 
    int bytes = stm.Read(resp, 0, resp.Length); 
    while (bytes > 0) 
    { 
     memStream.Write(resp, 0, bytes); 
     Thread.Sleep(20); 
     bytes = 0; 
     if (stm.DataAvailable) 
      bytes = stm.Read(resp, 0, resp.Length); 
    } 
    return Encoding.GetEncoding(1252).GetString(memStream.ToArray()); 
} 

पिछले एक "काम करता है", लेकिन यह निश्चित पाश पर विचार सॉकेट पहले से ही समय समाप्ति पढ़ का समर्थन करने वाले के अंदर एक हार्ड-कोडेड नींद डाल करने के लिए बदसूरत लग रहा है! क्या मुझे NetworkStream के TcpClient पर कुछ संपत्ति (ies) सेट करने की आवश्यकता है? क्या समस्या सर्वर में रहती है? सर्वर कनेक्शन बंद नहीं करता है, ऐसा करने के लिए ग्राहक पर निर्भर करता है। उपरोक्त यूआई थ्रेड संदर्भ (टेस्ट प्रोग्राम) के अंदर भी चल रहा है, शायद इसके साथ कुछ करने के लिए ...

क्या कोई डेटा को पढ़ने के लिए NetworkStream.Read का सही ढंग से उपयोग करने के बारे में जानता है, जब तक कोई डेटा उपलब्ध न हो जाए? मुझे लगता है कि मैं जो चाहता हूं वह पुरानी Win32 winsock टाइमआउट गुणों की तरह कुछ है ... ReadTimeout, आदि। यह टाइमआउट तक पहुंचने तक पढ़ने की कोशिश करता है, और फिर 0 वापस लौटाता है ... लेकिन कभी-कभी यह डेटा वापस लौटने लगता है उपलब्ध होना चाहिए (या रास्ते में .. यदि उपलब्ध हो तो वापसी 0 पढ़ सकते हैं?) और फिर डेटा उपलब्ध नहीं होने पर अंतिम बार पढ़ने पर अनिश्चित काल तक अवरुद्ध हो जाता है ...

हाँ, मुझे नुकसान हो रहा है!

+0

सावधान:

मैं ReceiveTimeout का उपयोग कर पाते हैं अतुल्यकालिक पढ़ने को लागू करने की तुलना में बहुत आसान है ... यहाँ काम कर कोड है। आपके प्रयास (और उत्तरों) में कोड क्लाइंट या स्ट्रीम को बंद नहीं करता है, जो बार-बार बुलाए जाने वाले बड़े परिणामों के साथ संसाधन रिसाव का कारण बनता है। आपको संभवत: 'var क्लाइंट = नया सिस्टम.Net.Sockets.TcpClient (ip, पोर्ट) का उपयोग करना चाहिए ( (var stm = client.GetStream()) का उपयोग करके' शेष बार्स में शेष विधि को संलग्न करें। यह गारंटी देगा कि विधि से बाहर निकलने पर, जो कुछ भी कारण है, कनेक्शन बंद हैं, संसाधनों को पुनः दावा किया जाता है। –

+0

क्या आपने टीसीपी क्लाइंट क्लास के लिए कोड देखा है? मैं इसे देखने की सलाह देता हूं ...यदि आप अभी भी एंडपॉइंट पर जवाब देना चाहते हैं, तो आपको स्ट्रीम को बंद नहीं करना चाहिए और न ही टीसीपीक्लिएंट (जो स्ट्रीम को बंद कर देता है, अगर मुझे गलत नहीं लगता है)। लेकिन मैं जिस वास्तविक कोड का उपयोग कर रहा हूं वह अलग है, मैं इसे खोद दूंगा और यहां जवाब भी अपडेट करूंगा। – Loudenvier

+0

सुझाव के लिए धन्यवाद। मैंने पाया [TCPClient.cs] (http://referencesource.microsoft.com/#system/net/System/Net/Sockets/TCPClient.cs)। वास्तव में 'टीसीपी क्लाइंट' को बंद या निपटाना धारा का निपटान करता है। मैंने वास्तव में कनेक्शन विफलताओं को देखा है जब एक कार्यक्रम ने बिना किसी देखभाल के हजारों कनेक्शन किए हैं (अन्य, बुरे, कारणों से)। यहां सामान्य आईडीस्पोजेबल पैटर्न से क्यों विचलित होना चाहिए? IDISposable कार्यान्वित करने में त्रुटि-प्रवण है, '() का उपयोग करना आसान और सुरक्षित है। –

उत्तर

8

अंतर्निहित सॉकेट ReceiveTimeout संपत्ति को स्थापित करने से चाल चल रही है। आप इसे इस तरह से एक्सेस कर सकते हैं: yourTcpClient.Client.ReceiveTimeout। अधिक जानकारी के लिए आप docs पढ़ सकते हैं।

अब कोड केवल जब तक "नींद" कुछ डेटा सॉकेट में आने के लिए है, या यह एक अपवाद कोई डेटा आता है, पढ़ने आपरेशन की शुरुआत में, अधिक से अधिक 20ms के लिए बढ़ा देंगे के लिए के रूप में की जरूरत है। यदि आवश्यक हो तो मैं इस टाइमआउट को ट्विक कर सकता हूं। अब मैं हर पुनरावृत्ति में 20 एमएमएस मूल्य का भुगतान नहीं कर रहा हूं, मैं केवल इसे अंतिम पढ़ने के ऑपरेशन पर भुगतान कर रहा हूं। चूंकि मेरे पास सर्वर से पढ़े गए पहले बाइट्स में संदेश की सामग्री-लंबाई है, इसलिए मैं इसे और भी ट्विक करने के लिए उपयोग कर सकता हूं और सभी अपेक्षित डेटा पहले ही प्राप्त होने पर पढ़ने की कोशिश नहीं कर सकता।

string SendCmd(string cmd, string ip, int port) 
{ 
    var client = new TcpClient(ip, port); 
    var data = Encoding.GetEncoding(1252).GetBytes(cmd); 
    var stm = client.GetStream(); 
    stm.Write(data, 0, data.Length); 
    byte[] resp = new byte[2048]; 
    var memStream = new MemoryStream(); 
    var bytes = 0; 
    client.Client.ReceiveTimeout = 20; 
    do 
    { 
     try 
     { 
      bytes = stm.Read(resp, 0, resp.Length); 
      memStream.Write(resp, 0, bytes); 
     } 
     catch (IOException ex) 
     { 
      // if the ReceiveTimeout is reached an IOException will be raised... 
      // with an InnerException of type SocketException and ErrorCode 10060 
      var socketExept = ex.InnerException as SocketException; 
      if (socketExept == null || socketExept.ErrorCode != 10060) 
       // if it's not the "expected" exception, let's not hide the error 
       throw ex; 
      // if it is the receive timeout, then reading ended 
      bytes = 0; 
     } 
    } while (bytes > 0); 
    return Encoding.GetEncoding(1252).GetString(memStream.ToArray()); 
} 
+2

इस हैक का उपयोग करके सावधान रहें। हमारे पास एक जावा एप्लिकेशन है जो सॉकेट टाइमआउट पर निर्भर करता है ताकि अंत में संदेश का पता लगाया जा सके और समय-समय पर हमने स्ट्रीम में बाइट खो दिया था। माना जाता है कि मुझे नहीं पता कि यह .NET में भी है, लेकिन मुझे संदेह है कि यह अंतर्निहित टीसीपी/आईपी स्टैक है जो कारण था। आपको बस टाइमआउट के बाद सॉकेट का पुन: उपयोग नहीं करना चाहिए। –

+0

@ सोरेनबॉसेन मैं टाइमआउट के बाद सॉकेट का पुन: उपयोग नहीं कर रहा हूं ... मैं डेटा खोला नहीं जाउंगा, लेकिन मेरे पास झूठा "सकारात्मक" टाइमआउट हो सकता है (यदि नेटवर्क धीमा डेटा इंटरैक्टेक्टर टाइमआउट आने से अधिक समय ले सकता है) , लेकिन इस मामले में दूसरी तरफ एक डिस्कनेक्शन को संभालेगा और उम्मीद है कि, पुनः प्रयास करें। यह वास्तव में एक हैक नहीं है, प्रोटोकॉल, जिसे मैं नहीं बदल सकता, अज्ञात आकार के संदेश भेजता है, इसलिए मुझे टाइमआउट पर भरोसा करना चाहिए। मैंने वास्तव में कोड को समय के साथ और अधिक मजबूत बना दिया है, लेकिन लेख को अपडेट नहीं किया है। अब यह एक अच्छा समय है। – Loudenvier

0

आपकी आवश्यकता के अनुसार, Thread.Sleep उपयोग करने के लिए बिल्कुल ठीक है क्योंकि आप सुनिश्चित नहीं हैं कि डेटा कब उपलब्ध होगा, इसलिए आपको डेटा उपलब्ध होने की प्रतीक्षा करनी पड़ सकती है। मैंने आपके फ़ंक्शन के तर्क को थोड़ा बदल दिया है इससे आपको थोड़ा और मदद मिल सकती है।

string SendCmd(string cmd, string ip, int port) 
{ 
    var client = new TcpClient(ip, port); 
    var data = Encoding.GetEncoding(1252).GetBytes(cmd); 
    var stm = client.GetStream(); 
    stm.Write(data, 0, data.Length); 
    byte[] resp = new byte[2048]; 
    var memStream = new MemoryStream(); 

    int bytes = 0; 

    do 
    { 
     bytes = 0; 
     while (!stm.DataAvailable) 
      Thread.Sleep(20); // some delay 
     bytes = stm.Read(resp, 0, resp.Length); 
     memStream.Write(resp, 0, bytes); 
    } 
    while (bytes > 0); 

    return Encoding.GetEncoding(1252).GetString(memStream.ToArray()); 
} 

आशा है कि इससे मदद मिलती है!

+1

मैंने प्रश्न पोस्ट करने के बाद इस तरह विधि को लिखा है! फिर भी मुझे यह स्वीकार्य नहीं लगता है। अगर मैं पुराने पुराने WIN32 का उपयोग कर रहा था IO ओवरलैप किया गया था, तब तक डेटा 20 से अधिक msecs के लिए "अवरुद्ध" हो सकता था जब तक डेटा शुरू नहीं हुआ, इसलिए मेरा कोड इस छद्म कोड की तरह कुछ सुरक्षित हो सकता है: 'stm.ReadIfDataBecomesAvailableInUpTo (timeout = 2000) 'I पता है कि 20 एमएमएस भुगतान करने के लिए एक छोटी सी कीमत है, लेकिन यह हर बातचीत में भुगतान किया जाएगा! एक एमबी आकार की प्रतिक्रिया तैयार करने के लिए यह एक बड़ा ओवरहेड होगा! मैं अपना खुद का टाइमआउट तर्क लिखना नहीं चाहता था ... :-( – Loudenvier

+5

आपको थ्रेड पर सोने से सावधान रहना होगा। यदि समय बहुत छोटा है, तो आप ओएस पर अनावश्यक संदर्भ स्विच को मजबूर कर रहे हैं, इस प्रकार सीपीयू। एक बेहतर तंत्र शायद बाधित-प्रेरित या 'घटना' संचालित होगा। इसके लिए आप stm.BeginRead() का उपयोग कर सकते हैं जो आपको डेटा तैयार होने पर एक ईवेंट कहने की अनुमति देगा, इस तरह आपकी प्रक्रिया एक हो सकती है अवरुद्ध स्थिति जहां संसाधन तैयार होने पर ओएस केवल जागृत होगा। नींद वापस प्रक्रिया के दौरान ओएस पर वापस आ जाएगी। – user1132959

14

नेटवर्किंग कोड लिखना, परीक्षण करना और डीबग करना बेहद मुश्किल है।

आप अक्सर बातें की बहुत सारी पर विचार करने के लिए है जैसे:

  • क्या "endian" आप डेटा है कि लेन-देन हो (इंटेल x86/x64 थोड़ा-endian पर आधारित है) के लिए इस्तेमाल करेगा - प्रणाली है कि बड़े-एंडियन का उपयोग अभी भी डेटा को पढ़ सकते हैं जो कि छोटे-अंत (और इसके विपरीत) में है, लेकिन उन्हें डेटा को पुनर्व्यवस्थित करना होगा। अपने "प्रोटोकॉल" को दस्तावेज करते समय बस यह स्पष्ट करें कि आप किस का उपयोग कर रहे हैं।

  • देखते हैं किसी भी "सेटिंग" कि सॉकेट जो प्रभावित कर सकता है "धारा" बर्ताव करती है (उदाहरण के लिए SO_LINGER) पर निर्धारित किया गया है - आप को चालू या बंद पर कुछ को चालू करने के लिए आवश्यकता हो सकती है यदि आपके कोड बहुत ही संवेदनशील है

  • "संदेश" एक क्लाइंट और सर्वर (किसी भी दिशा में) के बीच आदान-प्रदान किया जा रहा है तो कैसे वास्तविक दुनिया जो धारा में हो रही देरी का कारण बनता है में भीड़ अपने पढ़ने को प्रभावित करता है/तर्क लेखन

आकार में भिन्न हो सकते हैं तो अक्सर आपको उस "संदेश" को विश्वसनीय तरीके से आदान-प्रदान करने के लिए एक रणनीति का उपयोग करने की आवश्यकता होती है (उर्फ मसविदा बनाना)।- यह केवल एक "संख्या" हो सकता है पहले 2/4/8 में

  • एक हैडर कि डेटा के पहले आता है में encoded संदेश आकार है:

    यहाँ विनिमय को संभालने के लिए कई अलग अलग तरीके हैं (आपकी अधिकतम संदेश आकार पर निर्भर) भेजी गई बाइट, या एक अधिक विदेशी "शीर्षक"

  • मार्कर (प्रहरी) एक विशेष "संदेश के अंत" का उपयोग इनकोडिंग/भाग निकले वास्तविक डेटा के साथ, हो सकता है अगर वहाँ संभावना है वास्तविक डेटा को "मार्कर के अंत" के साथ भ्रमित किया जा रहा है

  • एक टाइमआउट का उपयोग करें .... यानी। कोई बाइट प्राप्त करने की एक निश्चित अवधि का मतलब है कि संदेश के लिए कोई और डेटा नहीं है - हालांकि, यह शॉर्ट टाइमआउट के साथ त्रुटि हो सकती है, जिसे आसानी से भीड़ वाले धाराओं पर मारा जा सकता है।

  • में "कमांड" और "डेटा" चैनल अलग-अलग "कनेक्शन" पर है .... यह एफ़टीपी प्रोटोकॉल का उपयोग करने का दृष्टिकोण है (लाभ कमांड से डेटा का स्पष्ट पृथक्करण है ... दूसरा कनेक्शन)

प्रत्येक दृष्टिकोण में "शुद्धता" के लिए इसके पेशेवर और विपक्ष हैं।

नीचे दिया गया कोड "टाइमआउट" विधि का उपयोग करता है, जैसा कि आप चाहते हैं कि एक जैसा लगता है।

http://msdn.microsoft.com/en-us/library/bk6w7hs8.aspx देखें। आप TCPClient पर NetworkStream तक पहुंच प्राप्त कर सकते हैं ताकि आप ReadTimeout बदल सकें।

string SendCmd(string cmd, string ip, int port) 
{ 
    var client = new TcpClient(ip, port); 
    var data = Encoding.GetEncoding(1252).GetBytes(cmd); 
    var stm = client.GetStream(); 
    // Set a 250 millisecond timeout for reading (instead of Infinite the default) 
    stm.ReadTimeout = 250; 
    stm.Write(data, 0, data.Length); 
    byte[] resp = new byte[2048]; 
    var memStream = new MemoryStream(); 
    int bytesread = stm.Read(resp, 0, resp.Length); 
    while (bytesread > 0) 
    { 
     memStream.Write(resp, 0, bytesread); 
     bytesread = stm.Read(resp, 0, resp.Length); 
    } 
    return Encoding.GetEncoding(1252).GetString(memStream.ToArray()); 
} 

इस लेखन नेटवर्क कोड पर अन्य रूपों के लिए एक फुटनोट के रूप में ... एक Read जहाँ आप एक "ब्लॉक" से बचना चाहते हैं कर जब, आप DataAvailable झंडा जाँच कर सकते हैं और उसके बाद ही पढ़ा क्या बफर में है .Length संपत्ति उदाहरण की जांच कर रहा है stm.Read(resp, 0, stm.Length);

+0

आकार अज्ञात है, एमबी रेंज पर हो सकता है ... जैसा कि मैंने कहा है सवाल, तीसरा विकल्प सब कुछ सही ढंग से पढ़ता है, लेकिन मैं वहां उस नींद की आवश्यकता को स्वीकार नहीं कर सकता, और मैं इसे समझा नहीं सकता। मुझे पता है कि यह क्या करता है: सोने से मैं डेटा को सॉकेट में आने का समय देता हूं , लेकिन अगर डेटा इससे पहले पहुंचा तो मैं 20ms सोना नहीं चाहता था। मुझे पता है कि गैर-ब्लॉकी के साथ ऐसा कैसे करें एनजी पढ़ता है और अपना खुद का ओवरलैप किया गया IO ... लेकिन मुझे नहीं लगता कि मुझे उसमें इसका उपयोग करने की आवश्यकता होनी चाहिए .NET! – Loudenvier

+0

पढ़ने के लिए अनुमति देने के लिए आप किस विधि का उपयोग करते हैं यदि यह सभी डेटा यानी सही मात्रा/आकार पढ़ा जाता है? सामान्य तरीका एक आकार शीर्षलेख का उपयोग करना है जो प्रतिक्रिया के पहले भाग में एन्कोड किया गया है। –

+0

मेरे पास एक सामग्री-लंबाई है (प्रोटोकॉल HTTP के लगभग समान है), लेकिन सादगी के लिए मैं "आने वाले डेटा टाइमआउट" का उपयोग करूंगा और मैंने सोचा था कि रीड एक समय के लिए अवरुद्ध होगा और फिर टाइमआउट होगा और उसके लिए हमेशा के लिए इंतजार नहीं करेगा आने! "अंदरूनी-लूप" नींद के साथ मैं "आवक डेटा टाइमआउट" के साथ प्रत्येक पुनरावृत्ति में 20ms की कीमत का भुगतान करूंगा, मैं केवल तभी भुगतान करूँगा जब आखिरी पठन हो। मैं ContentLenght का उपयोग करके इसे अनुकूलित कर सकता हूं, लेकिन अकेले लंबाई पर भरोसा करना भी जोखिम भरा है क्योंकि सर्वर गलत व्यवहार कर सकता है और सबकुछ नहीं भेज सकता है! – Loudenvier

संबंधित मुद्दे