मास्टरिंग कंकरेंसी पर पिछले रूबी मैजिक लेख में, हमने हमें रूबी डेवलपर्स के रूप में उपलब्ध कंकरेंसी प्राप्त करने के तीन तरीकों का परिचय दिया। यह लेख तीन-भाग श्रृंखला में पहला है जहां हम प्रत्येक विधि में गहराई से गोता लगाते हैं।
पहला: मल्टी-प्रोसेस। इस विधि के साथ एक मास्टर प्रोसेस खुद को कई वर्कर प्रोसेस में फोर्क करता है। वर्कर प्रोसेस वास्तविक काम करता है, जबकि मास्टर वर्कर्स को प्रबंधित करता है।
इस लेख में उदाहरणों में उपयोग किया गया पूरा सोर्स कोड 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 पर चल रहे सर्वर के लिए एक TCP कनेक्शन खोलता है। कनेक्ट होने पर, यह एक थ्रेड उत्पन्न करता है जो सर्वर द्वारा भेजी गई किसी भी चीज़ को puts करेगा, ताकि चैट टर्मिनल आउटपुट में दिखाई दे। अंत में, एक व्हाइल लूप है जो आपके द्वारा टाइप की गई किसी भी लाइन को सर्वर को भेजता है, जो इसे अन्य सभी जुड़े क्लाइंट को भेजेगा।
चैट सर्वर
इस उदाहरण में एक क्लाइंट अन्य क्लाइंट के साथ संवाद करने के लिए चैट सर्वर से जुड़ता है। सभी तीन कंकरेंसी दृष्टिकोणों के लिए हम रूबी के मानक पुस्तकालय से उसी TCP सर्वर का उपयोग करेंगे।
# server_processes.rb
# $ ruby server_processes.rb
require 'socket'
puts 'Starting server on port 2000'
server = TCPServer.open(2000)
इस बिंदु तक कोड सभी तीन कंकरेंसी मॉडल के लिए समान है। प्रत्येक मॉडल में चैट सर्वर को तब दो परिदृश्यों को संभालने की आवश्यकता होगी:
- क्लाइंट से नए कनेक्शन स्वीकार करें।
- क्लाइंट से संदेश प्राप्त करें और उन्हें अन्य सभी क्लाइंट को भेजें।
मल्टी-प्रोसेस चैट सर्वर
इन दो परिदृश्यों को मल्टी-प्रोसेस चैट सर्वर के साथ संभालने के लिए, हम प्रति क्लाइंट कनेक्शन एक प्रोसेस उत्पन्न करेंगे। यह प्रोसेस उस क्लाइंट के लिए भेजे और प्राप्त किए जा रहे सभी संदेशों को संभालेगा। हम मूल सर्वर प्रोसेस को फोर्क करके ये प्रोसेस बना सकते हैं।
प्रोसेस फोर्क करना
जब आप fork मेथड को कॉल करते हैं, तो यह वर्तमान प्रोसेस की एक प्रति बनाता है जिसमें ठीक वही स्थिति होती है जिसमें प्रोसेस होता है।
एक फोर्क्ड प्रोसेस की अपनी प्रोसेस आईडी होती है, और top या एक्टिविटी मॉनिटर जैसे टूल में अलग से दिखाई देगा। यह कुछ इस तरह दिखता है:
जिस प्रोसेस से आप शुरू करते हैं उसे मास्टर प्रोसेस कहा जाता है, और मास्टर प्रोसेस से फोर्क किए गए प्रोसेस को वर्कर प्रोसेस कहा जाता है।
चूंकि ये नए फोर्क्ड वर्कर प्रोसेस वास्तव में अलग प्रोसेस हैं, हम उनके और मास्टर प्रोसेस के बीच मेमोरी साझा नहीं कर सकते। हमें उनके बीच संवाद करने के लिए कुछ चाहिए।
यूनिक्स पाइप
प्रोसेस के बीच संवाद करने के लिए हम यूनिक्स पाइप का उपयोग करेंगे। एक यूनिक्स पाइप दो प्रोसेस के बीच बाइट्स की दो-तरफा स्ट्रीम सेट करता है, और आप इसका उपयोग एक प्रोसेस से दूसरे प्रोसेस में डेटा भेजने के लिए कर सकते हैं। सौभाग्य से, रूबी इन पाइप के चारों ओर एक अच्छा रैपर प्रदान करता है ताकि हमें पहिया को फिर से न बनाना पड़े।
निम्नलिखित उदाहरण में हम रूबी में एक पाइप सेट करते हैं - एक पढ़ने और एक लिखने वाले सिरे के साथ - और हम मास्टर प्रोसेस को fork करते हैं। fork को पास किए गए ब्लॉक के भीतर का कोड फोर्क्ड प्रोसेस में चल रहा है। मूल प्रोसेस इस ब्लॉक के बाद जारी रहता है। फिर हम फोर्क्ड प्रोसेस से मूल प्रोसेस को एक संदेश लिखते हैं।
reader, writer = IO.pipe
fork do
# यह फोर्क्ड प्रोसेस में चल रहा है।
writer.puts 'Hello from the forked process'
end
# यह मूल प्रोसेस में चल रहा है, यह फोर्क्ड प्रोसेस से संदेश को puts करेगा।
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 का कार्यान्वयन GitHub पर पा सकते हैं।
नए कनेक्शन स्वीकार करना
हमें आने वाले कनेक्शन स्वीकार करने और पाइप सेट करने की आवश्यकता होगी। नए राइटर को client_writers सरणी पर पुश किया जाएगा। मुख्य प्रोसेस सरणी के माध्यम से लूप करने और प्रत्येक वर्कर प्रोसेस को उसके पाइप पर लिखकर संदेश भेजने में सक्षम होगा।
फिर हम मास्टर प्रोसेस को फोर्क करते हैं, और फोर्क्ड वर्कर प्रोसेस के भीतर का कोड क्लाइंट कनेक्शन को संभालेगा।
loop do
while socket = server.accept
# एक क्लाइंट रीडर और राइटर बनाएं ताकि मास्टर
# प्रोसेस हमें वापस संदेश लिख सके।
client_reader, client_writer = IO.pipe
# क्लाइंट राइटर को राइटर्स की सूची पर रखें ताकि
# मास्टर प्रोसेस उन्हें लिख सके।
client_writers.push(client_writer)
# चाइल्ड प्रोसेस फोर्क करें, फोर्क ब्लॉक के भीतर सब कुछ
# केवल चाइल्ड प्रोसेस में चलता है।
fork do
# कनेक्शन संभालें
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)
# क्लाइंट से आने वाले संदेश पढ़ें।
while incoming = read_line_from(socket)
master_writer.puts "#{nickname}: #{incoming}"
end
puts "#{Process.pid}: Disconnected #{nickname}"
एक कार्यशील चैट सिस्टम
अब पूरा चैट सिस्टम काम करता है! लेकिन जैसा कि आप देख सकते हैं, मल्टीप्रोसेसिंग का उपयोग करने वाला प्रोग्राम लिखना काफी जटिल है और बहुत सारे संसाधनों का उपयोग करता है। उल्टा यह है कि यह बहुत मजबूत है। यदि चाइल्ड प्रोसेस में से एक क्रैश हो जाता है तो बाकी सिस्टम बस काम करता रहता है। आप उदाहरण कोड चलाकर और kill -9 <process-id> को प्रोसेस में से एक पर चलाकर इसे आज़मा सकते हैं (आप सर्वर के लॉग आउटपुट में प्रोसेस आईडी पा सकते हैं)।
अगले लेख में हम उसी चैट सिस्टम को केवल थ्रेड का उपयोग करके लागू करेंगे, ताकि हम एक ही प्रोसेस और कम मेमोरी का उपयोग करके समान सुविधाओं वाला सर्वर चला सकें।