.Net को लागि विश्वसनीय Udp प्रोटोकल को कार्यान्वयन

इन्टरनेट धेरै पहिले परिवर्तन भएको छ। इन्टरनेटको मुख्य प्रोटोकलहरू मध्ये एक - UDP अनुप्रयोगहरू द्वारा डाटाग्राम र प्रसारणहरू प्रदान गर्न मात्र नभई नेटवर्क नोडहरू बीच "पियर-टु-पियर" जडानहरू प्रदान गर्न पनि प्रयोग गरिन्छ। यसको सरल डिजाइनको कारणले गर्दा, यो प्रोटोकलमा धेरै पहिले अनियोजित प्रयोगहरू छन्, तथापि, प्रोटोकलका कमजोरीहरू, जस्तै ग्यारेन्टी डेलिभरीको अभाव, कहिँ पनि हराएको छैन। यो लेख UDP मा ग्यारेन्टी वितरण प्रोटोकल को कार्यान्वयन को वर्णन गर्दछ।
सामग्रीहरू:प्रवेश
प्रोटोकल आवश्यकताहरू
भरपर्दो UDP हेडर
प्रोटोकल को सामान्य सिद्धान्तहरू
टाइमआउट र प्रोटोकल टाइमर
भरपर्दो UDP प्रसारण अवस्था रेखाचित्र
कोडमा गहिरो। प्रसारण नियन्त्रण इकाई
कोडमा गहिरो। राज्यहरु

कोडमा गहिरो। जडानहरू सिर्जना र स्थापना गर्दै
कोडमा गहिरो। टाइमआउटमा जडान बन्द गर्दै
कोडमा गहिरो। डाटा स्थानान्तरण पुनर्स्थापना
भरपर्दो UDP API
निष्कर्षमा
उपयोगी लिङ्क र लेख

प्रवेश

इन्टरनेटको मूल वास्तुकलाले एकसमान ठेगाना स्पेस ग्रहण गर्‍यो जसमा प्रत्येक नोडको विश्वव्यापी र अद्वितीय IP ठेगाना थियो र अन्य नोडहरूसँग सीधा सञ्चार गर्न सक्छ। अब इन्टरनेटमा, वास्तवमा, फरक संरचना छ - विश्वव्यापी IP ठेगानाहरूको एक क्षेत्र र NAT उपकरणहरू पछाडि लुकेका निजी ठेगानाहरू भएका धेरै क्षेत्रहरू।यस वास्तुकलामा, केवल विश्वव्यापी ठेगाना स्थानमा रहेका उपकरणहरूले नेटवर्कमा जो कोहीसँग सजिलैसँग सञ्चार गर्न सक्छन् किनभने तिनीहरूसँग अद्वितीय, विश्वव्यापी रूपमा राउटेबल IP ठेगाना छ। निजी सञ्जालमा रहेको नोडले उही नेटवर्कमा अन्य नोडहरूसँग जडान गर्न सक्छ, र विश्वव्यापी ठेगाना ठाउँमा अन्य ज्ञात नोडहरूमा पनि जडान गर्न सक्छ। यो अन्तरक्रिया सञ्जाल ठेगाना अनुवाद संयन्त्रको कारणले ठूलो मात्रामा हासिल गरिएको छ। NAT उपकरणहरू, जस्तै Wi-Fi राउटरहरू, बहिर्गमन जडानहरूको लागि विशेष अनुवाद तालिका प्रविष्टिहरू सिर्जना गर्दछ र प्याकेटहरूमा IP ठेगानाहरू र पोर्ट नम्बरहरू परिमार्जन गर्दछ। यसले निजी नेटवर्कबाट ग्लोबल ठेगाना स्पेसमा होस्टहरूमा बहिर्गमन जडानहरूलाई अनुमति दिन्छ। तर एकै समयमा, NAT उपकरणहरूले सामान्यतया सबै आगमन ट्राफिकलाई रोक्दछ जबसम्म आगमन जडानहरूको लागि छुट्टै नियमहरू सेट गरिएको छैन।

इन्टरनेटको यो आर्किटेक्चर क्लाइन्ट-सर्भर सञ्चारको लागि पर्याप्त सही छ, जहाँ ग्राहकहरू निजी नेटवर्कहरूमा हुन सक्छन्, र सर्भरहरूको विश्वव्यापी ठेगाना छ। तर यसले दुई नोडहरू बीचको सीधा जडानको लागि कठिनाइहरू सिर्जना गर्दछ बिभिन्न निजी नेटवर्कहरू। पियर-टु-पियर अनुप्रयोगहरू जस्तै भ्वाइस ट्रान्समिशन (स्काइप), कम्प्युटरमा रिमोट पहुँच प्राप्त गर्ने (TeamViewer), वा अनलाइन गेमिङका लागि दुई नोडहरू बीचको सीधा जडान महत्त्वपूर्ण छ।

विभिन्न निजी नेटवर्कहरूमा यन्त्रहरू बीच पियर-टु-पियर जडान स्थापना गर्ने सबैभन्दा प्रभावकारी तरिका होल पंचिङ भनिन्छ। यो प्रविधि प्रायः UDP प्रोटोकलमा आधारित अनुप्रयोगहरूमा प्रयोग गरिन्छ।

तर यदि तपाईंको अनुप्रयोगलाई डाटाको ग्यारेन्टी डेलिभरी चाहिन्छ, उदाहरणका लागि, तपाईंले कम्प्युटरहरू बीच फाइलहरू स्थानान्तरण गर्नुभयो भने, UDP प्रयोग गर्दा धेरै कठिनाइहरू हुनेछ किनभने UDP एक ग्यारेन्टी डेलिभरी प्रोटोकल होइन र प्याकेट डेलिभरी प्रदान गर्दैन, TCP विपरीत। प्रोटोकल।

यस अवस्थामा, ग्यारेन्टी प्याकेट डेलिभरी सुनिश्चित गर्न, आवश्यक कार्यक्षमता प्रदान गर्ने र UDP मा काम गर्ने एप्लिकेसन लेयर प्रोटोकल लागू गर्न आवश्यक छ।

म तुरुन्तै नोट गर्न चाहन्छु कि विभिन्न निजी नेटवर्कहरूमा नोडहरू बीच TCP जडानहरू स्थापना गर्नको लागि त्यहाँ एक TCP होल पंचिंग प्रविधि छ, तर धेरै NAT उपकरणहरू द्वारा यसको लागि समर्थनको कमीको कारण, यसलाई सामान्यतया जडान गर्ने मुख्य तरिकाको रूपमा मानिएको छैन। त्यस्ता नोडहरू।

यस लेखको बाँकीको लागि, म ग्यारेन्टी गरिएको डेलिभरी प्रोटोकलको कार्यान्वयनमा मात्र ध्यान दिनेछु। UDP होल पंचिङ प्रविधिको कार्यान्वयनलाई निम्न लेखहरूमा वर्णन गरिनेछ।

प्रोटोकल आवश्यकताहरू

  1. भरपर्दो प्याकेट वितरण सकारात्मक प्रतिक्रिया संयन्त्र (तथाकथित सकारात्मक स्वीकृति) मार्फत लागू गरियो।
  2. ठूलो डाटाको कुशल स्थानान्तरणको लागि आवश्यकता, अर्थात्। प्रोटोकलले अनावश्यक प्याकेट रिले गर्नबाट जोगिनै पर्छ
  3. यो डिलिवरी पुष्टिकरण संयन्त्र रद्द गर्न सम्भव हुनुपर्छ ("शुद्ध" UDP प्रोटोकलको रूपमा कार्य गर्ने क्षमता)
  4. प्रत्येक सन्देशको पुष्टि संग, आदेश मोड लागू गर्न क्षमता
  5. प्रोटोकलमा डाटा ट्रान्सफरको आधारभूत एकाइ सन्देश हुनुपर्छ

यी आवश्यकताहरू धेरै हदसम्म भरपर्दो डाटा प्रोटोकल आवश्यकताहरूसँग मेल खान्छ जसमा वर्णन गरिएको छ rfc 908 XNUMX२१ и rfc 1151 XNUMX२१, र यो प्रोटोकल विकास गर्दा मैले ती मापदण्डहरूमा भर परेको थिएँ।

