मेलंगे - नेटवर्क प्रोटोकॉल के लिए डीएसएल

सभी प्रोग्रामर को जल्द या बाद में डेटा ट्रांसफर करना होगा। यह कोई रहस्य नहीं है कि जावा में लगभग> 9000 सीरियलाइजेशन लाइब्रेरी हैं, और सी ++ में वे वहां प्रतीत होते हैं, लेकिन वे प्रतीत नहीं होते हैं। सौभाग्य से बहुमत के लिए, Google प्रोटोबॉफ़ कुछ साल पहले दिखाई दिया, जिसने डेटा संरचनाओं को परिभाषित करने के लिए एक काफी सुविधाजनक तरीका लाया और जल्दी से लोकप्रिय प्यार जीता। यह वास्तव में जनता के लिए उपलब्ध पहली लाइब्रेरी थी, जो आपको XML जैसी किसी चीज़ के साथ गड़बड़ किए बिना नेटवर्क पर तैयार डेटा संरचनाओं को चलाने की अनुमति देती है। 2008 में यार्ड में था।

थोड़ा पीछे चलते हैं। 2006 में, एक साधारण भारतीय प्रोग्रामर (चाहे वह कितना भी संदिग्ध लग सकता हो!) अनिल माधवपेड्डी, जो दुनिया के सबसे प्रसिद्ध OCaml डेवलपर्स में से एक हैं और नवीनतम पुस्तक Real World OCaml के लेखक हैं, ने कैम्ब्रिज में अपनी Ph.D की थीसिस का बचाव किया। यह उसके बारे में है जो मैं आज आपको बताऊंगा।

अनिल तुरंत Google से आगे बढ़ गया। उन्होंने तुरंत सोचा कि लोग आमतौर पर नेटवर्क पर कुछ औपचारिक डेटा संरचनाएं क्यों भेजते हैं। किसी प्रकार के प्रोटोकॉल को लागू करने के लिए। और एक प्रोटोकॉल क्या है? यह किसी प्रकार की परिमित स्टेट मशीन है । और हम एक जटिल, अच्छी तरह से डिजाइन और समय-परीक्षण किए गए प्रोटोकॉल का एक अच्छा उदाहरण कहां से प्राप्त कर सकते हैं? हाँ, नियमित नेटवर्क स्टैक पर सही! इसलिए, हमने नेटवर्क डेटा संरचनाओं और प्रोटोकॉल का एक सेट लिया: ईथरनेट फ्रेम, आईपीवी 4, आईसीएमपी, टीसीपी, यूडीपी, एसएसएच, डीएनएस और डीएचसीपी और समस्या का विवरण: इनमें से अधिकांश प्रोटोकॉल (विशेष रूप से एसएसएच और डीएनएस) लागू होते हैं, जिसे "हाथ" कहा जाता है, और मैं चाहता हूं कि सी का कोई बफर ओवरफ्लो न हो, सभी बदलाव स्वचालित रूप से किए गए थे, यह सब सत्यापित किया जा सकता है, और इसलिए कि यह जल्दी से काम करता है, और हमेशा की तरह नहीं।

चूँकि कोई भी शोध प्रबंध नहीं पढ़ेगा, मैं तुरंत कहूँगा: यह संभव से अधिक था। कार्य के परिणामों के आधार पर, DNS और SSH सर्वर के संदर्भ कार्यान्वयन को लिखा गया था और BIND और OpenSSH के साथ तुलना की गई थी। पारंपरिक OCaml कार्यान्वयन की तुलना में, तुच्छ से प्रदर्शन लाभ लगभग दुगुना हो जाता है। इसके अलावा, SSH पर RFC में एक त्रुटि पाई गई (कार्यसमूह को अधिसूचित किया गया था और RFC ठीक किया गया था)। क्या किया गया है, और इसके साथ कैसे रहना है, इसके बारे में कट के नीचे पढ़ें।

एमपीएल

