Erlang 21 - 3. Concurrent Programming

3 समवर्ती प्रोग्रामिंग




erlang

3 समवर्ती प्रोग्रामिंग

3.1 प्रक्रियाएँ

अन्य कार्यात्मक भाषाओं के बजाय एरलांग का उपयोग करने के मुख्य कारणों में से एक है अरिलांग की संगोष्ठी और वितरित प्रोग्रामिंग को संभालने की क्षमता। संगामिति से तात्पर्य ऐसे कार्यक्रमों से है जो एक ही समय में निष्पादन के कई सूत्र संभाल सकते हैं। उदाहरण के लिए, आधुनिक ऑपरेटिंग सिस्टम आपको एक शब्द प्रोसेसर, एक स्प्रेडशीट, एक मेल क्लाइंट और एक ही समय में चलने वाले सभी प्रिंट कार्य का उपयोग करने की अनुमति देता है। सिस्टम में प्रत्येक प्रोसेसर (सीपीयू) संभवतः एक समय में केवल एक धागा (या नौकरी) संभाल रहा है, लेकिन यह नौकरियों के बीच इतनी दर से स्वैप करता है कि यह उन सभी को एक ही समय में चलाने का भ्रम देता है। Erlang प्रोग्राम में निष्पादन के समानांतर धागे बनाना और इन थ्रेड्स को एक-दूसरे के साथ संवाद करने की अनुमति देना आसान है। एरलैंग में, निष्पादन के प्रत्येक धागे को एक प्रक्रिया कहा जाता है।

(पहलू: "प्रक्रिया" शब्द का उपयोग आमतौर पर तब किया जाता है जब निष्पादन के धागे एक दूसरे के साथ कोई डेटा साझा नहीं करते हैं और "थ्रेड" शब्द किसी तरह से डेटा साझा करते हैं। एर्लांग में निष्पादन के धागे कोई डेटा साझा नहीं करते हैं, यही कारण है कि वे हैं। प्रक्रियाओं कहा जाता है)।

Erlang BIF spawn का उपयोग एक नई प्रक्रिया बनाने के लिए किया जाता है: spawn(Module, Exported_Function, List of Arguments) । निम्नलिखित मॉड्यूल पर विचार करें:

-module(tut14).

-export([start/0, say_something/2]).

say_something(What, 0) ->
    done;
say_something(What, Times) ->
    io:format("~p~n", [What]),
    say_something(What, Times - 1).

start() ->
    spawn(tut14, say_something, [hello, 3]),
    spawn(tut14, say_something, [goodbye, 3]).
5> c(tut14).
{ok,tut14}
6> tut14:say_something(hello, 3).
hello
hello
hello
done

जैसा कि दिखाया गया है, फ़ंक्शन say_something अपना पहला तर्क लिखता है दूसरे तर्क द्वारा निर्दिष्ट समय की संख्या। फ़ंक्शन start होता है दो एरलंग प्रक्रियाएं, एक जो "हैलो" तीन बार लिखती है और एक जो तीन बार "अलविदा" लिखती है। दोनों प्रक्रिया फ़ंक्शन का उपयोग करती हैं say_something । ध्यान दें कि एक प्रक्रिया शुरू करने के लिए spawn द्वारा इस तरह से उपयोग किए जाने वाले फ़ंक्शन को मॉड्यूल से निर्यात किया जाना चाहिए (जो कि मॉड्यूल के प्रारंभ में -export में है)।

9> tut14:start().
hello
goodbye
<0.63.0>
hello
goodbye
hello
goodbye

ध्यान दें कि उसने तीन बार "हैलो" नहीं लिखा और फिर तीन बार "अलविदा" लिखा। इसके बजाय, पहली प्रक्रिया ने एक "हैलो" लिखा, दूसरा एक "अलविदा", पहला दूसरा "हैलो" और इसके बाद। लेकिन <0.63.0> कहां से आया? किसी फ़ंक्शन का रिटर्न मान फ़ंक्शन में अंतिम "चीज़" का रिटर्न वैल्यू है। फ़ंक्शन start में अंतिम चीज़ है

spawn(tut14, say_something, [goodbye, 3]).

spawn एक प्रक्रिया पहचानकर्ता , या पीआईडी ​​देता है , जो विशिष्ट रूप से प्रक्रिया की पहचान करता है। तो <0.63.0> spawn फ़ंक्शन कॉल के ऊपर है। अगला उदाहरण दिखाता है कि ग्रिड का उपयोग कैसे करें।