यी आवश्यकताहरू बुझ्नको लागि, TCP र UDP प्रोटोकलहरू प्रयोग गरेर दुई नेटवर्क नोडहरू बीच डाटा स्थानान्तरणको समय हेरौं। दुबै अवस्थामा हामीसँग एउटा प्याकेट हराउनेछ।
TCP मा गैर-अन्तर्क्रियात्मक डेटा को स्थानान्तरण:.Net को लागि विश्वसनीय Udp प्रोटोकल को कार्यान्वयन

तपाईले रेखाचित्रबाट देख्न सक्नुहुन्छ, प्याकेट हराएको अवस्थामा, TCP ले हराएको प्याकेट पत्ता लगाउनेछ र हराएको खण्डको संख्या सोधेर प्रेषकलाई रिपोर्ट गर्नेछ।
UDP प्रोटोकल मार्फत डाटा स्थानान्तरण:.Net को लागि विश्वसनीय Udp प्रोटोकल को कार्यान्वयन

UDP ले कुनै पनि हानि पत्ता लगाउने कदम चाल्दैन। UDP प्रोटोकलमा प्रसारण त्रुटिहरूको नियन्त्रण पूर्ण रूपमा अनुप्रयोगको जिम्मेवारी हो।

TCP प्रोटोकलमा त्रुटि पत्ता लगाउने अन्तिम नोडसँग जडान स्थापना गरेर, त्यो जडानको स्थिति भण्डारण गरेर, प्रत्येक प्याकेट हेडरमा पठाइएका बाइटहरूको सङ्ख्या सङ्केत गरेर, र एक स्वीकृति नम्बर प्रयोग गरेर रसिदहरूलाई सूचित गरेर प्राप्त गरिन्छ।

थप रूपमा, कार्यसम्पादन सुधार गर्न (अर्थात् एक स्वीकृति प्राप्त नगरी एक भन्दा बढी खण्ड पठाउनुहोस्), TCP प्रोटोकलले तथाकथित प्रसारण विन्डो प्रयोग गर्दछ - खण्डको प्रेषकले प्राप्त गर्ने अपेक्षा गरेको डेटाको बाइटहरूको संख्या।

TCP प्रोटोकल बारे थप जानकारीको लागि, हेर्नुहोस् rfc 793 XNUMX२१, UDP देखि rfc 768 XNUMX२१जहाँ, वास्तवमा, तिनीहरू परिभाषित छन्।

माथिबाट, यो स्पष्ट छ कि UDP मा भरपर्दो सन्देश डेलिभरी प्रोटोकल सिर्जना गर्नको लागि (यसपछि उल्लेख गरिएको छ। भरपर्दो UDP), यो TCP जस्तै डाटा स्थानान्तरण संयन्त्र लागू गर्न आवश्यक छ। अर्थात्:

  • जडान स्थिति बचत गर्नुहोस्
  • खण्ड नम्बर प्रयोग गर्नुहोस्
  • विशेष पुष्टिकरण प्याकेजहरू प्रयोग गर्नुहोस्
  • प्रोटोकल थ्रुपुट बढाउन सरलीकृत विन्डो मेकानिजम प्रयोग गर्नुहोस्

थप रूपमा, तपाईंलाई आवश्यक छ:

  • जडानको लागि स्रोतहरू आवंटित गर्न सन्देशको सुरुवात संकेत गर्नुहोस्
  • सन्देशको अन्त्य संकेत गर्नुहोस्, प्राप्त सन्देशलाई अपस्ट्रिम अनुप्रयोगमा पास गर्न र प्रोटोकल स्रोतहरू जारी गर्न
  • जडान-विशिष्ट प्रोटोकललाई "शुद्ध" UDP को रूपमा कार्य गर्न वितरण पुष्टिकरण संयन्त्र असक्षम गर्न अनुमति दिनुहोस्।

भरपर्दो UDP हेडर

याद गर्नुहोस् कि एक UDP डाटाग्राम आईपी डाटाग्राममा इन्क्याप्सुलेटेड छ। भरपर्दो UDP प्याकेट UDP डाटाग्राममा उचित रूपमा "रेप" गरिएको छ।
भरपर्दो UDP हेडर encapsulation:.Net को लागि विश्वसनीय Udp प्रोटोकल को कार्यान्वयन

भरपर्दो UDP हेडरको संरचना एकदम सरल छ:

.Net को लागि विश्वसनीय Udp प्रोटोकल को कार्यान्वयन

  • झण्डा - प्याकेज नियन्त्रण झण्डा
  • MessageType - विशिष्ट सन्देशहरूमा सदस्यता लिन अपस्ट्रीम अनुप्रयोगहरू द्वारा प्रयोग गरिएको सन्देश प्रकार
  • TransmissionId - प्रसारणको संख्या, ठेगाना र प्राप्तकर्ताको पोर्ट सहित, जडान पहिचान गर्दछ।
  • प्याकेट नम्बर - प्याकेट नम्बर
  • विकल्पहरू - अतिरिक्त प्रोटोकल विकल्पहरू। पहिलो प्याकेटको अवस्थामा, यो सन्देशको साइज संकेत गर्न प्रयोग गरिन्छ

झण्डाहरू निम्नानुसार छन्:

  • FirstPacket - सन्देशको पहिलो प्याकेट
  • NoAsk - सन्देशलाई सक्षम हुन स्वीकृति संयन्त्र आवश्यक पर्दैन
  • LastPacket - सन्देशको अन्तिम प्याकेट
  • RequestForPacket - पुष्टिकरण प्याकेट वा हराएको प्याकेटको लागि अनुरोध

प्रोटोकल को सामान्य सिद्धान्तहरू

भरपर्दो UDP दुई नोडहरू बीच ग्यारेन्टी सन्देश प्रसारणमा केन्द्रित भएकोले, यो अर्को पक्षसँग जडान स्थापित गर्न सक्षम हुनुपर्छ। जडान स्थापना गर्न, प्रेषकले फर्स्टप्याकेट झण्डाको साथ प्याकेट पठाउँदछ, जसको प्रतिक्रियाको अर्थ जडान स्थापना भएको छ। सबै प्रतिक्रिया प्याकेटहरू, वा, अन्य शब्दहरूमा, स्वीकृति प्याकेटहरू, सधैं PacketNumber फिल्डको मानलाई सफलतापूर्वक प्राप्त प्याकेटहरूको सबैभन्दा ठूलो PacketNumber मान भन्दा बढीमा सेट गर्नुहोस्। पठाइएको पहिलो प्याकेटको लागि विकल्प क्षेत्र सन्देशको आकार हो।

एक समान संयन्त्र जडान समाप्त गर्न प्रयोग गरिन्छ। LastPacket झण्डा सन्देशको अन्तिम प्याकेटमा सेट गरिएको छ। प्रतिक्रिया प्याकेटमा, अन्तिम प्याकेट + 1 को संख्या संकेत गरिएको छ, जुन प्राप्त गर्ने पक्षको लागि सन्देशको सफल डेलिभरी हो।
जडान स्थापना र समाप्ति रेखाचित्र:.Net को लागि विश्वसनीय Udp प्रोटोकल को कार्यान्वयन

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

प्राप्त पक्षले प्याकेटहरू प्राप्त गर्दछ। प्रत्येक प्याकेट प्रसारण विन्डो भित्र पर्दछ कि भनेर जाँच गरिन्छ। विन्डोमा नपरेका प्याकेटहरू र नक्कलहरू फिल्टर गरिएका छन्। किनभने यदि विन्डोको साइज फिक्स गरिएको छ र प्राप्तकर्ता र प्रेषकको लागि समान छ भने, प्याकेटको ब्लक बिना हानि पुर्‍याइएको अवस्थामा, विन्डोलाई डाटाको अर्को ब्लकको प्याकेटहरू प्राप्त गर्न स्थानान्तरण गरिन्छ र डेलिभरी पुष्टि हुन्छ। पठाइयो। यदि कार्य टाइमर द्वारा निर्धारित अवधि भित्र विन्डो भरिएन भने, त्यसपछि एक जाँच सुरु गरिनेछ जसमा प्याकेटहरू डेलिभर गरिएको छैन र पुन: डेलिभरीको लागि अनुरोधहरू पठाइनेछ।
पुन: प्रसारण रेखाचित्र:.Net को लागि विश्वसनीय Udp प्रोटोकल को कार्यान्वयन