सबसे पहले, OCaml पर दो वर्णन भाषाओं और उनके अनुवादकों को लिखा गया था। पहली भाषा मेटा पैकेट भाषा या एमपीएल है, जो पैकेज की संरचना का वर्णन करती है। सामान्य शब्दों में, यह प्रोटोबॉफ़ का एक एनालॉग है, लेकिन काफी नहीं है। सबसे पहले, MPL आपकी संरचना में कोई ओवरहेड नहीं जोड़ता है। आम तौर पर। कोई अतिरिक्त बिट्स डेटा प्रकार या कुछ और का संकेत नहीं देता है। एक तरफ, यह अनुमति नहीं देता है, जैसे कि प्रोटोबुफ़ में, नए फ़ील्ड को आसानी से जोड़कर संरचना का विस्तार करने के लिए, दूसरी तरफ, आप कभी भी प्रोटोबोफ़ का उपयोग करके टीसीपी हेडर नहीं पढ़ेंगे। दूसरे, एमपीएल तुरंत नेटवर्क लॉजिक - पैकेजिंग या अलाइनमेंट के साथ-साथ फील्ड या फील्ड वैल्यू के वैरिएबल सेट जैसी चीजों पर आधारित सभी लॉजिक को लागू करता है, जो स्ट्रक्चर के अन्य क्षेत्रों पर निर्भर करते हैं। एक उदाहरण के लिए, IPv4 हेडर देखें:
packet ipv4 { version: bit[4] const(4); ihl: bit[4] min(5) value(offset(options) / 4); tos_precedence: bit[3] variant { |0 => Routine |1 -> Priority |2 -> Immediate |3 -> Flash |4 -> Flash_override |5 -> ECP |6 -> Internetwork_control |7 -> Network_control }; tos_delay: bit[1] variant {|0 => Normal |1 -> Low}; tos_throughput: bit[1] variant {|0 => Normal |1 -> Low}; tos_reliability: bit[1] variant {|0 => Normal |1 -> Low}; tos_reserved: bit[2] const(0); length: uint16 value(offset(data)); id: uint16; reserved: bit[1] const(0); dont_fragment: bit[1] default(0); can_fragment: bit[1] default(0); frag_offset: bit[13] default(0); ttl: byte; protocol: byte variant {|1->ICMP |2->IGMP |6->TCP |17->UDP}; checksum: uint16 default(0); src: uint32; dest: uint32; options: byte[(ihl * 4) - offset(dest)] align(32); header_end: label; data: byte[length-(ihl*4)]; } 


यहां, पैकेट की सामग्री को डेटा बाइट्स की एक सरणी के रूप में वर्णित किया गया है (ताकि सभी अन्य संभावित प्रोटोकॉल का वर्णन नहीं किया जा सके), लेकिन इसके स्थान पर अच्छी तरह से एक रिकॉर्ड हो सकता है:
 classify (protocol) { |1: "ICMP"-> data: packet icmp(); |2: "TCP" -> data: packet tcp(); |3: "UDP" -> data: packet udp(); }; 


और फिर IPv4 पैकेट को पढ़ते समय, हम तुरंत इसकी सामग्री को पार्स कर देंगे। नतीजतन, किसी भी पैकेज का क्रमांकन (डी) एक आकर्षक कार्य में बदल जाता है, जिसमें डेटा को ठीक से पैक करने के लिए और क्या गठबंधन किया जाना चाहिए, इस पर किसी भी विचार की आवश्यकता नहीं है। इसलिए, लोकप्रिय मैसेजपैक प्रारूप के लगभग पूर्ण कार्यान्वयन ने मुझे लगभग 40 लाइनें ले लीं।

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

एसपीएल

दूसरी भाषा स्टेटकॉल पॉलिसी लैंग्वेज या एसपीएल थी। यह परिमित ऑटोमेटा का वर्णन करने के लिए एक भाषा है, जो हमारे प्रोटोकॉल का दिल है। कड़े शब्दों में, सभी भाषाओं के लिए परिमित राज्य मशीन बनाने के लिए पुस्तकालयों का अस्तित्व मौजूद है। उनसे एसपीएल में केवल कुछ अंतर हैं (ऑटोमोबेटन के प्रोग्राम किए गए कार्य के बजाय इसकी विवरण भाषा के अलावा)। सबसे पहले, SPL कंपाइलर प्रोग्राम मॉडल SPIN के सत्यापन के लिए तुरंत PROMELA में एक विवरण उत्पन्न कर सकता है। ईमानदार होने के लिए, मैं SPIN का पता नहीं लगा सकता था, इसलिए इस स्थान पर मुझे लेखक के शब्द को लेने के लिए मजबूर किया गया था कि यह अच्छा है। दूसरे, राज्य के नाम Receive_NAME और Transmit_NAME (जहां NAME MPL से संदेश प्रकार है) का उपयोग करके, आप MPL से डेटा संरचनाओं के साथ राज्य मशीन को बारीकी से एकीकृत कर सकते हैं। हम इस बारे में बाद में बात करेंगे, लेकिन अब एसएसएच में प्राधिकरण के लिए एक राज्य मशीन के विवरण का एक उदाहरण देखें:
 automaton auth (bool success, bool failed) { Receive_Transport_ServiceAccept_UserAuth; Transmit_Auth_Req_None; Receive_Auth_Failure; do { either { always_allow (Receive_Auth_Banner) { either { Transmit_Auth_Req_Password_Request; auth_decision (success); } or { Transmit_Auth_Req_PublicKey_Request; auth_decision (success); } or { Transmit_Auth_Req_PublicKey_Check; either { Receive_Auth_PublicKeyOK; } or { Receive_Auth_Failure; } } } } or { Notify_Auth_Permanent_Failure; failed = true; } } until (success || failed); } 