यह भी ध्यान दें कि ~ p का उपयोग io:format में ~ w के बजाय किया जाता है io:format । मैनुअल को उद्धृत करने के लिए: "~ p डेटा को मानक सिंटैक्स के साथ उसी तरह लिखता है जैसे ~ w, लेकिन ऐसे शब्दों को तोड़ता है जिनका मुद्रित प्रतिनिधित्व एक पंक्ति से कई लाइनों में लंबा होता है और प्रत्येक पंक्ति को समझदारी से इंगित करता है। यह प्रिंट करने योग्य की सूचियों का पता लगाने की भी कोशिश करता है। अक्षर और इनका उत्पादन स्ट्रिंग्स के रूप में करना "

3.2 संदेश पासिंग

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

-module(tut15).

-export([start/0, ping/2, pong/0]).

ping(0, Pong_PID) ->
    Pong_PID ! finished,
    io:format("ping finished~n", []);

ping(N, Pong_PID) ->
    Pong_PID ! {ping, self()},
    receive
        pong ->
            io:format("Ping received pong~n", [])
    end,
    ping(N - 1, Pong_PID).

pong() ->
    receive
        finished ->
            io:format("Pong finished~n", []);
        {ping, Ping_PID} ->
            io:format("Pong received ping~n", []),
            Ping_PID ! pong,
            pong()
    end.

start() ->
    Pong_PID = spawn(tut15, pong, []),
    spawn(tut15, ping, [3, Pong_PID]).
1> c(tut15).
{ok,tut15}
2> tut15: start().
<0.36.0>
Pong received ping
Ping received pong
Pong received ping
Ping received pong
Pong received ping
Ping received pong
ping finished
Pong finished

फ़ंक्शन start पहले एक प्रक्रिया बनाता है, हम इसे "पोंग" कहते हैं:

Pong_PID = spawn(tut15, pong, [])

यह प्रक्रिया tut15:pong() निष्पादित tut15:pong() Pong_PID "पोंग" प्रक्रिया की प्रक्रिया पहचान है। फ़ंक्शन start अब एक और प्रक्रिया "पिंग" बनाता है:

spawn(tut15, ping, [3, Pong_PID]),

यह प्रक्रिया निष्पादित होती है:

tut15:ping(3, Pong_PID)

<0.36.0> start फंक्शन से रिटर्न वैल्यू है।

प्रक्रिया "पोंग" अब करती है:

receive
    finished ->
        io:format("Pong finished~n", []);
    {ping, Ping_PID} ->
        io:format("Pong received ping~n", []),
        Ping_PID ! pong,
        pong()
end.

receive निर्माण का उपयोग प्रक्रियाओं को अन्य प्रक्रियाओं के संदेशों की प्रतीक्षा करने के लिए किया जाता है। इसका निम्न प्रारूप है:

receive
   pattern1 ->
       actions1;
   pattern2 ->
       actions2;
   ....
   patternN
       actionsN
end.

सूचना नहीं है ""; end से पहले।

Erlang प्रक्रियाओं के बीच संदेश केवल Erlang शब्द मान्य हैं। यही है, वे सूची, ट्यूपल्स, पूर्णांक, परमाणु, पलिड और इतने पर हो सकते हैं।

प्रत्येक प्रक्रिया को प्राप्त संदेशों के लिए अपनी स्वयं की इनपुट कतार होती है। प्राप्त नए संदेश कतार के अंत में डाल दिए जाते हैं। जब कोई प्रक्रिया किसी receive निष्पादित करती है, तो कतार में पहला संदेश receive होने वाले पहले पैटर्न के विरुद्ध मिलान किया जाता है। यदि यह मेल खाता है, तो संदेश कतार से हटा दिया जाता है और पैटर्न के अनुरूप कार्यों को निष्पादित किया जाता है।

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

एरलैंग कार्यान्वयन "चतुर" है और प्रत्येक संदेश को प्रत्येक receive में पैटर्न के खिलाफ परीक्षण किए गए समय की संख्या को कम करता है।

अब वापस पिंग पोंग उदाहरण के लिए।

"पोंग" संदेशों का इंतजार कर रहा है। यदि परमाणु finished जाता है, तो "पांग" आउटपुट को "पांग समाप्त" लिखता है और, क्योंकि इसके पास करने के लिए अधिक कुछ नहीं है, समाप्त होता है। यदि यह प्रारूप के साथ एक संदेश प्राप्त करता है:

{ping, Ping_PID}

यह आउटपुट में "पोंग प्राप्त पिंग" लिखता है और परमाणु pong को "पिंग" प्रक्रिया में भेजता है:

Ping_PID ! pong

नोटिस कैसे ऑपरेटर "!" संदेश भेजने के लिए उपयोग किया जाता है। का सिंटेक्स "!" है:

Pid ! Message

यही है, Message (कोई भी एर्लांग शब्द) को पहचान के साथ प्रक्रिया में भेजा जाता है।