टाइमआउट र प्रोटोकल टाइमर

जडान हुन नसक्नुका धेरै कारणहरू छन्। उदाहरणका लागि, यदि प्राप्त गर्ने पार्टी अफलाइन छ भने। यस अवस्थामा, जडान स्थापना गर्न प्रयास गर्दा, जडान टाइमआउट द्वारा बन्द हुनेछ। भरपर्दो UDP कार्यान्वयनले टाइमआउट सेट गर्न दुई टाइमरहरू प्रयोग गर्दछ। पहिलो, काम गर्ने टाइमर, रिमोट होस्टबाट प्रतिक्रियाको लागि पर्खन प्रयोग गरिन्छ। यदि यो प्रेषक पक्षमा फायर हुन्छ भने, अन्तिम पठाइएको प्याकेट पुन: पठाइन्छ। यदि प्राप्तकर्तामा टाइमरको म्याद समाप्त हुन्छ, त्यसपछि हराएको प्याकेटहरूको लागि जाँच गरिन्छ र पुन: वितरणको लागि अनुरोधहरू पठाइन्छ।

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

भरपर्दो UDP प्रसारण अवस्था रेखाचित्र

प्रोटोकलका सिद्धान्तहरू परिमित राज्य मेसिनमा लागू हुन्छन्, जसको प्रत्येक राज्य प्याकेट प्रशोधनको निश्चित तर्कको लागि जिम्मेवार हुन्छ।
भरपर्दो UDP राज्य रेखाचित्र:

.Net को लागि विश्वसनीय Udp प्रोटोकल को कार्यान्वयन

बन्द - वास्तवमा राज्य होइन, यो automaton को लागि एक सुरु र अन्त्य बिन्दु हो। राज्य को लागी बन्द एक ट्रान्समिशन कन्ट्रोल ब्लक प्राप्त हुन्छ, जसले, एसिन्क्रोनस UDP सर्भर लागू गर्दै, उपयुक्त जडानहरूमा प्याकेटहरू फर्वार्ड गर्दछ र राज्य प्रक्रिया सुरु गर्दछ।

FirstPacketSending - प्रारम्भिक अवस्था जसमा सन्देश पठाइँदा बहिर्गमन जडान हुन्छ।

यस अवस्थामा, सामान्य सन्देशहरूको लागि पहिलो प्याकेट पठाइन्छ। पठाउने पुष्टिकरण बिना सन्देशहरूको लागि, यो एक मात्र राज्य हो जहाँ सम्पूर्ण सन्देश पठाइन्छ।

साइकल पठाउँदै - सन्देश प्याकेटहरूको प्रसारणको लागि ग्राउन्ड स्टेट।

राज्यबाट यसलाई संक्रमण FirstPacketSending सन्देशको पहिलो प्याकेट पठाए पछि गरिएको। यो यस अवस्थामा छ कि सबै स्वीकृतिहरू र पुन: प्रसारणको लागि अनुरोधहरू आउँछन्। यसबाट बाहिर निस्कन दुई अवस्थामा सम्भव छ - सन्देशको सफल डेलिभरी वा समय समाप्तिको अवस्थामा।

पहिलो प्याकेट प्राप्त भयो - सन्देश प्राप्तकर्ताको लागि प्रारम्भिक अवस्था।

यसले प्रसारणको सुरुवातको शुद्धता जाँच गर्दछ, आवश्यक संरचनाहरू सिर्जना गर्दछ, र पहिलो प्याकेटको प्राप्तिको स्वीकृति पठाउँदछ।

एउटा सन्देशको लागि जुन एक प्याकेट समावेश गर्दछ र डेलिभरीको प्रमाण प्रयोग नगरी पठाइएको थियो, यो मात्र राज्य हो। यस्तो सन्देश प्रशोधन पछि, जडान बन्द छ।

सम्बद्ध - सन्देश प्याकेटहरू प्राप्त गर्नको लागि आधारभूत अवस्था।

यसले अस्थायी भण्डारणमा प्याकेटहरू लेख्छ, प्याकेट हराएको जाँच गर्दछ, प्याकेटहरूको ब्लक र सम्पूर्ण सन्देशको डेलिभरीको लागि स्वीकृतिहरू पठाउँदछ, र हराएको प्याकेटहरूको पुन: वितरणको लागि अनुरोधहरू पठाउँदछ। सम्पूर्ण सन्देशको सफल प्राप्तिको अवस्थामा, जडान राज्यमा जान्छ पूरा, अन्यथा, टाइमआउट बाहिर निस्कन्छ।

पूरा - सम्पूर्ण सन्देशको सफल प्राप्तिको अवस्थामा जडान बन्द गर्दै।

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

कोडमा गहिरो। प्रसारण नियन्त्रण इकाई

भरपर्दो UDP को मुख्य तत्व मध्ये एक प्रसारण नियन्त्रण ब्लक हो। यस ब्लकको कार्य हालको जडानहरू र सहायक तत्वहरू भण्डारण गर्ने, सम्बन्धित जडानहरूमा आगमन प्याकेटहरू वितरण गर्ने, जडानमा प्याकेटहरू पठाउनको लागि इन्टरफेस प्रदान गर्ने, र प्रोटोकल API कार्यान्वयन गर्ने हो। प्रसारण नियन्त्रण ब्लकले UDP तहबाट प्याकेटहरू प्राप्त गर्दछ र तिनीहरूलाई प्रशोधनका लागि राज्य मेसिनमा पठाउँछ। प्याकेटहरू प्राप्त गर्न, यसले एसिन्क्रोनस UDP सर्भर लागू गर्दछ।
ReliableUdpConnectionControlBlock वर्गका केही सदस्यहरू:

internal class ReliableUdpConnectionControlBlock : IDisposable
{
  // массив байт для указанного ключа. Используется для сборки входящих сообщений    
  public ConcurrentDictionary<Tuple<EndPoint, Int32>, byte[]> IncomingStreams { get; private set;}
  // массив байт для указанного ключа. Используется для отправки исходящих сообщений.
  public ConcurrentDictionary<Tuple<EndPoint, Int32>, byte[]> OutcomingStreams { get; private set; }
  // connection record для указанного ключа.
  private readonly ConcurrentDictionary<Tuple<EndPoint, Int32>, ReliableUdpConnectionRecord> m_listOfHandlers;
  // список подписчиков на сообщения.
  private readonly List<ReliableUdpSubscribeObject> m_subscribers;    
  // локальный сокет    
  private Socket m_socketIn;
  // порт для входящих сообщений
  private int m_port;
  // локальный IP адрес
  private IPAddress m_ipAddress;    
  // локальная конечная точка    
  public IPEndPoint LocalEndpoint { get; private set; }    
  // коллекция предварительно инициализированных
  // состояний конечного автомата
  public StatesCollection States { get; private set; }
  // генератор случайных чисел. Используется для создания TransmissionId
  private readonly RNGCryptoServiceProvider m_randomCrypto;    	
  //...
}

एसिन्क्रोनस UDP सर्भरको कार्यान्वयन:

private void Receive()
{
  EndPoint connectedClient = new IPEndPoint(IPAddress.Any, 0);
  // создаем новый буфер, для каждого socket.BeginReceiveFrom 
  byte[] buffer = new byte[DefaultMaxPacketSize + ReliableUdpHeader.Length];
  // передаем буфер в качестве параметра для асинхронного метода
  this.m_socketIn.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref connectedClient, EndReceive, buffer);
}   