जैसा कि आप देख सकते हैं, एसपीएल में राज्य मशीन आपको काफी शाखित तर्क लिखने की अनुमति देती है, एक ही एसपीएल में लिखे फंक्शंस ( ओडिट_डेसिजन ) पर कॉल डालें, और यहां तक ​​कि चर के साथ भी काम कर सकते हैं।

इसके साथ कैसे काम करें?

दुर्भाग्य से, परियोजना (इसे पूरे मेलांगे कहा जाता है) प्रलेखन में बहुत समृद्ध नहीं है, इसका मुख्य स्रोत शोध प्रबंध है। इसलिए, मैंने अवधारणा का एक छोटा सा सबूत लिखने का फैसला किया, पूरे उत्पाद के संचालन का प्रदर्शन किया, और एक ही समय में इस तरह के एक छोटे से त्वरित शुरुआत गाइड। ऐसा करने के लिए, आपको कुछ छोटे नेटवर्क एप्लिकेशन लिखने की आवश्यकता है। एक सरल और स्पष्ट प्रोटोकॉल के साथ एक सरल अनुप्रयोग की भूमिका के लिए, मैंने अच्छे पुराने खेल को चुना - समुद्री लड़ाई। यह हमारी संदेश संरचना जैसा दिखेगा:
 packet message { message_type: byte; message_id: uint16; classify (message_type) { | 0:"Shot" -> row: bit[4]; column: bit[4]; | 1:"ShotResult" -> result: byte variant { |0 -> Missed |1 -> Damaged |2 -> Killed }; | 2:"Disconnect" -> (); }; } 

हमारे पास तीन प्रकार के संदेश हैं: एक शॉट, एक शॉट का परिणाम, और जानकारी जो हम किसी कारण से डिस्कनेक्ट करना चाहते हैं। अब प्रस्तावित प्रोटोकॉल पर एक नज़र डालते हैं:
 automaton seawar () { Initialize; during { multiple(1..) { Ready; either { Transmit_Message_Shot; Receive_Message_ShotResult; } or { Receive_Message_Shot; Transmit_Message_ShotResult; } } } handle { either { Transmit_Message_Disconnect; exit; } or { Receive_Message_Disconnect; exit; } } } 

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