संदेश pong को "पिंग" प्रक्रिया में भेजने के बाद, "पोंग" फिर से pong फ़ंक्शन को कॉल करता है, जिसके कारण यह फिर से प्राप्त करने के लिए वापस आ जाता है और दूसरे संदेश की प्रतीक्षा करता है।

अब हम "पिंग" प्रक्रिया को देखते हैं। याद रखें कि इसे क्रियान्वित करके शुरू किया गया था:

tut15:ping(3, Pong_PID)

फ़ंक्शन ping/2 को देखते हुए, ping/2 के दूसरे खंड को निष्पादित किया जाता है क्योंकि पहले तर्क का मान 3 है (0 नहीं) (पहला खंड सिर ping(0,Pong_PID) , दूसरा खंड सिर ping(N,Pong_PID) , इसलिए N 3 हो जाता है)।

दूसरा खंड "पोंग" के लिए एक संदेश भेजता है:

Pong_PID ! {ping, self()},

self() को निष्पादित करने वाली प्रक्रिया के पीड को लौटाता है self() , इस मामले में "पिंग" की पिड। ("पोंग" के लिए कोड को याद करें, यह पहले से समझाए गए receive में चर Ping_PID में है।)

"पिंग" अब "पोंग" के उत्तर की प्रतीक्षा कर रहा है:

receive
    pong ->
        io:format("Ping received pong~n", [])
end,

यह "पिंग प्राप्त पोंग" लिखता है जब यह उत्तर आता है, जिसके बाद "पिंग" फिर से ping फ़ंक्शन को कॉल करता है।

ping(N - 1, Pong_PID)

N-1 का पहला तर्क तब तक घटाया जा सकता है जब तक कि यह 0. नहीं हो जाता। जब ऐसा होता है, तो ping/2 का पहला क्लॉज निष्पादित किया जाता है:

ping(0, Pong_PID) ->
    Pong_PID !  finished,
    io:format("ping finished~n", []);

finished परमाणु को "पोंग" (इसे ऊपर वर्णित के अनुसार समाप्त करने के लिए भेजा जाता है) और "पिंग समाप्त" को आउटपुट के लिए लिखा जाता है। "पिंग" तब समाप्त होता है क्योंकि इसके पास करने के लिए कुछ नहीं बचा है।

3.3 पंजीकृत प्रक्रिया नाम

उपरोक्त उदाहरण में, "पोंग" पहली बार "पिंग" की पहचान देने में सक्षम होने के लिए बनाया गया था जब "पिंग" शुरू किया गया था। यही है, किसी तरह "पिंग" को "पोंग" की पहचान जानने के लिए सक्षम होना चाहिए ताकि वह एक संदेश भेज सके। कभी-कभी ऐसी प्रक्रियाएं जिन्हें एक-दूसरे की पहचान जानने की आवश्यकता होती है, उन्हें एक-दूसरे से स्वतंत्र रूप से शुरू किया जाता है। एर्लैंग इस प्रकार प्रक्रियाओं को नाम दिए जाने के लिए एक तंत्र प्रदान करता है ताकि इन नामों को पहचान के रूप में इस्तेमाल किया जा सके। यह register BIF का उपयोग करके किया जाता है:

register(some_atom, Pid)

आइए अब इस का उपयोग करके पिंग पोंग उदाहरण को फिर से लिखें और "पोंग" प्रक्रिया को नाम दें।

-module(tut16).

-export([start/0, ping/1, pong/0]).

ping(0) ->
    pong ! finished,
    io:format("ping finished~n", []);

ping(N) ->
    pong ! {ping, self()},
    receive
        pong ->
            io:format("Ping received pong~n", [])
    end,
    ping(N - 1).

pong() ->
    receive
        finished ->
            io:format("Pong finished~n", []);
        {ping, Ping_PID} ->
            io:format("Pong received ping~n", []),
            Ping_PID ! pong,
            pong()
    end.

start() ->
    register(pong, spawn(tut16, pong, [])),
    spawn(tut16, ping, [3]).
2> c(tut16).
{ok, tut16}
3> tut16:start().
<0.38.0>
Pong received ping
Ping received pong
Pong received ping
Ping received pong
Pong received ping
Ping received pong
ping finished
Pong finished

यहाँ start/0 समारोह,

register(pong, spawn(tut16, pong, [])),

दोनों "पोंग" प्रक्रिया को जन्म देते हैं और इसे pong नाम देते हैं। "पिंग" प्रक्रिया में, संदेश pong द्वारा भेजे जा सकते हैं:

pong ! {ping, self()},

ping/2 अब ping/1 बन जाता है क्योंकि Pong_PID के तर्क की आवश्यकता नहीं है।

3.4 वितरित प्रोग्रामिंग

