c# - सी#में सॉकेट प्रोग्रामिंग के साथ शुरू करना-सर्वोत्तम प्रथाओं




.net sockets (4)

मैंने सॉकेट के बारे में SO पर कई संसाधन देखे हैं। मेरा मानना ​​है कि उनमें से कोई भी उन विवरणों को कवर नहीं करता जिन्हें मैं जानना चाहता था। मेरे आवेदन में, सर्वर सभी प्रसंस्करण करता है और ग्राहकों को आवधिक अद्यतन भेजता है।

इस पोस्ट का इरादा सॉकेट एप्लिकेशन विकसित करते समय आवश्यक सर्वोत्तम बुनियादी विचारों को कवर करना और सर्वोत्तम प्रथाओं पर चर्चा करना है। यहां मूलभूत चीजें हैं जिन्हें आप लगभग सभी सॉकेट आधारित अनुप्रयोगों के साथ देखेंगे।

1 - एक सॉकेट पर बाध्यकारी और सुनना

मैं निम्नलिखित कोड का उपयोग कर रहा हूँ। यह मेरी मशीन पर अच्छी तरह से काम करता है। जब मैं इसे वास्तविक सर्वर पर तैनात करता हूं तो क्या मुझे किसी और चीज की देखभाल करने की ज़रूरत है?

IPHostEntry localHost = Dns.GetHostEntry(Dns.GetHostName());
IPEndPoint endPoint = new IPEndPoint(localHost.AddressList[0], 4444);

serverSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, 
                     ProtocolType.Tcp);
serverSocket.Bind(endPoint);
serverSocket.Listen(10);

2 - डेटा प्राप्त करना

मैंने 255 आकार के बाइट सरणी का उपयोग किया है। तो जब मुझे 255 बाइट्स से अधिक डेटा प्राप्त हो रहा है, तो मुझे पूरा डेटा प्राप्त होने तक प्राप्त विधि को कॉल करने की आवश्यकता है, है ना? एक बार मुझे पूरा डेटा मिलने के बाद, मुझे पूरा संदेश प्राप्त करने के लिए अब तक प्राप्त सभी बाइट्स को जोड़ना होगा। क्या वो सही है? या क्या एक बेहतर दृष्टिकोण है?

3 - डेटा भेजना और डेटा लंबाई निर्दिष्ट करना

चूंकि संदेश प्राप्त करने की लंबाई खोजने के लिए टीसीपी में कोई रास्ता नहीं है, इसलिए मैं संदेश में लंबाई जोड़ने की योजना बना रहा हूं। यह पैकेट का पहला बाइट होगा। इसलिए क्लाइंट सिस्टम जानता है कि पढ़ने के लिए कितना डेटा उपलब्ध है।

कोई अन्य बेहतर दृष्टिकोण?

4 - ग्राहक को बंद करना

जब ग्राहक बंद हो जाता है, तो यह बंदरगाह को इंगित करने वाले सर्वर को एक संदेश भेजेगा। सर्वर क्लाइंट विवरण को इसकी ग्राहक सूची से हटा देगा। सॉकेट को डिस्कनेक्ट करने के लिए क्लाइंट साइड पर इस्तेमाल किया गया कोड (मैसेजिंग भाग दिखाया नहीं गया है)।

client.Shutdown(SocketShutdown.Both);
client.Close();

कोई सुझाव या समस्याएं?

5 - सर्वर बंद करना

सर्वर बंद करने का संकेत देने वाले सभी ग्राहकों को संदेश भेजता है। जब यह संदेश प्राप्त होता है तो प्रत्येक क्लाइंट सॉकेट को डिस्कनेक्ट कर देगा। ग्राहक सर्वर पर बंद संदेश भेज देंगे और बंद कर देंगे। एक बार जब सर्वर सभी ग्राहकों से घनिष्ठ संदेश प्राप्त करता है, तो यह सॉकेट को डिस्कनेक्ट करता है और सुनना बंद कर देता है। संसाधनों को जारी करने के लिए प्रत्येक क्लाइंट सॉकेट पर कॉल का निपटान करें। क्या यह सही दृष्टिकोण है?