private void EndReceive(IAsyncResult ar)
{
  EndPoint connectedClient = new IPEndPoint(IPAddress.Any, 0);
  int bytesRead = this.m_socketIn.EndReceiveFrom(ar, ref connectedClient);
  //пакет получен, готовы принимать следующий        
  Receive();
  // т.к. простейший способ решить вопрос с буфером - получить ссылку на него 
  // из IAsyncResult.AsyncState        
  byte[] bytes = ((byte[]) ar.AsyncState).Slice(0, bytesRead);
  // получаем заголовок пакета        
  ReliableUdpHeader header;
  if (!ReliableUdpStateTools.ReadReliableUdpHeader(bytes, out header))
  {          
    // пришел некорректный пакет - отбрасываем его
    return;
  }
  // конструируем ключ для определения connection record’а для пакета
  Tuple<EndPoint, Int32> key = new Tuple<EndPoint, Int32>(connectedClient, header.TransmissionId);
  // получаем существующую connection record или создаем новую
  ReliableUdpConnectionRecord record = m_listOfHandlers.GetOrAdd(key, new ReliableUdpConnectionRecord(key, this, header.ReliableUdpMessageType));
  // запускаем пакет в обработку в конечный автомат
  record.State.ReceivePacket(record, header, bytes);
}

प्रत्येक सन्देश स्थानान्तरणको लागि, जडानको बारेमा जानकारी समावेश गर्ने संरचना सिर्जना गरिएको छ। यस्तो संरचना भनिन्छ जडान रेकर्ड.
ReliableUdpConnectionRecord वर्गका केही सदस्यहरू:

internal class ReliableUdpConnectionRecord : IDisposable
{    
  // массив байт с сообщением    
  public byte[] IncomingStream { get; set; }
  // ссылка на состояние конечного автомата    
  public ReliableUdpState State { get; set; }    
  // пара, однозначно определяющая connection record
  // в блоке управления передачей     
  public Tuple<EndPoint, Int32> Key { get; private set;}
  // нижняя граница приемного окна    
  public int WindowLowerBound;
  // размер окна передачи
  public readonly int WindowSize;     
  // номер пакета для отправки
  public int SndNext;
  // количество пакетов для отправки
  public int NumberOfPackets;
  // номер передачи (именно он и есть вторая часть Tuple)
  // для каждого сообщения свой	
  public readonly Int32 TransmissionId;
  // удаленный IP endpoint – собственно получатель сообщения
  public readonly IPEndPoint RemoteClient;
  // размер пакета, во избежание фрагментации на IP уровне
  // не должен превышать MTU – (IP.Header + UDP.Header + RelaibleUDP.Header)
  public readonly int BufferSize;
  // блок управления передачей
  public readonly ReliableUdpConnectionControlBlock Tcb;
  // инкапсулирует результаты асинхронной операции для BeginSendMessage/EndSendMessage
  public readonly AsyncResultSendMessage AsyncResult;
  // не отправлять пакеты подтверждения
  public bool IsNoAnswerNeeded;
  // последний корректно полученный пакет (всегда устанавливается в наибольший номер)
  public int RcvCurrent;
  // массив с номерами потерянных пакетов
  public int[] LostPackets { get; private set; }
  // пришел ли последний пакет. Используется как bool.
  public int IsLastPacketReceived = 0;
  //...
}

कोडमा गहिरो। राज्यहरु

राज्यहरूले भरपर्दो UDP प्रोटोकलको राज्य मेसिन लागू गर्दछ, जहाँ प्याकेटहरूको मुख्य प्रशोधन हुन्छ। सार वर्ग ReliableUdpState ले राज्यको लागि इन्टरफेस प्रदान गर्दछ:

.Net को लागि विश्वसनीय Udp प्रोटोकल को कार्यान्वयन

प्रोटोकलको सम्पूर्ण तर्क माथि प्रस्तुत गरिएका वर्गहरूद्वारा लागू गरिएको छ, एक सहायक वर्गसँग जसले स्थिर विधिहरू प्रदान गर्दछ, जस्तै, जडान रेकर्डबाट ReliableUdp हेडर निर्माण गर्ने।

अर्को, हामी प्रोटोकलको आधारभूत एल्गोरिदमहरू निर्धारण गर्ने इन्टरफेस विधिहरूको कार्यान्वयनलाई विस्तारमा विचार गर्नेछौं।

DisposeByTimeout विधि

DisposeByTimeout विधि टाइमआउट पछि जडान स्रोतहरू जारी गर्न र सफल/असफल सन्देश डेलिभरीको संकेत गर्न जिम्मेवार छ।
ReliableUdpState.DisposeByTimeout:

protected virtual void DisposeByTimeout(object record)
{
  ReliableUdpConnectionRecord connectionRecord = (ReliableUdpConnectionRecord) record;      
  if (record.AsyncResult != null)
  {
    connectionRecord.AsyncResult.SetAsCompleted(false);
  }
  connectionRecord.Dispose();
}

यो राज्यमा मात्र ओभरराइड गरिएको छ पूरा.
पूरा भयो।DisposeByTimeout:

protected override void DisposeByTimeout(object record)
{
  ReliableUdpConnectionRecord connectionRecord = (ReliableUdpConnectionRecord) record;
  // сообщаем об успешном получении сообщения
  SetAsCompleted(connectionRecord);        
}

प्रक्रिया प्याकेट विधि

ProcessPackets विधि प्याकेज वा प्याकेजहरूको अतिरिक्त प्रशोधनको लागि जिम्मेवार छ। प्रत्यक्ष वा प्याकेट पर्खाइ टाइमर मार्फत कल।

सक्षम सम्बद्ध विधि ओभरराइड गरिएको छ र हराएको प्याकेटहरू जाँच गर्न र राज्यमा संक्रमणको लागि जिम्मेवार छ पूरा, अन्तिम प्याकेट प्राप्त गर्ने र सफल चेक पास गर्ने अवस्थामा
एसेम्बल।प्रोसेस प्याकेटहरू:

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.IsDone != 0)
    return;
  if (!ReliableUdpStateTools.CheckForNoPacketLoss(connectionRecord, connectionRecord.IsLastPacketReceived != 0))
  {
    // есть потерянные пакеты, отсылаем запросы на них
    foreach (int seqNum in connectionRecord.LostPackets)
    {
      if (seqNum != 0)
      {
        ReliableUdpStateTools.SendAskForLostPacket(connectionRecord, seqNum);
      }
    }
    // устанавливаем таймер во второй раз, для повторной попытки передачи
    if (!connectionRecord.TimerSecondTry)
    {
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // если после двух попыток срабатываний WaitForPacketTimer 
    // не удалось получить пакеты - запускаем таймер завершения соединения
    StartCloseWaitTimer(connectionRecord);
  }
  else if (connectionRecord.IsLastPacketReceived != 0)
  // успешная проверка 
  {
    // высылаем подтверждение о получении блока данных
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    connectionRecord.State = connectionRecord.Tcb.States.Completed;
    connectionRecord.State.ProcessPackets(connectionRecord);
    // вместо моментальной реализации ресурсов
    // запускаем таймер, на случай, если
    // если последний ack не дойдет до отправителя и он запросит его снова.
    // по срабатыванию таймера - реализуем ресурсы
    // в состоянии Completed метод таймера переопределен
    StartCloseWaitTimer(connectionRecord);
  }
  // это случай, когда ack на блок пакетов был потерян
  else
  {
    if (!connectionRecord.TimerSecondTry)
    {
      ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // запускаем таймер завершения соединения
    StartCloseWaitTimer(connectionRecord);
  }
}

सक्षम साइकल पठाउँदै यो विधि केवल टाइमरमा बोलाइन्छ, र अन्तिम सन्देश पुन: पठाउनको लागि जिम्मेवार छ, साथै जडान बन्द टाइमर सक्षम गर्न।
SendingCycle.ProcessPackets:

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.IsDone != 0)
    return;        
  // отправляем повторно последний пакет 
  // ( в случае восстановления соединения узел-приемник заново отправит запросы, которые до него не дошли)        
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, connectionRecord.SndNext - 1));
  // включаем таймер CloseWait – для ожидания восстановления соединения или его завершения
  StartCloseWaitTimer(connectionRecord);
}