आइए हम विभिन्न कंप्यूटरों पर "पिंग" और "पोंग" के साथ पिंग पोंग कार्यक्रम को फिर से लिखें। काम करने के लिए इसे स्थापित करने के लिए पहले कुछ चीजों की आवश्यकता होती है। वितरित Erlang कार्यान्वयन किसी अन्य कंप्यूटर पर Erlang सिस्टम के लिए अनजाने तक पहुँच को रोकने के लिए एक बहुत ही बुनियादी प्रमाणीकरण तंत्र प्रदान करता है। Erlang सिस्टम जो एक दूसरे से बात करते हैं, उनके पास एक ही जादू की कुकी होनी चाहिए। इसे प्राप्त करने का सबसे आसान तरीका एक फ़ाइल है, जिसका नाम है .erlang.cookie आपके घर की निर्देशिका में उन सभी मशीनों पर, जिन पर आप एक दूसरे के साथ संचार करते हुए Erlang सिस्टम चलाने जा रहे हैं:

  • विंडोज सिस्टम पर होम डाइरेक्टरी है जो कि पर्यावरण चर $ HOME द्वारा इंगित की गई है - आपको इसे सेट करने की आवश्यकता हो सकती है।
  • लिनक्स या यूनिक्स पर आप इसे सुरक्षित रूप से अनदेखा कर सकते हैं और बस एक फ़ाइल बना सकते हैं जिसका नाम है .erlang.cookie इस निर्देशिका में आपको बिना किसी तर्क के कमांड cd निष्पादित करने के बाद मिलता है।

.erlang.cookie फ़ाइल में एक ही परमाणु के साथ एक रेखा होती है। उदाहरण के लिए, OS शेल में लिनक्स या UNIX पर:

$ cd
$ cat > .erlang.cookie
this_is_very_secret
$ chmod 400 .erlang.cookie

ऊपर दिया गया chmod केवल फ़ाइल के स्वामी द्वारा .erlang.cookie फ़ाइल को सुलभ बनाता है। यह एक आवश्यकता है।

जब आप एक Erlang सिस्टम शुरू करते हैं जो अन्य Erlang सिस्टम से बात करने वाला है, तो आपको इसे एक नाम देना होगा, उदाहरण के लिए:

$ erl -sname my_name

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

(नोट: erl -sname यह मानता है कि सभी नोड्स एक ही IP डोमेन में हैं और हम IP पते के केवल पहले घटक का उपयोग कर सकते हैं, यदि हम अलग-अलग डोमेन में नोड्स का उपयोग करना चाहते हैं, तो हम इसके बजाय -name उपयोग करते हैं, लेकिन उसके बाद सभी IP पते का उपयोग करना चाहिए पूरा दिया जाए।)

यहाँ पिंग पोंग उदाहरण को दो अलग-अलग नोड्स पर चलाने के लिए संशोधित किया गया है:

-module(tut17).

-export([start_ping/1, start_pong/0,  ping/2, pong/0]).

ping(0, Pong_Node) ->
    {pong, Pong_Node} ! finished,
    io:format("ping finished~n", []);

ping(N, Pong_Node) ->
    {pong, Pong_Node} ! {ping, self()},
    receive
        pong ->
            io:format("Ping received pong~n", [])
    end,
    ping(N - 1, Pong_Node).

pong() ->
    receive
        finished ->
            io:format("Pong finished~n", []);
        {ping, Ping_PID} ->
            io:format("Pong received ping~n", []),
            Ping_PID ! pong,
            pong()
    end.

start_pong() ->
    register(pong, spawn(tut17, pong, [])).

start_ping(Pong_Node) ->
    spawn(tut17, ping, [3, Pong_Node]).

आइए हम मानते हैं कि दो कंप्यूटर हैं जिन्हें गोलम और कोस्केन कहा जाता है। पहले एक नोड कोसकेन पर शुरू किया जाता है, जिसे पिंग कहा जाता है, और फिर गोलम पर एक नोड, जिसे पोंग कहा जाता है।

Kosken पर (एक लिनक्स / यूनिक्स प्रणाली पर):

kosken> erl -sname ping
Erlang (BEAM) emulator version 5.2.3.7 [hipe] [threads:0]

Eshell V5.2.3.7  (abort with ^G)
([email protected])1>

गोलम पर:

gollum> erl -sname pong
Erlang (BEAM) emulator version 5.2.3.7 [hipe] [threads:0]

Eshell V5.2.3.7  (abort with ^G)
([email protected])1>

अब गोलम पर "पोंग" प्रक्रिया शुरू की गई है:

([email protected])1> tut17:start_pong().
true

और कोस्केन पर "पिंग" प्रक्रिया शुरू की गई है (ऊपर दिए गए कोड से आप देख सकते हैं कि start_ping फ़ंक्शन का एक पैरामीटर Erlang सिस्टम का नोड नाम है जहां "पोंग" चल रहा है):