6 - अज्ञात क्लाइंट डिस्कनेक्शन

कभी-कभी, क्लाइंट सर्वर को सूचित किये बिना डिस्कनेक्ट हो सकता है। इसे संभालने की मेरी योजना यह है: जब सर्वर सभी ग्राहकों को संदेश भेजता है, तो सॉकेट स्थिति की जांच करें। यदि यह कनेक्ट नहीं है, तो उस क्लाइंट को क्लाइंट सूची से हटा दें और उस क्लाइंट के लिए सॉकेट बंद करें।

कोई भी मदद बहुत अच्छी रहेगी!


Answers

चूंकि यह 'प्रारंभ करना' है, इसलिए मेरा उत्तर एक उच्च स्केलेबल के बजाय एक सरल कार्यान्वयन के साथ चिपकेगा। चीज़ों को और अधिक जटिल बनाने से पहले सरल दृष्टिकोण से पहले सहज महसूस करना सबसे अच्छा है।

1 - बाध्यकारी और सुनना
आपका कोड मेरे लिए ठीक लगता है, व्यक्तिगत रूप से मैं इसका उपयोग करता हूं:

serverSocket.Bind(new IPEndPoint(IPAddress.Any, 4444));

DNS मार्ग जाने के बजाय, लेकिन मुझे नहीं लगता कि वास्तविक समस्या किसी भी तरह से है।

1.5 - ग्राहक कनेक्शन स्वीकार करना
बस पूर्णता के लिए इसका जिक्र करना ... मुझे लगता है कि आप यह कर रहे हैं अन्यथा आप चरण 2 नहीं प्राप्त करेंगे।

2 - डेटा प्राप्त करना
मैं बफर को 255 बाइट्स से थोड़ा लंबा बना दूंगा, जब तक कि आप अपने सभी सर्वर संदेशों को 255 बाइट्स पर होने की उम्मीद नहीं कर सकते। मुझे लगता है कि आप एक बफर चाहते हैं जो टीसीपी पैकेट आकार से बड़ा होने की संभावना है ताकि आप डेटा के एक ब्लॉक को प्राप्त करने के लिए एकाधिक पढ़ने से बच सकें।

मैं कहूंगा कि 1500 बाइट चुनना ठीक होना चाहिए, या शायद एक अच्छा दौर संख्या के लिए 2048 भी होना चाहिए।

वैकल्पिक रूप से, हो सकता है कि आप डेटा खंडों को संग्रहीत करने के लिए byte[] का उपयोग करने से बच सकें, और इसके बजाय अपने सर्वर-साइड क्लाइंट सॉकेट को NetworkStream में BinaryReader , जिसे BinaryReader में लपेटा गया है, ताकि आप बिना किसी चिंता के सॉकेट से अपने संदेश के घटकों को पढ़ सकें बफर आकार के बारे में।

3 - डेटा भेजना और डेटा लंबाई निर्दिष्ट करना
आपका दृष्टिकोण ठीक काम करेगा, लेकिन यह स्पष्ट रूप से आवश्यक है कि इसे भेजने से पहले पैकेट की लंबाई की गणना करना आसान हो।

वैकल्पिक रूप से, यदि आपका संदेश प्रारूप (इसके घटकों का क्रम) एक फैशन में डिज़ाइन किया गया है ताकि किसी भी समय ग्राहक यह निर्धारित करने में सक्षम हो कि क्या अधिक डेटा होना चाहिए (उदाहरण के लिए, कोड 0x01 का अर्थ है कि अगला एक int और a होगा स्ट्रिंग, कोड 0x02 का मतलब है कि अगला 16 बाइट्स इत्यादि होगा)। क्लाइंट साइड पर NetworkStream दृष्टिकोण के साथ, यह एक बहुत ही प्रभावी दृष्टिकोण हो सकता है।

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