सक्षम पूरा विधिले चलिरहेको टाइमर रोक्छ र सदस्यहरूलाई सन्देश पठाउँछ।
पूरा भयो। प्रक्रिया प्याकेटहरू:

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.WaitForPacketsTimer != null)
    connectionRecord.WaitForPacketsTimer.Dispose();
  // собираем сообщение и передаем его подписчикам
  ReliableUdpStateTools.CreateMessageFromMemoryStream(connectionRecord);
}

प्याकेट विधि प्राप्त गर्नुहोस्

सक्षम पहिलो प्याकेट प्राप्त भयो विधिको मुख्य कार्य भनेको पहिलो सन्देश प्याकेट वास्तवमा इन्टरफेसमा आइपुगेको हो कि होइन भनेर निर्धारण गर्नु हो, र एकल प्याकेट समावेश भएको सन्देश सङ्कलन गर्नु पनि हो।
FirstPacketReceived.ReceivePacket:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket))
    // отбрасываем пакет
    return;
  // комбинация двух флагов - FirstPacket и LastPacket - говорит что у нас единственное сообщение
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket) &
      header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
  {
    ReliableUdpStateTools.CreateMessageFromSinglePacket(connectionRecord, header, payload.Slice(ReliableUdpHeader.Length, payload.Length));
    if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.NoAsk))
    {
      // отправляем пакет подтверждение          
      ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    }
    SetAsCompleted(connectionRecord);
    return;
  }
  // by design все packet numbers начинаются с 0;
  if (header.PacketNumber != 0)          
    return;
  ReliableUdpStateTools.InitIncomingBytesStorage(connectionRecord, header);
  ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
  // считаем кол-во пакетов, которые должны прийти
  connectionRecord.NumberOfPackets = (int)Math.Ceiling((double) ((double) connectionRecord.IncomingStream.Length/(double) connectionRecord.BufferSize));
  // записываем номер последнего полученного пакета (0)
  connectionRecord.RcvCurrent = header.PacketNumber;
  // после сдвинули окно приема на 1
  connectionRecord.WindowLowerBound++;
  // переключаем состояние
  connectionRecord.State = connectionRecord.Tcb.States.Assembling;
  // если не требуется механизм подтверждение
  // запускаем таймер который высвободит все структуры         
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.NoAsk))
  {
    connectionRecord.CloseWaitTimer = new Timer(DisposeByTimeout, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
  }
  else
  {
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
  }
}

सक्षम साइकल पठाउँदै यो विधि वितरण स्वीकृति र पुन: प्रसारण अनुरोधहरू स्वीकार गर्न ओभरराइड गरिएको छ।
SendingCycle.ReceivePacket:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (connectionRecord.IsDone != 0)
    return;
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.RequestForPacket))
    return;
  // расчет конечной границы окна
  // берется граница окна + 1, для получения подтверждений доставки
  int windowHighestBound = Math.Min((connectionRecord.WindowLowerBound + connectionRecord.WindowSize), (connectionRecord.NumberOfPackets));
  // проверка на попадание в окно        
  if (header.PacketNumber < connectionRecord.WindowLowerBound || header.PacketNumber > windowHighestBound)
    return;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // проверить на последний пакет:
  if (header.PacketNumber == connectionRecord.NumberOfPackets)
  {
    // передача завершена
    Interlocked.Increment(ref connectionRecord.IsDone);
    SetAsCompleted(connectionRecord);
    return;
  }
  // это ответ на первый пакет c подтверждением         
  if ((header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket) && header.PacketNumber == 1))
  {
    // без сдвига окна
    SendPacket(connectionRecord);
  }
  // пришло подтверждение о получении блока данных
  else if (header.PacketNumber == windowHighestBound)
  {
    // сдвигаем окно прием/передачи
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // обнуляем массив контроля передачи
    connectionRecord.WindowControlArray.Nullify();
    // отправляем блок пакетов
    SendPacket(connectionRecord);
  }
  // это запрос на повторную передачу – отправляем требуемый пакет          
  else
    ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, header.PacketNumber));
}

सक्षम सम्बद्ध रिसिभप्याकेट विधिमा, आगमन प्याकेटहरूबाट सन्देशहरू जम्मा गर्ने मुख्य काम हुन्छ।
संकलन। प्राप्त प्याकेट:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (connectionRecord.IsDone != 0)
    return;
  // обработка пакетов с отключенным механизмом подтверждения доставки
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.NoAsk))
  {
    // сбрасываем таймер
    connectionRecord.CloseWaitTimer.Change(connectionRecord.LongTimerPeriod, -1);
    // записываем данные
    ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
    // если получили пакет с последним флагом - делаем завершаем          
    if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
    {
      connectionRecord.State = connectionRecord.Tcb.States.Completed;
      connectionRecord.State.ProcessPackets(connectionRecord);
    }
    return;
  }        
  // расчет конечной границы окна
  int windowHighestBound = Math.Min((connectionRecord.WindowLowerBound + connectionRecord.WindowSize - 1), (connectionRecord.NumberOfPackets - 1));
  // отбрасываем не попадающие в окно пакеты
  if (header.PacketNumber < connectionRecord.WindowLowerBound || header.PacketNumber > (windowHighestBound))
    return;
  // отбрасываем дубликаты
  if (connectionRecord.WindowControlArray.Contains(header.PacketNumber))
    return;
  // записываем данные 
  ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
  // увеличиваем счетчик пакетов        
  connectionRecord.PacketCounter++;
  // записываем в массив управления окном текущий номер пакета        
  connectionRecord.WindowControlArray[header.PacketNumber - connectionRecord.WindowLowerBound] = header.PacketNumber;
  // устанавливаем наибольший пришедший пакет        
  if (header.PacketNumber > connectionRecord.RcvCurrent)
    connectionRecord.RcvCurrent = header.PacketNumber;
  // перезапускам таймеры        
  connectionRecord.TimerSecondTry = false;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // если пришел последний пакет
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
  {
    Interlocked.Increment(ref connectionRecord.IsLastPacketReceived);
  }
  // если нам пришли все пакеты окна, то сбрасываем счетчик
  // и высылаем пакет подтверждение
  else if (connectionRecord.PacketCounter == connectionRecord.WindowSize)
  {
    // сбрасываем счетчик.      
    connectionRecord.PacketCounter = 0;
    // сдвинули окно передачи
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // обнуление массива управления передачей
    connectionRecord.WindowControlArray.Nullify();
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
  }
  // если последний пакет уже имеется        
  if (Thread.VolatileRead(ref connectionRecord.IsLastPacketReceived) != 0)
  {
    // проверяем пакеты          
    ProcessPackets(connectionRecord);
  }
}

सक्षम पूरा विधिको एक मात्र कार्य सन्देशको सफल डेलिभरीको पुन: स्वीकृति पठाउनु हो।
पूरा भयो।प्याकेट प्राप्त गर्नुहोस्:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // повторная отправка последнего пакета в связи с тем,
  // что последний ack не дошел до отправителя
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
  {
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
  }
}

प्याकेट विधि पठाउनुहोस्

सक्षम FirstPacketSending यो विधिले डाटाको पहिलो प्याकेट पठाउँछ, वा, यदि सन्देशलाई डेलिभरी पुष्टिकरण आवश्यक पर्दैन भने, सम्पूर्ण सन्देश।
FirstPacketSending.SendPacket:

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
  connectionRecord.PacketCounter = 0;
  connectionRecord.SndNext = 0;
  connectionRecord.WindowLowerBound = 0;       
  // если подтверждения не требуется - отправляем все пакеты
  // и высвобождаем ресурсы
  if (connectionRecord.IsNoAnswerNeeded)
  {
    // Здесь происходит отправка As Is
    do
    {
      ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, ReliableUdpStateTools. CreateReliableUdpHeader(connectionRecord)));
      connectionRecord.SndNext++;
    } while (connectionRecord.SndNext < connectionRecord.NumberOfPackets);
    SetAsCompleted(connectionRecord);
    return;
  }
  // создаем заголовок пакета и отправляем его 
  ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
  // увеличиваем счетчик
  connectionRecord.SndNext++;
  // сдвигаем окно
  connectionRecord.WindowLowerBound++;
  connectionRecord.State = connectionRecord.Tcb.States.SendingCycle;
  // Запускаем таймер
  connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}