([email protected])1> tut17:start_ping([email protected]).
<0.37.0>
Ping received pong
Ping received pong 
Ping received pong
ping finished

दिखाया गया है, पिंग पोंग कार्यक्रम चला है। "पोंग" की ओर:

([email protected])2>
Pong received ping                 
Pong received ping                 
Pong received ping                 
Pong finished                      
([email protected])2>

tut17 कोड को देखते हुए, आप देखते हैं कि pong फ़ंक्शन स्वयं अपरिवर्तित है, निम्न पंक्तियां उसी तरह से काम करती हैं, जिस पर "पिंग" प्रक्रिया को नोड के बावजूद निष्पादित किया जाता है:

{ping, Ping_PID} ->
    io:format("Pong received ping~n", []),
    Ping_PID ! pong,

इस प्रकार, Erlang pids में प्रक्रिया के क्रियान्वयन की जानकारी होती है। तो अगर आप एक प्रक्रिया के बारे में जानते हैं, तो "!" ऑपरेटर का उपयोग यह संदेश भेजने के लिए किया जा सकता है कि क्या प्रक्रिया एक ही नोड पर या एक अलग नोड पर है।

एक अंतर यह है कि संदेशों को किसी अन्य नोड पर पंजीकृत प्रक्रिया में कैसे भेजा जाता है:

{pong, Pong_Node} ! {ping, self()},

केवल {registered_name,node_name} बजाय एक टपल {registered_name,node_name} का उपयोग किया जाता है।

पिछले उदाहरण में, "पिंग" और "पोंग" को दो अलग-अलग एरलांग नोड्स के गोले से शुरू किया गया था। spawn उपयोग अन्य नोड्स में प्रक्रिया शुरू करने के लिए भी किया जा सकता है।

अगला उदाहरण पिंग पोंग कार्यक्रम है, फिर भी, लेकिन इस बार "पिंग" दूसरे नोड में शुरू किया गया है:

-module(tut18).

-export([start/1,  ping/2, pong/0]).

ping(0, Pong_Node) ->
    {pong, Pong_Node} ! finished,
    io:format("ping finished~n", []);

ping(N, Pong_Node) ->
    {pong, Pong_Node} ! {ping, self()},
    receive
        pong ->
            io:format("Ping received pong~n", [])
    end,
    ping(N - 1, Pong_Node).

pong() ->
    receive
        finished ->
            io:format("Pong finished~n", []);
        {ping, Ping_PID} ->
            io:format("Pong received ping~n", []),
            Ping_PID ! pong,
            pong()
    end.

start(Ping_Node) ->
    register(pong, spawn(tut18, pong, [])),
    spawn(Ping_Node, tut18, ping, [3, node()]).

एरलंग प्रणाली को पिंग कहा जाता है (लेकिन "पिंग" प्रक्रिया नहीं) कोसकेन पर शुरू किया गया है, फिर गोलम पर यह किया जाता है:

([email protected])1> tut18:start([email protected]).
<3934.39.0>
Pong received ping
Ping received pong
Pong received ping
Ping received pong
Pong received ping
Ping received pong
Pong finished
ping finished

गौर करें कि सभी आउटपुट गोलम पर प्राप्त होते हैं। ऐसा इसलिए है क्योंकि I / O सिस्टम यह पता लगाता है कि प्रक्रिया कहाँ से उत्पन्न हुई है और वहाँ सभी आउटपुट भेजता है।

3.5 एक बड़ा उदाहरण

अब एक सरल "दूत" के साथ एक बड़ा उदाहरण के लिए। मैसेंजर एक ऐसा प्रोग्राम है जो उपयोगकर्ताओं को विभिन्न नोड्स पर लॉग इन करने और एक दूसरे को सरल संदेश भेजने की अनुमति देता है।

शुरू करने से पहले, निम्नलिखित पर ध्यान दें:

  • यह उदाहरण केवल संदेश पास करने वाले तर्क को दर्शाता है - एक अच्छा चित्रमय उपयोगकर्ता इंटरफ़ेस प्रदान करने के लिए कोई प्रयास नहीं किया गया है, हालांकि यह इरडांग में भी किया जा सकता है।

  • ओटीपी में सुविधाओं के उपयोग से इस तरह की समस्या को आसानी से हल किया जा सकता है, जो कि मक्खी और इतने पर कोड अपडेट करने के तरीके भी प्रदान करते हैं ( OTP Design Principles देखें)।

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

मैसेंजर को "क्लाइंट" को एक केंद्रीय सर्वर से कनेक्ट करने की अनुमति देकर और कहा जाता है कि वे कौन और कहां हैं। यही है, एक उपयोगकर्ता को एरलैंग नोड का नाम जानने की आवश्यकता नहीं है जहां एक अन्य उपयोगकर्ता संदेश भेजने के लिए स्थित है।