4/5 - क्लाइंट और सर्वर को बंद करना
निजी तौर पर मैं बिना किसी संदेश के Close करने का विकल्प चुनूंगा; जब कोई कनेक्शन बंद हो जाता है तो आपको उस कनेक्शन के दूसरे छोर पर किसी भी अवरुद्ध पढ़ने / लिखने पर अपवाद मिलेगा जिसे आपको पूरा करना होगा।

चूंकि आपको एक मजबूत समाधान प्राप्त करने के लिए 'अज्ञात डिस्कनेक्शन' को पूरा करना है, इसलिए किसी भी जटिल को डिस्कनेक्ट करना आम तौर पर व्यर्थ है।

6 - अज्ञात डिस्कनेक्शन
मैं सॉकेट स्थिति पर भी भरोसा नहीं करता ... ग्राहक या सर्वर के बिना क्लाइंट / सर्वर के बीच पथ के साथ कहीं भी मरने के लिए कनेक्शन संभव है।

एक कनेक्शन को बताने का एकमात्र गारंटी तरीका अप्रत्याशित रूप से मर गया है जब आप कनेक्शन के साथ कुछ भेजने का प्रयास करते हैं। उस बिंदु पर कनेक्शन के साथ कुछ भी गलत होने पर आपको हमेशा विफलता का संकेत मिलेगा।

नतीजतन, सभी अप्रत्याशित कनेक्शनों का पता लगाने का एकमात्र मूर्ख-प्रमाण तरीका एक 'पिंग' तंत्र को लागू करना है, जहां आदर्श रूप से ग्राहक और सर्वर समय-समय पर दूसरे संदेश को एक संदेश भेज देंगे जो केवल एक प्रतिक्रिया संदेश में परिणाम देगा जो दर्शाता है कि 'पिंग' प्राप्त हुआ था।

अनावश्यक पिंग्स को अनुकूलित करने के लिए, आप एक 'टाइम-आउट' तंत्र चाहते हैं जो केवल एक पिंग भेजता है जब दूसरे सिरे से किसी अन्य ट्रैफिक को निश्चित समय के लिए प्राप्त नहीं किया जाता है (उदाहरण के लिए, यदि अंतिम संदेश सर्वर x सेकेंड से अधिक पुराना है, क्लाइंट यह सुनिश्चित करने के लिए एक पिंग भेजता है कि कनेक्शन बिना अधिसूचना के मर गया है)।

अधिक उन्नत
यदि आप उच्च स्केलेबिलिटी चाहते हैं तो आपको सभी सॉकेट ऑपरेशंस के लिए एसिंक्रोनस विधियों को देखना होगा (स्वीकार / भेजें / प्राप्त करें)। ये 'स्टार्ट / एंड' वेरिएंट हैं, लेकिन वे उपयोग करने के लिए बहुत जटिल हैं।

मैं तब तक कोशिश करने की सलाह देता हूं जब तक कि आपके पास सरल संस्करण और काम न हो।

यह भी ध्यान रखें कि यदि आप कुछ दर्जन से अधिक ग्राहकों को स्केल करने की योजना नहीं बना रहे हैं तो यह वास्तव में किसी समस्या के बावजूद समस्या नहीं होगी। Async तकनीक वास्तव में केवल तभी जरूरी है जब आप अपने सर्वर को पूरी तरह से मरने के दौरान हजारों या सैकड़ों हजारों कनेक्टेड क्लाइंट में स्केल करना चाहते हैं।

मैं शायद अन्य महत्वपूर्ण सुझावों का पूरा समूह भूल गया हूं, लेकिन यह आपको शुरू करने के लिए एक काफी मजबूत और भरोसेमंद कार्यान्वयन के लिए पर्याप्त होना चाहिए


1 - एक सॉकेट पर बाध्यकारी और सुनना

मुझे ठीक लग रहा है। आपका कोड सॉकेट को केवल एक आईपी पते पर बांध देगा। यदि आप बस किसी भी आईपी पते / नेटवर्क इंटरफ़ेस को सुनना चाहते हैं, तो IPAddress.Any उपयोग करें। कोई भी:

