"Boost.Asio C ++ नेटवर्क प्रोग्रामिंग।" अध्याय 3: इको सर्वर / क्लाइंट

सभी को नमस्कार!
मैं जॉन टोरजो की पुस्तक Boost.Asio C ++ नेटवर्क प्रोग्रामिंग का अनुवाद करना जारी रखता हूं।

सामग्री:


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



सभी कोड यहां प्रस्तुत नहीं किए जाएंगे, लेकिन इसके कुछ हिस्सों को, सभी कोड लेख के अंत में लिंक पर देखे जा सकते हैं।


टीसीपी इको सर्वर / क्लाइंट


टीसीपी के लिए, हमें एक अतिरिक्त लाभ हो सकता है, प्रत्येक संदेश चरित्र '\ n' के साथ समाप्त होता है। एक सिंक्रोनस सर्वर / क्लाइंट इको लिखना बहुत सरल है।
हम एक तुल्यकालिक क्लाइंट, एक सिंक्रोनस सर्वर, एक एसिंक्रोनस क्लाइंट और एक एसिंक्रोनस सर्वर जैसे कार्यक्रमों के उदाहरण देंगे।

टीसीपी तुल्यकालिक ग्राहक

अधिकांश गैर-तुच्छ उदाहरणों में, आमतौर पर क्लाइंट कोड सर्वर की तुलना में बहुत सरल होता है (क्योंकि सर्वर को कई क्लाइंट से निपटना चाहिए)।
निम्नलिखित उदाहरण नियम का अपवाद है:

ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 8001); size_t read_complete(char * buf, const error_code & err, size_t bytes) { if ( err) return 0; bool found = std::find(buf, buf + bytes, '\n') < buf + bytes; // we read one-by-one until we get to enter, no buffering return found ? 0 : 1; } void sync_echo(std::string msg) { msg += "\n"; ip::tcp::socket sock(service); sock.connect(ep); sock.write_some(buffer(msg)); char buf[1024]; int bytes = read(sock, buffer(buf), boost::bind(read_complete,buf,_1,_2)); std::string copy(buf, bytes - 1); msg = msg.substr(0, msg.size() - 1); std::cout << "server echoed our " << msg << ": "<< (copy == msg ? "OK" : "FAIL") << std::endl; sock.close(); } int main(int argc, char* argv[]) { char* messages[] = { "John says hi", "so does James", "Lucy just got home", "Boost.Asio is Fun!", 0 }; boost::thread_group threads; for ( char ** message = messages; *message; ++message) { threads.create_thread( boost::bind(sync_echo, *message)); boost::this_thread::sleep( boost::posix_time::millisec(100)); } threads.join_all(); } 

sync_echo फ़ंक्शन पर ध्यान sync_echo । इसमें सर्वर से कनेक्ट करने के लिए सभी तर्क हैं, इसे एक संदेश भेजता है और वापसी प्रतिक्रिया की प्रतीक्षा करता है।
आपने देखा कि हम read() लिए मुफ्त read() फ़ंक्शन का उपयोग करते हैं, क्योंकि हम चरित्र '\ n' तक पूरा संदेश प्राप्त करना चाहते हैं। sock.read_some() फ़ंक्शन पर्याप्त नहीं होगा, क्योंकि यह केवल वही पढ़ेगा जो उपलब्ध है, लेकिन संपूर्ण संदेश बिल्कुल नहीं।
रीड () फ़ंक्शन का तीसरा तर्क अंतिम हैंडलर है। यदि संदेश पूरी तरह से पढ़ा गया है तो यह 0 पर वापस आ जाएगा। अन्यथा, अधिकतम बफर आकार वापस आ जाता है, जिसे अगले चरण में पढ़ा जा सकता है (जब तक read पूरा read )। हमारे मामले में, 1 हमेशा लौटाया जाएगा, क्योंकि हम गलती से ज़रूरत से ज़्यादा नहीं पढ़ना चाहते हैं।
main() हम कई थ्रेड बनाते हैं; ग्राहक को भेजे जाने वाले प्रत्येक संदेश के लिए एक धागा, और उनके पूरा होने तक प्रतीक्षा करें। यदि आप प्रोग्राम चलाते हैं, तो आपको निम्न आउटपुट दिखाई देंगे:

 server echoed our John says hi: OK server echoed our so does James: OK server echoed our Lucy just got home: OK server echoed our Boost.Asio is Fun!: OK 

कृपया ध्यान दें कि चूंकि हम एक तुल्यकालिक ग्राहक के साथ काम कर रहे हैं, इसलिए service.run() को कॉल करने की कोई आवश्यकता नहीं है।

टीसीपी तुल्यकालिक सर्वर

एक तुल्यकालिक इको सर्वर लिखना काफी सरल है, जैसा कि निम्नलिखित कोड टुकड़ा में दिखाया गया है:

 io_service service; size_t read_complete(char * buff, const error_code & err, size_t bytes) { if ( err) return 0; bool found = std::find(buff, buff + bytes, '\n') < buff + bytes; // we read one-by-one until we get to enter, no buffering return found ? 0 : 1; } void handle_connections() { ip::tcp::acceptor acceptor(service, ip::tcp::endpoint(ip::tcp::v4(),8001)); char buff[1024]; while ( true) { ip::tcp::socket sock(service); acceptor.accept(sock); int bytes = read(sock, buffer(buff), boost::bind(read_complete,buff,_1,_2)); std::string msg(buff, bytes); sock.write_some(buffer(msg)); sock.close(); } } int main(int argc, char* argv[]) { handle_connections(); } 

सभी सर्वर लॉजिक handle_connections() में संलग्न है। चूंकि यह सिंगल-थ्रेडेड है, हम एक नए क्लाइंट को स्वीकार करते हैं, जो संदेश उसने भेजा है, उसे पढ़ें, उसे वापस भेजें, और फिर अगले क्लाइंट की प्रतीक्षा करें। कहते हैं, अगर दो क्लाइंट एक साथ जुड़ते हैं, तो दूसरे को इंतजार करना होगा जबकि सर्वर पहले क्लाइंट की सेवा करता है।
कृपया ध्यान दें कि चूंकि हम समकालिक रूप से काम कर रहे हैं, इसलिए service.run() को कॉल करने की आवश्यकता नहीं है।

टीसीपी अतुल्यकालिक ग्राहक

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

 #define MEM_FN(x) boost::bind(&self_type::x, shared_from_this()) #define MEM_FN1(x,y) boost::bind(&self_type::x, shared_from_this(),y) #define MEM_FN2(x,y,z) boost::bind(&self_type::x, shared_from_this(),y,z) class talk_to_svr : public boost::enable_shared_from_this<talk_to_svr>,boost::noncopyable { typedef talk_to_svr self_type; talk_to_svr(const std::string & message) : sock_(service), started_(true),message_(message) {} void start(ip::tcp::endpoint ep) { sock_.async_connect(ep, MEM_FN1(on_connect,_1)); } public: typedef boost::system::error_code error_code; typedef boost::shared_ptr<talk_to_svr> ptr; static ptr start(ip::tcp::endpoint ep, const std::string & message) { ptr new_(new talk_to_svr(message)); new_->start(ep); return new_; } void stop() { if ( !started_) return; started_ = false; sock_.close(); } bool started() { return started_; } ... private: ip::tcp::socket sock_; enum { max_msg = 1024 }; char read_buffer_[max_msg]; char write_buffer_[max_msg]; bool started_; std::string message_; }; 

हम हमेशा talk_to_svr पर साझा किए गए पॉइंटर्स का उपयोग करना चाहते हैं, जिससे कि talk_to_svr उदाहरण में अतुल्यकालिक संचालन हो, यह उदाहरण जीवित रहा। स्टैक पर talk_to_svr इंस्टेंस बनाने जैसी त्रुटियों से बचने के लिए, हमने कंस्ट्रक्टर को निजी बना दिया और कॉपी कंस्ट्रक्टर को प्रतिबंधित कर दिया (विरासत से प्राप्त boost::noncopyable )।
हमारे पास बुनियादी कार्य हैं जैसे कि start(), stop() , और started() जो उनके नाम कहते हैं वही करते हैं। एक कनेक्शन बनाने के लिए, बस talk_to_svr::start(endpoint, message) कॉल करें। हमारे पास पढ़ने और लिखने के लिए बफ़र भी हैं ( read_buffer_ और write_buffer_ )।
जैसा कि पहले बताया गया है, निम्नलिखित लाइनें बहुत अलग हैं:

 // equivalent to "sock_.async_connect(ep, MEM_FN1(on_connect,_1));" sock_.async_connect(ep, boost::bind(&talk_to_svr::on_connect,shared_ptr_from_this(),_1)); sock_.async_connect(ep, boost::bind(&talk_to_svr::on_connect,this,_1)); 

पहले मामले में, हम सही ढंग से अंतिम हैंडलर async_connect , यह साझा किए गए पॉइंटर को talk_to_server उदाहरण तक सहेज देगा, जब तक कि यह अंतिम हैंडलर को कॉल न कर दे, जिससे यह सुनिश्चित हो जाता है कि ऐसा होने पर हम अभी भी जीवित हैं।
बाद के मामले में, हम गलत तरीके से अंतिम हैंडलर बनाते हैं। जब तक talk_to_server आवृत्ति को talk_to_server , तब तक वह पहले ही हटा दी जा सकती है!
सॉकेट में पढ़ने और लिखने के लिए, हम निम्नलिखित कोड टुकड़े का उपयोग करेंगे:

 void do_read() { async_read(sock_, buffer(read_buffer_), MEM_FN2(read_complete,_1,_2), MEM_FN2(on_read,_1,_2)); } void do_write(const std::string & msg) { if ( !started() ) return; std::copy(msg.begin(), msg.end(), write_buffer_); sock_.async_write_some( buffer(write_buffer_, msg.size()), MEM_FN2(on_write,_1,_2)); } size_t read_complete(const boost::system::error_code & err, size_t bytes) { // similar to the one shown in TCP Synchronous Client } 

do_read() फ़ंक्शन पहले सुनिश्चित करता है कि हम सर्वर से संदेश पढ़ रहे हैं, जिसके बाद on_read() कहा जाता है। do_write() फ़ंक्शन पहले संदेश को बफर में कॉपी करता है (एक मौका है कि संदेश समय के साथ दायरे से बाहर हो सकता है और on_write() हो सकता है), और फिर सुनिश्चित करें कि वास्तविक रिकॉर्डिंग के बाद on_write() कॉल होती है।
और सबसे महत्वपूर्ण कार्य जिसमें कक्षा का मुख्य तर्क शामिल है:

 void on_connect(const error_code & err) { if ( !err) do_write(message_ + "\n"); else stop(); } void on_read(const error_code & err, size_t bytes) { if ( !err) { std::string copy(read_buffer_, bytes - 1); std::cout << "server echoed our " << message_ << ": "<< (copy == message_ ? "OK" : "FAIL") << std::endl; } stop(); } void on_write(const error_code & err, size_t bytes) { do_read(); } 

उसके बाद, हम सर्वर से एक संदेश कनेक्ट और भेजते हैं, do_write() । जब लिखने का कार्य पूरा हो जाता है, तो on_write() कहा जाता है, जो do_read() फ़ंक्शन को आरंभ करता है। जब do_read() पूरा हो जाता है, do_read() कहा जाता है, यहां हम सिर्फ यह do_read() कि सर्वर से संदेश वही है जो हमने उसे भेजा था और उससे बाहर निकलें।
हम सर्वर को केवल तीन संदेश भेजेंगे ताकि यह और अधिक रोचक बन सके:

 int main(int argc, char* argv[]) { ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 8001); char* messages[] = { "John says hi", "so does James", "Lucy got home", 0 }; for ( char ** message = messages; *message; ++message) { talk_to_svr::start( ep, *message); boost::this_thread::sleep( boost::posix_time::millisec(100)); } service.run(); } 

पिछले कोड के टुकड़े में, निम्नलिखित कोड उत्पन्न होगा:

 server echoed our John says hi: OK server echoed our so does James: OK server echoed our Lucy just got home: OK 


टीसीपी अतुल्यकालिक सर्वर

जैसा कि नीचे दिखाया गया है, मुख्य कार्य अतुल्यकालिक ग्राहक के कार्यों के समान हैं:

 class talk_to_client : public boost::enable_shared_from_this<talk_to_client>, boost::noncopyable { typedef talk_to_client self_type; talk_to_client() : sock_(service), started_(false) {} public: typedef boost::system::error_code error_code; typedef boost::shared_ptr<talk_to_client> ptr; void start() { started_ = true; do_read(); } static ptr new_() { ptr new_(new talk_to_client); return new_; } void stop() { if ( !started_) return; started_ = false; vsock_.close(); } ip::tcp::socket & sock() { return sock_;} ... private: ip::tcp::socket sock_; enum { max_msg = 1024 }; char read_buffer_[max_msg]; char write_buffer_[max_msg]; bool started_; }; 

यह एक बहुत ही सरल इको सर्वर है; is_started() फ़ंक्शन की कोई आवश्यकता नहीं है। प्रत्येक क्लाइंट के लिए, हम केवल उस संदेश को पढ़ते हैं जो उसने भेजा था, उसी संदेश को वापस भेजें और कनेक्शन को बंद करें।
do_read(), do_write() और read_complete() बिल्कुल अतुल्यकालिक टीसीपी क्लाइंट के समान हैं।
कक्षा का मुख्य तर्क on_read() और on_write() फ़ंक्शन में है:

 void on_read(const error_code & err, size_t bytes) { if ( !err) { std::string msg(read_buffer_, bytes); do_write(msg + "\n"); } stop(); } void on_write(const error_code & err, size_t bytes) { do_read(); } 

ग्राहकों के साथ काम इस प्रकार है:

 ip::tcp::acceptor acceptor(service, ip::tcp::endpoint(ip::tcp::v4(), 8001)); void handle_accept(talk_to_client::ptr client, const error_code & err) { client->start(); talk_to_client::ptr new_client = talk_to_client::new_(); acceptor.async_accept(new_client->sock(), boost::bind(handle_accept,new_client,_1)); } int main(int argc, char* argv[]) { talk_to_client::ptr client = talk_to_client::new_(); acceptor.async_accept(client->sock(), boost::bind(handle_accept,client,_1)); service.run(); } 

जब भी कोई क्लाइंट सर्वर से जुड़ता है, तो handle_accep t handle_accep , जो इस क्लाइंट से एसिंक्रोनस रूप से पढ़ना शुरू कर देता है और एसिंक्रोनस रूप से नए क्लाइंट की प्रतीक्षा करता है।

UDP इको सर्वर / क्लाइंट


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

यूडीपी सिंक्रोनस इको क्लाइंट

यूपीओ इको क्लाइंट टीसीपी इको क्लाइंट की तुलना में थोड़ा सरल है:

 ip::udp::endpoint ep( ip::address::from_string("127.0.0.1"), 8001); void sync_echo(std::string msg) { ip::udp::socket sock(service, ip::udp::endpoint(ip::udp::v4(), 0) ); sock.send_to(buffer(msg), ep); char buff[1024]; ip::udp::endpoint sender_ep; int bytes = sock.receive_from(buffer(buff), sender_ep); std::string copy(buff, bytes); std::cout << "server echoed our " << msg << ": "<< (copy == msg ? "OK" : "FAIL") << std::endl; sock.close(); } int main(int argc, char* argv[]) { char* messages[] = { "John says hi", "so does James", "Lucy got home", 0 }; boost::thread_group threads; for ( char ** message = messages; *message; ++message) { threads.create_thread( boost::bind(sync_echo, *message)); boost::this_thread::sleep( boost::posix_time::millisec(100)); } threads.join_all(); } 

सभी तर्क synch_echo() फ़ंक्शन में है; सर्वर से कनेक्ट करना, संदेश भेजना, सर्वर से प्रतिक्रिया संदेश प्राप्त करना और कनेक्शन बंद करना।

यूडीपी सिंक्रोनस इको सर्वर

UDP इको सर्वर सबसे सरल सर्वर है जिसे आप लिख सकते हैं:

 io_service service; void handle_connections() { char buff[1024]; ip::udp::socket sock(service, ip::udp::endpoint(ip::udp::v4(), 8001)); while ( true) { ip::udp::endpoint sender_ep; int bytes = sock.receive_from(buffer(buff), sender_ep); std::string msg(buff, bytes); sock.send_to(buffer(msg), sender_ep); } } int main(int argc, char* argv[]) { handle_connections(); } 

यहां सब कुछ बहुत सरल है और अपने लिए बोलता है।
आइए हम एक अभ्यास के रूप में पाठक को एसिंक्रोनस यूडीपी सर्वर और क्लाइंट के लेखन को छोड़ दें।

सारांश


हमने कुछ एप्लिकेशन लिखे और अंत में Boost.Asio के साथ शुरुआत की। इस लाइब्रेरी से शुरुआत करने के लिए ये एप्लिकेशन बहुत अच्छे हैं।
अगले अध्याय में, हम अधिक जटिल ग्राहक / सर्वर एप्लिकेशन बनाएंगे, हम मेमोरी लीक, गतिरोध और इसी तरह की त्रुटियों से बचना सीखेंगे।

आप सभी को बहुत बहुत धन्यवाद!

इस लेख के लिए संसाधन: लिंक

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


All Articles