फ़ाइल messenger.erl :

%%% Message passing utility.  
%%% User interface:
%%% logon(Name)
%%%     One user at a time can log in from each Erlang node in the
%%%     system messenger: and choose a suitable Name. If the Name
%%%     is already logged in at another node or if someone else is
%%%     already logged in at the same node, login will be rejected
%%%     with a suitable error message.
%%% logoff()
%%%     Logs off anybody at that node
%%% message(ToName, Message)
%%%     sends Message to ToName. Error messages if the user of this 
%%%     function is not logged on or if ToName is not logged on at
%%%     any node.
%%%
%%% One node in the network of Erlang nodes runs a server which maintains
%%% data about the logged on users. The server is registered as "messenger"
%%% Each node where there is a user logged on runs a client process registered
%%% as "mess_client" 
%%%
%%% Protocol between the client processes and the server
%%% ----------------------------------------------------
%%% 
%%% To server: {ClientPid, logon, UserName}
%%% Reply {messenger, stop, user_exists_at_other_node} stops the client
%%% Reply {messenger, logged_on} logon was successful
%%%
%%% To server: {ClientPid, logoff}
%%% Reply: {messenger, logged_off}
%%%
%%% To server: {ClientPid, logoff}
%%% Reply: no reply
%%%
%%% To server: {ClientPid, message_to, ToName, Message} send a message
%%% Reply: {messenger, stop, you_are_not_logged_on} stops the client
%%% Reply: {messenger, receiver_not_found} no user with this name logged on
%%% Reply: {messenger, sent} Message has been sent (but no guarantee)
%%%
%%% To client: {message_from, Name, Message},
%%%
%%% Protocol between the "commands" and the client
%%% ----------------------------------------------
%%%
%%% Started: messenger:client(Server_Node, Name)
%%% To client: logoff
%%% To client: {message_to, ToName, Message}
%%%
%%% Configuration: change the server_node() function to return the
%%% name of the node where the messenger server runs

-module(messenger).
-export([start_server/0, server/1, logon/1, logoff/0, message/2, client/2]).

%%% Change the function below to return the name of the node where the
%%% messenger server runs
server_node() ->
    [email protected]

%%% This is the server process for the "messenger"
%%% the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...]
server(User_List) ->
    receive
        {From, logon, Name} ->
            New_User_List = server_logon(From, Name, User_List),
            server(New_User_List);
        {From, logoff} ->
            New_User_List = server_logoff(From, User_List),
            server(New_User_List);
        {From, message_to, To, Message} ->
            server_transfer(From, To, Message, User_List),
            io:format("list is now: ~p~n", [User_List]),
            server(User_List)
    end.

%%% Start the server
start_server() ->
    register(messenger, spawn(messenger, server, [[]])).


%%% Server adds a new user to the user list
server_logon(From, Name, User_List) ->
    %% check if logged on anywhere else
    case lists:keymember(Name, 2, User_List) of
        true ->
            From ! {messenger, stop, user_exists_at_other_node},  %reject logon
            User_List;
        false ->
            From ! {messenger, logged_on},
            [{From, Name} | User_List]        %add user to the list
    end.

%%% Server deletes a user from the user list
server_logoff(From, User_List) ->
    lists:keydelete(From, 1, User_List).


%%% Server transfers a message between user
server_transfer(From, To, Message, User_List) ->
    %% check that the user is logged on and who he is
    case lists:keysearch(From, 1, User_List) of
        false ->
            From ! {messenger, stop, you_are_not_logged_on};
        {value, {From, Name}} ->
            server_transfer(From, Name, To, Message, User_List)
    end.
%%% If the user exists, send the message
server_transfer(From, Name, To, Message, User_List) ->
    %% Find the receiver and send the message
    case lists:keysearch(To, 2, User_List) of
        false ->
            From ! {messenger, receiver_not_found};
        {value, {ToPid, To}} ->
            ToPid ! {message_from, Name, Message}, 
            From ! {messenger, sent} 
    end.


%%% User Commands
logon(Name) ->
    case whereis(mess_client) of 
        undefined ->
            register(mess_client, 
                     spawn(messenger, client, [server_node(), Name]));
        _ -> already_logged_on
    end.

logoff() ->
    mess_client ! logoff.

message(ToName, Message) ->
    case whereis(mess_client) of % Test if the client is running
        undefined ->
            not_logged_on;
        _ -> mess_client ! {message_to, ToName, Message},
             ok
end.


%%% The client process which runs on each server node
client(Server_Node, Name) ->
    {messenger, Server_Node} ! {self(), logon, Name},
    await_result(),
    client(Server_Node).