अब देखते हैं कि कोड से इसका उपयोग कैसे किया जाता है। संदेशों को पढ़ने के लिए, हम एक विशेष एमपीएल बफर का उपयोग करेंगे, जो डेटा से भरा होगा - हमारे मामले में, हम उन्हें नेटवर्क से लेते हैं।
 val mutable env_ = Mpl_stdlib.new_env (String.make 4 '\000'); val mutable tick_ = Protocol.init (); method tick state = tick_ <- Protocol.tick tick_ state; method send_message msg = ( Mpl_stdlib.reset env_; self#tick (msg#xmit_statecall :> Protocol.s); if not (Thread.wait_timed_write sock_ 10.) then self#disconnect ~exc_text:"Timeout"; Mpl_stdlib.flush env_ sock_ ); method receive_message = ( Mpl_stdlib.reset env_; if not (Thread.wait_timed_read sock_ 300.) then self#disconnect ~exc_text:"Timeout"; Mpl_stdlib.fill env_ sock_; let msg = Message.unmarshal env_ in let state = Message.recv_statecall msg in self#tick state; msg ); 

कृपया ध्यान दें कि प्रत्येक संदेश, दोनों भेजे और प्राप्त किए बिना, विफल होने पर राज्य मशीन को नए राज्य में संक्रमण का कारण बनता है। संदेश # xmit_statecall और Message.recv_statecall संदेश कॉल इसके लिए ज़िम्मेदार हैं, जो संदेशों (जैसे ShotResult) के आधार पर संबंधित राज्यों ( Transmit_Message_shhotResult और Receive_Message_ShotResult ) के नाम बनाते हैं। इसके कारण, अधिकांश संभावित प्रोग्राम त्रुटियों का पता यहां लगाया जाएगा, जब एक गलत मशीन संक्रमण एक Bad_statecall अपवाद का कारण होगा। उदाहरण के लिए, यदि एआई के मामले में, सब कुछ सरल है - यह एक धागे में काम करता है, बिल्कुल तुल्यकालिक और ऐसे सरल कार्य में कभी कोई समस्या नहीं हो सकती है, तो ग्राफिकल इंटरफ़ेस में सब कुछ अधिक जटिल हो सकता है।


उदाहरण के लिए, सब कुछ कैसे आसानी से "विस्फोट" कर सकता है का एक सरल उदाहरण। ग्राफिकल इंटरफ़ेस के लिए, मैंने ताज़ा रिलीज़ क्यूटी 5.2 फ्रेमवर्क लिया, जिसके लिए हमारे हमवतन दिमित्री कोसारेव ने ओकेमेल बाइंडर्स लिखा (दिलचस्प पर्याप्त, यदि आप चाहें, तो मैं आपको एक अलग पोस्ट में बताऊंगा)। जब एक अलग क्षेत्र में दुश्मन के क्षेत्र की सेल पर क्लिक करते हैं, तो निम्न कोड निष्पादित किया जा सकता है:
 let send_shot col row = ignore(game#send_message (Message.Shot.t ~row:row ~column:col)); let result, state = game#receive_message in (match result with | `ShotResult x -> Board.mark opp_board row col x#result; next_turn state x#result | `Disconnect x -> game#disconnect ~send:false | _ -> game#disconnect ~exc_text:"Unexpected_Message_Type" ~raise_exc:true ~send:true) 

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

कोड की पूरी समीक्षा के साथ लेख को अव्यवस्थित नहीं करने के लिए, मैं स्रोतों के लिए बेहतर लिंक देता हूं, जिसे कोई भी डाउनलोड कर सकता है और देख सकता है।

निष्कर्ष

आगे क्या है, पाठक मुझसे पूछेंगे। खैर, कुछ प्रकार के सीमांत उपकरण जो अब 4 वर्षों से अपडेट नहीं किए गए हैं, एक अयोग्य भाषा में काम कर रहे हैं। अगर मेरे पास Node.js, MongoDB और स्ट्रॉबेरी स्मूदी है तो मुझे यह सब क्यों चाहिए?

पाठक सही है। उपकरण पुराना है, इसमें कई महत्वपूर्ण कमियां हैं जिनका मैंने उल्लेख किया है। लेकिन साथ ही, वह दिखाता है कि किस दिशा में विकास करना है। तो, सभी घोषणात्मक विवरणों, एक ग्राफिकल इंटरफ़ेस और सबसे बेवकूफ एआई सहित संपूर्ण एप्लिकेशन कोड 850 रेखाएं हैं। यह निश्चित रूप से "जावास्क्रिप्ट में 30 लाइनें" नहीं है, लेकिन बहुत अधिक नहीं है।

लगभग 8 साल पहले, यह दिखाया गया था कि नेटवर्क इंटरैक्शन कैसे होना चाहिए और लगभग 6 साल पहले Google ने इसका आधा हिस्सा ही लोकप्रिय कर दिया था। विचार में कोई रॉकेट विज्ञान नहीं है, यह सच है, और व्यक्तिगत रूप से सभी घटकों को लंबे समय से लिखा गया है। आप, USERNAME%, के पास आने वाले नए साल में इस विचार को महसूस करने का एक बड़ा अवसर है, विश्व प्रसिद्ध बनें और दुनिया को गुलाम बनाएं। अच्छा या ऐसा कुछ।

Source: https://habr.com/ru/post/In207764/


All Articles