मास्टेरिंग कंसीडर पर पिछले रूबी मैजिक लेख में, हमने कंसीडर प्राप्त करने के तीन तरीकों का परिचय दिया था जो रूबी डेवलपर्स के रूप में हमारे लिए उपलब्ध हैं। यह लेख तीन-भाग श्रृंखला में पहला है जहां हम प्रत्येक विधि में गहराई से गोता लगाते हैं।
सबसे पहले:बहु-प्रक्रिया . इस पद्धति के साथ एक मास्टर प्रक्रिया कई कार्यकर्ता प्रक्रियाओं के लिए खुद को फोर्क करती है। कार्यकर्ता प्रक्रिया वास्तविक कार्य करती है, जबकि मास्टर श्रमिकों का प्रबंधन करता है।
<ब्लॉकक्वॉट>इस आलेख में उदाहरणों में उपयोग किया गया पूर्ण स्रोत कोड GitHub पर उपलब्ध है, इसलिए आप स्वयं इसका प्रयोग कर सकते हैं।
चलो एक चैट सिस्टम बनाते हैं!
एक चैट सिस्टम बनाना समवर्ती में गोता लगाने का एक अच्छा तरीका है। हमें एक चैट सिस्टम के सर्वर घटक की आवश्यकता होगी जो कई क्लाइंट के साथ कनेक्शन बनाए रखने में सक्षम हो। यह हमें एक क्लाइंट से प्राप्त संदेशों को अन्य सभी कनेक्टेड क्लाइंट में वितरित करने की अनुमति देगा।
हमारा चैट सर्वर बाएँ टैब में चल रहा है। दो चैट क्लाइंट सही टैब में चल रहे हैं। क्लाइंट द्वारा भेजा गया कोई भी संदेश अन्य सभी क्लाइंट द्वारा प्राप्त किया जाएगा।
चैट क्लाइंट
यह लेख चैट सर्वर पर केंद्रित है, लेकिन इसके साथ संवाद करने के लिए हमें पहले एक चैट क्लाइंट की आवश्यकता होगी। निम्नलिखित कोड हमारा बहुत ही सरल ग्राहक होगा। (एक और पूरा उदाहरण GitHub पर पाया जा सकता है।)
# client.rb
# $ ruby client.rb
require 'socket'
client = TCPSocket.open(ARGV[0], 2000)
Thread.new do
while line = client.gets
puts line.chop
end
end
while input = STDIN.gets.chomp
client.puts input
end
क्लाइंट पोर्ट 2000 पर चल रहे सर्वर से एक टीसीपी कनेक्शन खोलता है। कनेक्ट होने पर, यह एक थ्रेड उत्पन्न करता है जो puts
होगा। सर्वर कुछ भी भेजता है, इसलिए चैट टर्मिनल आउटपुट में दिखाई देती है। अंत में, एक समय लूप होता है जो आपके द्वारा टाइप की गई किसी भी लाइन को सर्वर पर भेजता है, जिसे वह अन्य सभी कनेक्टेड क्लाइंट को भेजेगा।
चैट सर्वर
इस उदाहरण में क्लाइंट अन्य क्लाइंट के साथ संचार करने के लिए चैट सर्वर से कनेक्ट होता है। तीनों समवर्ती दृष्टिकोणों के लिए हम रूबी के मानक पुस्तकालय से एक ही टीसीपी सर्वर का उपयोग करेंगे।
# server_processes.rb
# $ ruby server_processes.rb
require 'socket'
puts 'Starting server on port 2000'
server = TCPServer.open(2000)
इस बिंदु तक सभी तीन समवर्ती मॉडल के लिए कोड समान है। फिर प्रत्येक मॉडल में चैट सर्वर को दो परिदृश्यों को संभालने की आवश्यकता होगी:
- ग्राहकों से नए कनेक्शन स्वीकार करें।
- ग्राहकों से संदेश प्राप्त करें और उन्हें अन्य सभी ग्राहकों को भेजें।
एक बहु-प्रक्रिया चैट सर्वर
एक बहु-प्रक्रिया चैट सर्वर के साथ इन दो परिदृश्यों को संभालने के लिए, हम प्रति क्लाइंट कनेक्शन के लिए एक प्रक्रिया तैयार करेंगे। यह प्रक्रिया उस क्लाइंट के लिए भेजे और प्राप्त किए जा रहे सभी संदेशों को संभाल लेगी। हम मूल सर्वर प्रक्रिया को फोर्क करके इन प्रक्रियाओं को बना सकते हैं।
फोर्किंग प्रक्रिया
जब आप फोर्क विधि को कॉल करते हैं, तो यह वर्तमान प्रक्रिया की उसी स्थिति के साथ एक प्रतिलिपि बनाता है जिसमें प्रक्रिया है।
एक फोर्कड प्रक्रिया की अपनी प्रक्रिया आईडी होती है, और यह top
. जैसे टूल में अलग से दिखाई देगी या गतिविधि मॉनिटर। यह कुछ इस तरह दिखता है:
आप जिस प्रक्रिया से शुरू करते हैं उसे मास्टर प्रक्रिया कहा जाता है, और जो प्रक्रियाएँ मास्टर प्रक्रिया से बाहर निकलती हैं उन्हें वर्कर प्रोसेस कहा जाता है।
चूंकि ये नई फोर्क की गई कार्यकर्ता प्रक्रियाएं वास्तव में अलग प्रक्रियाएं हैं, इसलिए हम उनके और मास्टर प्रक्रिया के बीच स्मृति साझा नहीं कर सकते हैं। हमें उनके बीच संवाद करने के लिए कुछ चाहिए।
यूनिक्स पाइप
प्रक्रियाओं के बीच संवाद करने के लिए हम यूनिक्स पाइप का उपयोग करेंगे। एक यूनिक्स पाइप दो प्रक्रियाओं के बीच बाइट्स की दो-तरफा धारा स्थापित करता है, और आप इसका उपयोग एक प्रक्रिया से दूसरी प्रक्रिया में डेटा भेजने के लिए कर सकते हैं। सौभाग्य से, रूबी इन पाइपों के चारों ओर एक अच्छा आवरण प्रदान करती है, इसलिए हमें पहिया का फिर से आविष्कार करने की आवश्यकता नहीं है।
निम्नलिखित उदाहरण में हमने रूबी में एक पाइप स्थापित किया है - एक रीडिंग और एक राइटिंग एंड के साथ- और हम fork
मास्टर प्रक्रिया। ब्लॉक के भीतर का कोड जो fork
. को पास किया गया है फोर्कड प्रक्रिया में चल रहा है। इस ब्लॉक के बाद मूल प्रक्रिया जारी है। फिर हम फोर्कड से मूल प्रक्रिया के लिए एक संदेश लिखते हैं।
reader, writer = IO.pipe
fork do
# This is running in the forked process.
writer.puts 'Hello from the forked process'
end
# This is running in the original process, it will puts the
# message from the forked process.
puts reader.gets
पाइप का उपयोग करके हम अलग-अलग प्रक्रियाओं के बीच संवाद कर सकते हैं, भले ही प्रक्रियाएं एक-दूसरे से पूरी तरह से अलग-थलग हों।
चैट सर्वर का कार्यान्वयन
पहले हम सभी ग्राहकों और उनके "लेखकों" (पाइप का लेखन अंत) के लिए पाइप का ट्रैक रखने के लिए एक सरणी स्थापित करते हैं, ताकि हम ग्राहकों के साथ संवाद कर सकें। फिर हम यह सुनिश्चित करते हैं कि ग्राहकों से आने वाले सभी संदेश अन्य सभी ग्राहकों को भेजे जाएं।
client_writers = []
master_reader, master_writer = IO.pipe
write_incoming_messages_to_child_processes(master_reader, client_writers)
आप write_incoming_messages_to_child_processes
. का क्रियान्वयन देख सकते हैं गिटहब पर अगर आप इसके संचालन का विवरण देखना चाहते हैं।
नए कनेक्शन स्वीकार करना
हमें आने वाले कनेक्शन स्वीकार करने और पाइप स्थापित करने की आवश्यकता होगी। नए लेखक को client_writers
. पर धकेल दिया जाएगा सरणी। मुख्य प्रक्रिया सरणी के माध्यम से लूप करने में सक्षम होगी और प्रत्येक कार्यकर्ता प्रक्रिया को उसके पाइप पर लिखकर एक संदेश भेज सकेगी।
फिर हम मास्टर प्रोसेस को फोर्क करते हैं, और फोर्क्ड वर्कर प्रोसेस में कोड क्लाइंट कनेक्शन को हैंडल करेगा।
loop do
while socket = server.accept
# Create a client reader and writer so that the master
# process can write messages back to us.
client_reader, client_writer = IO.pipe
# Put the client writer on the list of writers so the
# master process can write to them.
client_writers.push(client_writer)
# Fork child process, everything in the fork block
# only runs in the child process.
fork do
# Handle connection
end
end
end
क्लाइंट कनेक्शन संभालना
हमें क्लाइंट कनेक्शन को भी संभालने की जरूरत है।
फोर्कड प्रक्रिया क्लाइंट से उपनाम प्राप्त करके शुरू होती है (क्लाइंट डिफ़ॉल्ट रूप से उपनाम भेजता है)। उसके बाद यह write_incoming_messages_to_client
. में एक थ्रेड शुरू करता है जो मुख्य प्रक्रिया के संदेशों को सुनता है।
अंत में, कांटा प्रक्रिया एक लूप शुरू करती है जो आने वाले संदेशों को सुनती है और उन्हें मास्टर प्रक्रिया में भेजती है। मास्टर प्रक्रिया सुनिश्चित करती है कि अन्य कार्यकर्ता प्रक्रिया संदेश प्राप्त करे।
nickname = read_line_from(socket)
puts "#{Process.pid}: Accepted connection from #{nickname}"
write_incoming_messages_to_client(nickname, client_reader, socket)
# Read incoming messages from the client.
while incoming = read_line_from(socket)
master_writer.puts "#{nickname}: #{incoming}"
end
puts "#{Process.pid}: Disconnected #{nickname}"
एक कार्यशील चैट सिस्टम
अब पूरा चैट सिस्टम काम करता है! लेकिन जैसा कि आप देख सकते हैं, मल्टीप्रोसेसिंग का उपयोग करने वाला प्रोग्राम लिखना काफी जटिल है और बहुत सारे संसाधनों का उपयोग करता है। उल्टा यह है कि यह बहुत मजबूत है। यदि चाइल्ड प्रोसेस में से कोई एक क्रैश हो जाता है तो बाकी सिस्टम बस काम करता रहता है। आप उदाहरण कोड चलाकर और kill -9 <process-id>
. चलाकर कोशिश कर सकते हैं प्रक्रियाओं में से एक पर (आप सर्वर के लॉग आउटपुट में प्रक्रिया आईडी पा सकते हैं)।
अगले लेख में हम वही चैट सिस्टम केवल थ्रेड्स का उपयोग करके लागू करेंगे, ताकि हम केवल एक प्रक्रिया और कम मेमोरी का उपयोग करके समान सुविधाओं वाला सर्वर चला सकें।