client(Server_Node) ->
    receive
        logoff ->
            {messenger, Server_Node} ! {self(), logoff},
            exit(normal);
        {message_to, ToName, Message} ->
            {messenger, Server_Node} ! {self(), message_to, ToName, Message},
            await_result();
        {message_from, FromName, Message} ->
            io:format("Message from ~p: ~p~n", [FromName, Message])
    end,
    client(Server_Node).

%%% wait for a response from the server
await_result() ->
    receive
        {messenger, stop, Why} -> % Stop the client 
            io:format("~p~n", [Why]),
            exit(normal);
        {messenger, What} ->  % Normal response
            io:format("~p~n", [What])
    end.

इस कार्यक्रम का उपयोग करने के लिए, आपको निम्न करने की आवश्यकता है:

  • server_node() फ़ंक्शन को कॉन्फ़िगर करें।
  • प्रत्येक कंप्यूटर पर उस निर्देशिका के लिए संकलित कोड ( messenger.beam ) को कॉपी करें जहां आप Erlang शुरू करते हैं।

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

चार एरलैंग नोड्स शुरू किए गए हैं: मैसेंजर @ सुपर, सी 1 @ बिल्बो, सी 2 @ कोसकेन, सी 3 @ गोलम।

पहले मैसेंजर @ सुपर पर सर्वर शुरू किया गया है:

([email protected])1> messenger:start_server().
true

अब पीटर ने c1 @ bilbo पर लॉग इन किया:

([email protected])1> messenger:logon(peter).
true
logged_on

जेम्स ने c2 @ कोसकेन पर लॉग इन किया:

([email protected])1> messenger:logon(james).
true
logged_on

और फ्रेड ने c3 @ गोलम पर लॉग इन किया:

([email protected])1> messenger:logon(fred).
true
logged_on

अब पीटर फ्रेड को एक संदेश भेजता है:

([email protected])2> messenger:message(fred, "hello").
ok
sent

फ्रेड संदेश प्राप्त करता है और पीटर को संदेश भेजता है और लॉग ऑफ करता है:

Message from peter: "hello"
([email protected])2> messenger:message(peter, "go away, I'm busy").
ok
sent
([email protected])3> messenger:logoff().
logoff

जेम्स अब फ्रेड को एक संदेश भेजने की कोशिश करता है:

([email protected])2> messenger:message(fred, "peter doesn't like you").
ok
receiver_not_found

लेकिन यह विफल रहता है क्योंकि फ्रेड पहले ही लॉग ऑफ कर चुके हैं।

पहले हमें कुछ नई अवधारणाओं पर नजर डालते हैं जिन्हें पेश किया गया है।

server_transfer फ़ंक्शन के दो संस्करण हैं: चार तर्क ( server_transfer/4 ) के साथ एक और पांच ( server_transfer/5 ) के साथ एक। इन्हें एरलांग ने दो अलग-अलग कार्यों के रूप में माना है।

ध्यान दें कि server फ़ंक्शन को कैसे लिखना है ताकि वह खुद को server(User_List) माध्यम से कॉल करे, और इस तरह एक लूप बनाता है। एर्लैंग कंपाइलर "चतुर" है और कोड का अनुकूलन करता है ताकि यह वास्तव में एक प्रकार का लूप हो न कि एक उचित फ़ंक्शन कॉल। लेकिन यह केवल तभी काम करता है जब कॉल के बाद कोई कोड न हो। अन्यथा, कंपाइलर कॉल को वापस करने और एक उचित फ़ंक्शन कॉल करने की उम्मीद करता है। इससे हर लूप के लिए प्रक्रिया बड़ी और बड़ी होती जाएगी।

lists मॉड्यूल में कार्यों का उपयोग किया जाता है। यह एक बहुत ही उपयोगी मॉड्यूल है और मैनुअल पेज के एक अध्ययन की सिफारिश की जाती है ( erl -man lists )। lists:keymember(Key,Position,Lists) ट्यूपल्स की एक सूची के माध्यम से देखती है और यह देखने के लिए प्रत्येक टपल में Position को देखती है कि क्या यह Key के समान है। पहला तत्व स्थिति 1 है। यदि यह एक नल को खोजता है जहां Position का तत्व Key के समान है, तो यह true , अन्यथा false