सक्षम साइकल पठाउँदै यस विधिमा, प्याकेटहरूको ब्लक पठाइन्छ।
SendingCycle.SendPacket:

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{      
  // отправляем блок пакетов      
  for (connectionRecord.PacketCounter = 0;
        connectionRecord.PacketCounter < connectionRecord.WindowSize &&
        connectionRecord.SndNext < connectionRecord.NumberOfPackets;
        connectionRecord.PacketCounter++)
  {
    ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
    ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
    connectionRecord.SndNext++;
  }
  // на случай большого окна передачи, перезапускаем таймер после отправки
  connectionRecord.WaitForPacketsTimer.Change( connectionRecord.ShortTimerPeriod, -1 );
  if ( connectionRecord.CloseWaitTimer != null )
  {
    connectionRecord.CloseWaitTimer.Change( -1, -1 );
  }
}

कोडमा गहिरो। जडानहरू सिर्जना र स्थापना गर्दै

अब जब हामीले आधारभूत राज्यहरू र राज्यहरू ह्यान्डल गर्न प्रयोग गरिएका विधिहरू देखेका छौं, प्रोटोकलले कसरी काम गर्छ भन्ने केही उदाहरणहरू बिस्तारै विस्तार गरौं।
सामान्य अवस्थामा डाटा प्रसारण रेखाचित्र:.Net को लागि विश्वसनीय Udp प्रोटोकल को कार्यान्वयन

सृष्टिलाई विस्तृत रूपमा विचार गर्नुहोस् जडान रेकर्ड जडान गर्न र पहिलो प्याकेट पठाउन। स्थानान्तरण सधैं एप्लिकेसन द्वारा प्रारम्भ गरिएको छ जसले सन्देश पठाउनुहोस् API लाई कल गर्दछ। अर्को, प्रसारण नियन्त्रण ब्लकको StartTransmission विधि आह्वान गरिएको छ, जसले नयाँ सन्देशको लागि डाटा प्रसारण सुरु गर्दछ।
बहिर्गमन जडान सिर्जना गर्दै:

private void StartTransmission(ReliableUdpMessage reliableUdpMessage, EndPoint endPoint, AsyncResultSendMessage asyncResult)
{
  if (m_isListenerStarted == 0)
  {
    if (this.LocalEndpoint == null)
    {
      throw new ArgumentNullException( "", "You must use constructor with parameters or start listener before sending message" );
    }
    // запускаем обработку входящих пакетов
    StartListener(LocalEndpoint);
  }
  // создаем ключ для словаря, на основе EndPoint и ReliableUdpHeader.TransmissionId        
  byte[] transmissionId = new byte[4];
  // создаем случайный номер transmissionId        
  m_randomCrypto.GetBytes(transmissionId);
  Tuple<EndPoint, Int32> key = new Tuple<EndPoint, Int32>(endPoint, BitConverter.ToInt32(transmissionId, 0));
  // создаем новую запись для соединения и проверяем, 
  // существует ли уже такой номер в наших словарях
  if (!m_listOfHandlers.TryAdd(key, new ReliableUdpConnectionRecord(key, this, reliableUdpMessage, asyncResult)))
  {
    // если существует – то повторно генерируем случайный номер 
    m_randomCrypto.GetBytes(transmissionId);
    key = new Tuple<EndPoint, Int32>(endPoint, BitConverter.ToInt32(transmissionId, 0));
    if (!m_listOfHandlers.TryAdd(key, new ReliableUdpConnectionRecord(key, this, reliableUdpMessage, asyncResult)))
      // если снова не удалось – генерируем исключение
      throw new ArgumentException("Pair TransmissionId & EndPoint is already exists in the dictionary");
  }
  // запустили состояние в обработку         
  m_listOfHandlers[key].State.SendPacket(m_listOfHandlers[key]);
}

पहिलो प्याकेट पठाउँदै (पहिलो प्याकेट पठाउने अवस्था):

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
  connectionRecord.PacketCounter = 0;
  connectionRecord.SndNext = 0;
  connectionRecord.WindowLowerBound = 0;       
  // ... 
  // создаем заголовок пакета и отправляем его 
  ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
  // увеличиваем счетчик
  connectionRecord.SndNext++;
  // сдвигаем окно
  connectionRecord.WindowLowerBound++;
  // переходим в состояние SendingCycle
  connectionRecord.State = connectionRecord.Tcb.States.SendingCycle;
  // Запускаем таймер
  connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}

पहिलो प्याकेट पठाएपछि, प्रेषक राज्यमा प्रवेश गर्दछ साइकल पठाउँदै - प्याकेज डेलिभरीको पुष्टिको लागि पर्खनुहोस्।
प्राप्त पक्ष, EndReceive विधि प्रयोग गरेर, पठाइएको प्याकेट प्राप्त गर्दछ, नयाँ सिर्जना गर्दछ जडान रेकर्ड र यो प्याकेट, पूर्व-पार्स गरिएको हेडरको साथ, प्रक्रियाको लागि राज्यको रिसिभप्याकेट विधिमा पास गर्दछ। पहिलो प्याकेट प्राप्त भयो
प्राप्त पक्षमा जडान सिर्जना गर्दै:

private void EndReceive(IAsyncResult ar)
{
  // ...
  // пакет получен
  // парсим заголовок пакета        
  ReliableUdpHeader header;
  if (!ReliableUdpStateTools.ReadReliableUdpHeader(bytes, out header))
  {          
    // пришел некорректный пакет - отбрасываем его
    return;
  }
  // конструируем ключ для определения connection record’а для пакета
  Tuple<EndPoint, Int32> key = new Tuple<EndPoint, Int32>(connectedClient, header.TransmissionId);
  // получаем существующую connection record или создаем новую
  ReliableUdpConnectionRecord record = m_listOfHandlers.GetOrAdd(key, new ReliableUdpConnectionRecord(key, this, header. ReliableUdpMessageType));
  // запускаем пакет в обработку в конечный автомат
  record.State.ReceivePacket(record, header, bytes);
}

पहिलो प्याकेट प्राप्त गर्दै र स्वीकृति पठाउँदै (पहिलो प्याकेट प्राप्त अवस्था):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket))
    // отбрасываем пакет
    return;
  // ...
  // by design все packet numbers начинаются с 0;
  if (header.PacketNumber != 0)          
    return;
  // инициализируем массив для хранения частей сообщения
  ReliableUdpStateTools.InitIncomingBytesStorage(connectionRecord, header);
  // записываем данные пакет в массив
  ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
  // считаем кол-во пакетов, которые должны прийти
  connectionRecord.NumberOfPackets = (int)Math.Ceiling((double) ((double) connectionRecord.IncomingStream.Length/(double) connectionRecord.BufferSize));
  // записываем номер последнего полученного пакета (0)
  connectionRecord.RcvCurrent = header.PacketNumber;
  // после сдвинули окно приема на 1
  connectionRecord.WindowLowerBound++;
  // переключаем состояние
  connectionRecord.State = connectionRecord.Tcb.States.Assembling;  
  if (/*если не требуется механизм подтверждение*/)
  // ...
  else
  {
    // отправляем подтверждение
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
  }
}

कोडमा गहिरो। टाइमआउटमा जडान बन्द गर्दै

टाइमआउट ह्यान्डलिंग विश्वसनीय UDP को एक महत्वपूर्ण भाग हो। एउटा उदाहरणलाई विचार गर्नुहोस् जसमा मध्यवर्ती नोड असफल भयो र दुबै दिशामा डाटा डेलिभरी असम्भव भयो।
टाइमआउट द्वारा जडान बन्द गर्न को लागी रेखाचित्र:.Net को लागि विश्वसनीय Udp प्रोटोकल को कार्यान्वयन