serverSocket.Bind(new IPEndPoint(IPAddress.Any, 4444));

भविष्य के सबूत होने के लिए, आप आईपीवी 6 का समर्थन करना चाह सकते हैं। किसी भी आईपीवी 6 पते को सुनने के लिए, IPAddress.IPv6Any किसी भी IPAddress.IPv6Any के स्थान पर उपयोग करें। कोई भी।

ध्यान दें कि आप एक ही समय में किसी भी आईपीवी 4 और किसी भी आईपीवी 6 पते पर नहीं सुन सकते हैं, सिवाय इसके कि आप दोहरी-स्टैक सॉकेट का उपयोग करते हैं। यह आपको IPV6_V6ONLY सॉकेट विकल्प को अनसेट करने की आवश्यकता होगी:

serverSocket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, 0);

अपनी सॉकेट के साथ Teredo को सक्षम करने के लिए, आपको PROTECTION_LEVEL_UNRESTRICTED सॉकेट विकल्प सेट करने की आवश्यकता है:

serverSocket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)23, 10);

2 - डेटा प्राप्त करना

मैं NetworkStream का उपयोग करने की अनुशंसा करता हूं जो मैन्युअल रूप से हिस्सों को पढ़ने के बजाय Stream में सॉकेट को लपेटता है।

बाइट्स की एक निश्चित संख्या को पढ़ना थोड़ा अजीब है हालांकि:

using (var stream = new NetworkStream(serverSocket)) {
   var buffer = new byte[MaxMessageLength];
   while (true) {
      int type = stream.ReadByte();
      if (type == BYE) break;
      int length = stream.ReadByte();
      int offset = 0;
      do
         offset += stream.Read(buffer, offset, length - offset);
      while (offset < length);
      ProcessMessage(type, buffer, 0, length);
   }
}

जहां NetworkStream वास्तव में चमकता है कि आप इसे किसी अन्य Stream तरह उपयोग कर सकते हैं। यदि सुरक्षा महत्वपूर्ण है, तो सर्वर को प्रमाणीकृत करने के लिए बस NetworkStream को NetworkStream में लपेटें और (वैकल्पिक रूप से) X.50 9 प्रमाणपत्र वाले क्लाइंट। संपीड़न वैसे ही काम करता है।

var sslStream = new SslStream(stream, false);
sslStream.AuthenticateAsServer(serverCertificate, false, SslProtocols.Tls, true);
// receive/send data SSL secured

3 - डेटा भेजना और डेटा लंबाई निर्दिष्ट करना

आपका दृष्टिकोण काम करना चाहिए, यद्यपि आप शायद पहिया को फिर से शुरू करने और इसके लिए एक नया प्रोटोकॉल तैयार करने के लिए सड़क पर नहीं जाना चाहेंगे। BEEP पर एक नज़र डालें या शायद protobuf तरह कुछ सरल भी देखें।

अपने लक्ष्यों के आधार पर, डब्ल्यूसीएफ या कुछ अन्य आरपीसी तंत्र जैसे सॉकेट के ऊपर एक अमूर्तता चुनने के बारे में सोचने लायक हो सकता है।

4/5/6 - समापन और अज्ञात डिस्कनेक्शन

क्या jerryjvl ने कहा :-) jerryjvl विश्वसनीय होने पर केवल विश्वसनीय पहचान तंत्र पिंग या रख-रखाव भेज रहे हैं।

जबकि आपको किसी भी मामले में अज्ञात डिस्कनेक्शन से निपटना होगा, मैं व्यक्तिगत रूप से बिना किसी चेतावनी के इसे बंद करने के बजाय पारस्परिक समझौते में कनेक्शन बंद करने के लिए कुछ प्रोटोकॉल तत्व रखूंगा।



मुझे \ t (बीएफरी के उत्तर को देखें) कॉलम सेपरेटर्स और \ n पंक्ति विभाजक के रूप में एक्सेल में चिपकने में सफलता मिली है।





c# .net sockets