3> lists:keymember(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
true
4> lists:keymember(p, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
false

lists:keydelete उसी तरह से काम करती है, लेकिन पाया गया पहला टपल हटा देती है (यदि कोई हो) और शेष सूची लौटा देती है:

5> lists:keydelete(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
[{x,y,z},{b,b,b},{q,r,s}]

lists:keysearch की तरह होती lists:keymember , लेकिन यह {value,Tuple_Found} या परमाणु को false {value,Tuple_Found}

lists मॉड्यूल में कई बहुत उपयोगी कार्य हैं।

एक एरलैंग प्रक्रिया (वैचारिक रूप से) तब तक चलती है जब तक कि उसे receive नहीं receive और कोई संदेश नहीं है जिसे वह संदेश कतार में प्राप्त करना चाहता है। "वैचारिक रूप से" यहाँ उपयोग किया जाता है क्योंकि सिस्टम में सक्रिय प्रक्रियाओं के बीच एर्लांग प्रणाली सीपीयू समय को साझा करती है।

एक प्रक्रिया समाप्त हो जाती है जब ऐसा करने के लिए अधिक कुछ नहीं होता है, अर्थात, अंतिम फ़ंक्शन जिसे यह केवल रिटर्न कहता है और दूसरे फ़ंक्शन को कॉल नहीं करता है। किसी प्रक्रिया को समाप्त करने का दूसरा तरीका इसके लिए exit/1 को कॉल करना है। exit/1 तर्क exit/1 का एक विशेष अर्थ है, जिसकी चर्चा बाद में की गई। इस उदाहरण में, exit(normal) किया जाता है, जिसका प्रभाव उसी प्रक्रिया के रूप में होता है जो कॉल करने के लिए कार्यों से बाहर चलती है।

BIF whereis(RegisteredName) जाँच करता है कि नाम RegisteredName की कोई पंजीकृत प्रक्रिया मौजूद है या नहीं। यदि यह मौजूद है, तो उस प्रक्रिया का पृष्ठ लौटाया जाता है। यदि यह मौजूद नहीं है, तो परमाणु undefined वापस आ जाता है।

अब तक आपको मैसेंजर-मॉड्यूल के अधिकांश कोड को समझने में सक्षम होना चाहिए। आइए एक मामले का विस्तार से अध्ययन करें: एक संदेश एक उपयोगकर्ता से दूसरे उपयोगकर्ता को भेजा जाता है।

उपर्युक्त उदाहरण में पहला उपयोगकर्ता "संदेश" भेजता है:

messenger:message(fred, "hello")

परीक्षण के बाद कि ग्राहक प्रक्रिया मौजूद है:

whereis(mess_client) 

और एक संदेश mess_client को भेजा जाता है:

mess_client ! {message_to, fred, "hello"}

क्लाइंट द्वारा सर्वर को संदेश भेजता है:

{messenger, [email protected]} ! {self(), message_to, fred, "hello"},

और सर्वर से उत्तर की प्रतीक्षा करता है।

सर्वर यह संदेश प्राप्त करता है और कॉल करता है:

server_transfer(From, fred, "hello", User_List),

यह जाँचता है कि पीआईडी From User_List :

lists:keysearch(From, 1, User_List) 

यदि keysearch परमाणु को false लौटाती है, तो कुछ त्रुटि हुई है और सर्वर संदेश वापस भेजता है:

From ! {messenger, stop, you_are_not_logged_on}

यह क्लाइंट द्वारा प्राप्त किया जाता है, जो बदले में exit(normal) और समाप्त हो जाता है। यदि keysearch {value,{From,Name}} लौटाती हैं {value,{From,Name}} तो यह निश्चित है कि उपयोगकर्ता लॉग ऑन है और उसका नाम (पीटर) चर Name

अब हमें कॉल करें:

server_transfer(From, peter, fred, "hello", User_List)

ध्यान दें कि जैसा कि यह server_transfer/5 , यह पिछले फ़ंक्शन server_transfer/4 के समान नहीं है। एक अन्य keysearch खोज ग्राहक के User_List को ढूंढने के लिए User_List पर की User_List है:

lists:keysearch(fred, 2, User_List)

इस समय तर्क 2 का उपयोग किया जाता है, जो टपल में दूसरा तत्व है। यदि यह परमाणु को false लौटाता है, तो लॉग ऑन नहीं किया जाता है और निम्न संदेश भेजा जाता है:

From ! {messenger, receiver_not_found};

यह क्लाइंट द्वारा प्राप्त किया जाता है।

यदि keysearch वापस आती हैं:

{value, {ToPid, fred}}

निम्न संदेश को fred के ग्राहक को भेजा जाता है:

ToPid ! {message_from, peter, "hello"}, 

निम्न संदेश पेटीएम क्लाइंट को भेजा जाता है:

From ! {messenger, sent} 

फ्रेड का ग्राहक संदेश प्राप्त करता है और उसे प्रिंट करता है:

{message_from, peter, "hello"} ->
    io:format("Message from ~p: ~p~n", [peter, "hello"])

पीटर के ग्राहक को await_result फ़ंक्शन में संदेश प्राप्त होता है।