रेखाचित्रबाट देख्न सकिन्छ, प्रेषकको कार्य टाइमर प्याकेटहरूको ब्लक पठाएपछि तुरुन्तै सुरु हुन्छ। यो राज्यको SendPacket विधिमा हुन्छ साइकल पठाउँदै.
कार्य टाइमर सक्षम पार्दै (पठाउने साइकल अवस्था):

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{      
  // отправляем блок пакетов   
  // ...   
  // перезапускаем таймер после отправки
  connectionRecord.WaitForPacketsTimer.Change( connectionRecord.ShortTimerPeriod, -1 );
  if ( connectionRecord.CloseWaitTimer != null )
    connectionRecord.CloseWaitTimer.Change( -1, -1 );
}

जडान सिर्जना हुँदा टाइमर अवधिहरू सेट गरिन्छ। पूर्वनिर्धारित ShortTimerPeriod 5 सेकेन्ड हो। उदाहरणमा, यो 1,5 सेकेन्डमा सेट गरिएको छ।

आगमन जडानको लागि, अन्तिम आगमन डेटा प्याकेट प्राप्त गरेपछि टाइमर सुरु हुन्छ, यो राज्यको रिसिभप्याकेट विधिमा हुन्छ। सम्बद्ध
कार्य टाइमर सक्षम गर्दै (एसेम्बलिङ स्टेट):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // ... 
  // перезапускаем таймеры        
  connectionRecord.TimerSecondTry = false;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // ...
}

काम गर्ने टाइमरको प्रतीक्षा गर्दा आगमन जडानमा थप प्याकेटहरू आएनन्। टाइमर बन्द भयो र ProcessPackets विधि बोलाइयो, जहाँ हराएको प्याकेटहरू फेला परे र पहिलो पटक पुन: डेलिभरी अनुरोधहरू पठाइयो।
पुन: वितरण अनुरोधहरू पठाउँदै (एसेम्बलिङ स्टेट):

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  // ...        
  if (/*проверка на потерянные пакеты */)
  {
    // отправляем запросы на повторную доставку
    // устанавливаем таймер во второй раз, для повторной попытки передачи
    if (!connectionRecord.TimerSecondTry)
    {
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
    connectionRecord.TimerSecondTry = true;
    return;
    }
  // если после двух попыток срабатываний WaitForPacketTimer 
  // не удалось получить пакеты - запускаем таймер завершения соединения
  StartCloseWaitTimer(connectionRecord);
  }
  else if (/*пришел последний пакет и успешная проверка */)
  {
    // ...
    StartCloseWaitTimer(connectionRecord);
  }
  // если ack на блок пакетов был потерян
  else
  { 
    if (!connectionRecord.TimerSecondTry)
    {
      // повторно отсылаем ack
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // запускаем таймер завершения соединения
    StartCloseWaitTimer(connectionRecord);
  }
}

TimerSecondTry चर सेट गरिएको छ साँचो। यो चर काम गर्ने टाइमर पुन: सुरु गर्न जिम्मेवार छ।

प्रेषकको पक्षमा, काम गर्ने टाइमर पनि ट्रिगर हुन्छ र अन्तिम पठाइएको प्याकेट पुनः पठाइन्छ।
जडान बन्द टाइमर सक्षम गर्दै (पठाउने साइकल अवस्था):

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  // ...        
  // отправляем повторно последний пакет 
  // ...        
  // включаем таймер CloseWait – для ожидания восстановления соединения или его завершения
  StartCloseWaitTimer(connectionRecord);
}

त्यस पछि, जडान बन्द टाइमर बाहिर जाने जडानमा सुरु हुन्छ।
ReliableUdpState.StartCloseWaitTimer:

protected void StartCloseWaitTimer(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(connectionRecord.LongTimerPeriod, -1);
  else
    connectionRecord.CloseWaitTimer = new Timer(DisposeByTimeout, connectionRecord, connectionRecord.LongTimerPeriod, -1);
}

जडान बन्द टाइमर टाइमआउट अवधि पूर्वनिर्धारित रूपमा 30 सेकेन्ड हो।

छोटो समय पछि, प्राप्तकर्ताको छेउमा काम गर्ने टाइमर फेरि फायर हुन्छ, अनुरोधहरू फेरि पठाइन्छ, त्यसपछि जडान बन्द टाइमर आगमन जडानको लागि सुरु हुन्छ।

जब क्लोज टाइमरहरू फायर हुन्छन्, दुबै जडान रेकर्डका सबै स्रोतहरू जारी हुन्छन्। प्रेषकले अपस्ट्रीम अनुप्रयोगमा डेलिभरी असफल भएको रिपोर्ट गर्दछ (विश्वसनीय UDP API हेर्नुहोस्).
जडान रेकर्ड स्रोतहरू जारी गर्दै:

public void Dispose()
{
  try
  {
    System.Threading.Monitor.Enter(this.LockerReceive);
  }
  finally
  {
    Interlocked.Increment(ref this.IsDone);
    if (WaitForPacketsTimer != null)
    {
      WaitForPacketsTimer.Dispose();
    }
    if (CloseWaitTimer != null)
    {
      CloseWaitTimer.Dispose();
    }
    byte[] stream;
    Tcb.IncomingStreams.TryRemove(Key, out stream);
    stream = null;
    Tcb.OutcomingStreams.TryRemove(Key, out stream);
    stream = null;
    System.Threading.Monitor.Exit(this.LockerReceive);
  }
}

कोडमा गहिरो। डाटा स्थानान्तरण पुनर्स्थापना

प्याकेट हराएको अवस्थामा डाटा ट्रान्समिशन रिकभरी रेखाचित्र:.Net को लागि विश्वसनीय Udp प्रोटोकल को कार्यान्वयन

टाइमआउटमा जडान बन्द गर्न पहिले नै छलफल गरिएझैं, काम गर्ने टाइमरको म्याद सकिएपछि, रिसीभरले हराएको प्याकेटहरू जाँच गर्नेछ। प्याकेट हराएको अवस्थामा, प्राप्तकर्तामा नपुगेका प्याकेटहरूको संख्याको सूची संकलन गरिनेछ। यी नम्बरहरू एक विशिष्ट जडानको LostPackets array मा प्रविष्ट गरिएका छन्, र पुन: वितरणको लागि अनुरोधहरू पठाइन्छ।
पुन: वितरण प्याकेजहरूमा अनुरोधहरू पठाउँदै (एसेम्बलिङ स्टेट):

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  //...
  if (!ReliableUdpStateTools.CheckForNoPacketLoss(connectionRecord, connectionRecord.IsLastPacketReceived != 0))
  {
    // есть потерянные пакеты, отсылаем запросы на них
    foreach (int seqNum in connectionRecord.LostPackets)
    {
      if (seqNum != 0)
      {
        ReliableUdpStateTools.SendAskForLostPacket(connectionRecord, seqNum);
      }
    }
    // ...
  }
}

प्रेषकले पुन: वितरण अनुरोध स्वीकार गर्नेछ र छुटेको प्याकेटहरू पठाउनेछ। यो ध्यान दिन लायक छ कि यस क्षणमा प्रेषकले पहिले नै जडान बन्द टाइमर सुरु गरिसकेको छ र, जब अनुरोध प्राप्त हुन्छ, यो रिसेट हुन्छ।
हराएको प्याकेटहरू पुन: पठाउँदै (पठाउने साइकल अवस्था):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // ...
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  // сброс таймера закрытия соединения 
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // ...
  // это запрос на повторную передачу – отправляем требуемый пакет          
  else
    ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, header.PacketNumber));
}

रिसेन्ट प्याकेट (चित्रमा प्याकेट #3) आगमन जडानद्वारा प्राप्त हुन्छ। प्राप्त सञ्झ्याल भरिएको छ र सामान्य डाटा प्रसारण पुनर्स्थापित भएको छ कि छैन भनेर हेर्नको लागि जाँच गरिन्छ।
प्राप्त विन्डोमा हिटहरूको लागि जाँच गर्दै (एसेम्बलिङ स्टेट):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // ...
  // увеличиваем счетчик пакетов        
  connectionRecord.PacketCounter++;
  // записываем в массив управления окном текущий номер пакета        
  connectionRecord.WindowControlArray[header.PacketNumber - connectionRecord.WindowLowerBound] = header.PacketNumber;
  // устанавливаем наибольший пришедший пакет        
  if (header.PacketNumber > connectionRecord.RcvCurrent)
    connectionRecord.RcvCurrent = header.PacketNumber;
  // перезапускам таймеры        
  connectionRecord.TimerSecondTry = false;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // ...
  // если нам пришли все пакеты окна, то сбрасываем счетчик
  // и высылаем пакет подтверждение
  else if (connectionRecord.PacketCounter == connectionRecord.WindowSize)
  {
    // сбрасываем счетчик.      
    connectionRecord.PacketCounter = 0;
    // сдвинули окно передачи
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // обнуление массива управления передачей
    connectionRecord.WindowControlArray.Nullify();
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
  }
  // ...
}

भरपर्दो UDP API

डाटा ट्रान्सफर प्रोटोकलसँग अन्तर्क्रिया गर्न, त्यहाँ एक खुला भरपर्दो Udp वर्ग छ, जुन स्थानान्तरण नियन्त्रण ब्लकमा र्यापर हो। यहाँ कक्षाका सबैभन्दा महत्त्वपूर्ण सदस्यहरू छन्:

public sealed class ReliableUdp : IDisposable
{
  // получает локальную конечную точку
  public IPEndPoint LocalEndpoint    
  // создает экземпляр ReliableUdp и запускает
  // прослушивание входящих пакетов на указанном IP адресе
  // и порту. Значение 0 для порта означает использование
  // динамически выделенного порта
  public ReliableUdp(IPAddress localAddress, int port = 0) 
  // подписка на получение входящих сообщений
  public ReliableUdpSubscribeObject SubscribeOnMessages(ReliableUdpMessageCallback callback, ReliableUdpMessageTypes messageType = ReliableUdpMessageTypes.Any, IPEndPoint ipEndPoint = null)    
  // отписка от получения сообщений
  public void Unsubscribe(ReliableUdpSubscribeObject subscribeObject)
  // асинхронно отправить сообщение 
  // Примечание: совместимость с XP и Server 2003 не теряется, т.к. используется .NET Framework 4.0
  public Task<bool> SendMessageAsync(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, CancellationToken cToken)
  // начать асинхронную отправку сообщения
  public IAsyncResult BeginSendMessage(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, AsyncCallback asyncCallback, Object state)
  // получить результат асинхронной отправки
  public bool EndSendMessage(IAsyncResult asyncResult)  
  // очистить ресурсы
  public void Dispose()    
}

सन्देशहरू सदस्यता द्वारा प्राप्त गरिन्छ। कलब्याक विधिको लागि प्रतिनिधि हस्ताक्षर:

public delegate void ReliableUdpMessageCallback( ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteClient );

Сообщение:

public class ReliableUdpMessage
{
  // тип сообщения, простое перечисление
  public ReliableUdpMessageTypes Type { get; private set; }
  // данные сообщения
  public byte[] Body { get; private set; }
  // если установлено в true – механизм подтверждения доставки будет отключен
  // для передачи конкретного сообщения
  public bool NoAsk { get; private set; }
}

एक विशेष सन्देश प्रकार र/वा विशिष्ट प्रेषकको सदस्यता लिन, दुई वैकल्पिक प्यारामिटरहरू प्रयोग गरिन्छ: ReliableUdpMessageTypes messageType र IPEndPoint ipEndPoint।

सन्देश प्रकारहरू:

public enum ReliableUdpMessageTypes : short
{ 
  // Любое
  Any = 0,
  // Запрос к STUN server 
  StunRequest = 1,
  // Ответ от STUN server
  StunResponse = 2,
  // Передача файла
  FileTransfer =3,
  // ...
}

सन्देश एसिन्क्रोनस रूपमा पठाइन्छ; यसको लागि, प्रोटोकलले एसिन्क्रोनस प्रोग्रामिङ मोडेल लागू गर्दछ:

public IAsyncResult BeginSendMessage(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, AsyncCallback asyncCallback, Object state)

सन्देश पठाउने नतिजा साँचो हुनेछ - यदि सन्देश सफलतापूर्वक प्रापकमा पुग्यो र गलत - यदि जडान टाइमआउट द्वारा बन्द गरिएको थियो भने:

public bool EndSendMessage(IAsyncResult asyncResult)

निष्कर्षमा

यस लेखमा धेरै वर्णन गरिएको छैन। थ्रेड मिल्दो संयन्त्र, अपवाद र त्रुटि ह्यान्डलिंग, एसिन्क्रोनस सन्देश पठाउने विधिहरूको कार्यान्वयन। तर प्रोटोकलको मूल, प्याकेटहरू प्रशोधन गर्ने, जडान स्थापना गर्ने र टाइमआउटहरू ह्यान्डल गर्ने तर्कको विवरण तपाईंलाई स्पष्ट हुनुपर्छ।

भरपर्दो डेलिभरी प्रोटोकलको प्रदर्शन संस्करण पहिले परिभाषित आवश्यकताहरू पूरा गर्न बलियो र लचिलो छ। तर म थप्न चाहन्छु कि वर्णन गरिएको कार्यान्वयन सुधार गर्न सकिन्छ। उदाहरणका लागि, थ्रुपुट बढाउन र गतिशील रूपमा टाइमर अवधिहरू परिवर्तन गर्न, प्रोटोकलमा स्लाइडिङ सञ्झ्याल र RTT जस्ता संयन्त्रहरू थप्न सकिन्छ, यो जडान नोडहरू बीच MTU निर्धारण गर्ने संयन्त्र लागू गर्न पनि उपयोगी हुनेछ (तर ठूला सन्देशहरू पठाइएमा मात्र)। ।

तपाईंको ध्यानको लागि धन्यवाद, म तपाईंको टिप्पणी र टिप्पणीहरूको लागि तत्पर छु।

PS विवरणहरूमा रुचि राख्नेहरू वा केवल प्रोटोकल परीक्षण गर्न चाहनेहरूका लागि, GitHube मा परियोजनाको लिङ्क:
भरपर्दो UDP परियोजना

उपयोगी लिङ्क र लेख

  1. TCP प्रोटोकल विशिष्टता: अंग्रेजीमा и रुसीमा
  2. UDP प्रोटोकल विशिष्टता: अंग्रेजीमा и रुसीमा
  3. RUDP प्रोटोकलको छलफल: मस्यौदा-ietf-sigtran-reliable-udp-00
  4. भरपर्दो डाटा प्रोटोकल: rfc 908 XNUMX२१ и rfc 1151 XNUMX२१
  5. UDP मा वितरण पुष्टिकरण को एक सरल कार्यान्वयन: .NET र UDP मार्फत तपाईंको नेटवर्किङको पूर्ण नियन्त्रण लिनुहोस्
  6. NAT ट्राभर्सल मेकानिज्महरू वर्णन गर्ने लेख: नेटवर्क ठेगाना अनुवादकहरूमा पियर-टू-पियर सञ्चार
  7. एसिन्क्रोनस प्रोग्रामिङ मोडेलको कार्यान्वयन: CLR एसिन्क्रोनस प्रोग्रामिङ मोडेल लागू गर्दै и IAsyncResult डिजाइन ढाँचा कसरी कार्यान्वयन गर्ने
  8. कार्य-आधारित एसिन्क्रोनस ढाँचा (TAP मा APM) मा एसिन्क्रोनस प्रोग्रामिङ मोडेल पोर्ट गर्दै:
    TPL र परम्परागत .NET एसिन्क्रोनस प्रोग्रामिङ
    अन्य एसिन्क्रोनस ढाँचा र प्रकारहरूसँग अन्तरक्रिया गर्नुहोस्

अपडेट: धन्यवाद mayorovp и sidristij इन्टरफेसमा कार्य थप्ने विचारको लागि। पुरानो अपरेटिङ सिस्टमसँग पुस्तकालयको अनुकूलता उल्लङ्घन गरिएको छैन, किनभने चौथो फ्रेमवर्कले XP र 4 सर्भर दुवैलाई समर्थन गर्दछ।

स्रोत: www.habr.com

एक टिप्पणी थप्न