Erlang 分布式編程

2022-07-07 15:41 更新

Erlang分布式編程

下面我們進(jìn)一步對(duì) ping pong 示例程序進(jìn)行改進(jìn)。 這一次,我們要讓 “ping”、“pong” 進(jìn)程分別位于不同的計(jì)算機(jī)上。要想讓這個(gè)程序工作,你首先的搭建一下分布式的系統(tǒng)環(huán)境。分布式 Erlang 系統(tǒng)的實(shí)現(xiàn)提供了基本的安全機(jī)制,它阻止未授權(quán)的外部設(shè)備訪問本機(jī)的 Erlang 系統(tǒng)。同一個(gè)系統(tǒng)中的 Erlang 要想相互通信需要設(shè)置相同的 magic cookie。設(shè)置 magic cookie 最便捷地實(shí)現(xiàn)方式就是在你打算運(yùn)行分布式 Erlang 系統(tǒng)的所有計(jì)算機(jī)的 home 目錄下創(chuàng)建一個(gè) .erlang.cookie 文件:

  • 在 windows 系統(tǒng)中,home 目錄為環(huán)境變量 $HOME 指定的目錄--這個(gè)變量的值可能需要你手動(dòng)設(shè)置
  • 在 Linux 或者 UNIX 系統(tǒng)中簡(jiǎn)單很多,你只需要在執(zhí)行 cd 命令后所進(jìn)入的目錄下創(chuàng)建一個(gè) .erlang.cookie 文件就可以了。

.erlang.cookie 文件只有一行內(nèi)容,這一行包含一個(gè)原子值。例如,在 Linux 或 UNIX 系統(tǒng)的 shell 執(zhí)行如下命令:

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

使用 chmod 命令讓 .erlang.cookie 文件只有文件擁者可以訪問。這個(gè)是必須設(shè)置的。

當(dāng)你想要啟動(dòng) erlang 系統(tǒng)與其它 erlang 系統(tǒng)通信時(shí),你需要給 erlang 系統(tǒng)一個(gè)名稱,例如:

$erl -sname my_name

在后面你還會(huì)看到更加詳細(xì)的內(nèi)容。如果你想嘗試一下分布式 Erlang 系統(tǒng),而又只有一臺(tái)計(jì)算機(jī),你可以在同一臺(tái)計(jì)算機(jī)上分別啟動(dòng)兩個(gè) Erlang 系統(tǒng),并分別賦予不同的名稱即可。運(yùn)行在每個(gè)計(jì)算機(jī)上的 Erlang 被稱為一個(gè) Erang 結(jié)點(diǎn)(Erlang Node)。

(注意:erl -sname 要求所有的結(jié)點(diǎn)在同一個(gè) IP 域內(nèi)。如果我們的 Erlang 結(jié)點(diǎn)位于不同的 IP 域中,則我們需要使用 -name,而且需要指定所有的 IP 地址。)

下面這個(gè)修改后的 ping pong 示例程序可以分別運(yùn)行在兩個(gè)結(jié)點(diǎn)之上:

-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]).

我們假設(shè)這兩臺(tái)計(jì)算分別稱之為 gollum 與 kosken。在 kosken 上啟動(dòng)結(jié)點(diǎn) ping。在 gollum 上啟動(dòng)結(jié)點(diǎn) pong。

在 kosken 系統(tǒng)上(Linux/Unix 系統(tǒng)):

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

Eshell V5.2.3.7  (abort with ^G)
(ping@kosken)1>

在 gollum 上:

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

Eshell V5.2.3.7  (abort with ^G)
(pong@gollum)1>

下面,在 gollum 上啟動(dòng) "pong" 進(jìn)程:

(pong@gollum)1> tut17:start_pong().
true

然后在 kosken 上啟動(dòng) “ping” 進(jìn)程(從上面的代碼中可以看出,start_ping 的函數(shù)的其中一個(gè)參數(shù)為 “pong” 進(jìn)程所在結(jié)點(diǎn)的名稱):

(ping@kosken)1> tut17:start_ping(pong@gollum).
<0.37.0>
Ping received pong
Ping received pong 
Ping received pong
ping finished

如上所示,ping pong 程序已經(jīng)開始運(yùn)行了。在 “pong” 的這一端:

(pong@gollum)2>
Pong received ping                 
Pong received ping                 
Pong received ping                 
Pong finished                      
(pong@gollum)2>

再看一下 tut17 的代碼,你可以看到 pong 函數(shù)根本就沒有發(fā)生任何改變,無論 “ping” 進(jìn)程運(yùn)行在哪個(gè)結(jié)點(diǎn)下,下面這一行代碼都可以正確的工作:

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

因此,Erlang 的進(jìn)程標(biāo)識(shí)符中包含了程序運(yùn)行在哪個(gè)結(jié)點(diǎn)上的位置信息。所以,如果你知道了進(jìn)程的進(jìn)程標(biāo)識(shí)符,無論進(jìn)程是運(yùn)行在本地結(jié)點(diǎn)上還是其它結(jié)點(diǎn)上面,"!" 操作符都可以將消息發(fā)送到該進(jìn)程。

要想通過進(jìn)程注冊(cè)的名稱向其它結(jié)點(diǎn)上的進(jìn)程發(fā)送消息,這時(shí)候就有一些不同之處了:

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

這個(gè)時(shí)候,我們就不能再只用 registered_name 作為參數(shù)了,而需要使用元組 {registered_name,node_name} 作為注冊(cè)進(jìn)程的名稱參數(shù)。

在之前的代碼中了,“ping”、“pong” 進(jìn)程是在兩個(gè)獨(dú)立的 Erlang 結(jié)點(diǎn)上通過 shell 啟動(dòng)的。 spawn 也可以在其它結(jié)點(diǎn)(非本地結(jié)點(diǎn))啟動(dòng)新的進(jìn)程。

下面這段示例代碼也是一個(gè) ping pong 程序,但是這一次 “ping” 是在異地結(jié)點(diǎn)上啟動(dòng)的:

-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()]).

假設(shè)在 Erlang 系統(tǒng) ping 結(jié)點(diǎn)(注意不是進(jìn)程 “ping”)已經(jīng)在 kosken 中啟動(dòng)(譯注:可以理解 Erlang 結(jié)點(diǎn)已經(jīng)啟動(dòng)),則在 gollum 會(huì)有如下的輸出:

<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

注意所有的內(nèi)容都輸出到了 gollum 結(jié)點(diǎn)上。這是因?yàn)?I/O 系統(tǒng)發(fā)現(xiàn)進(jìn)程是由其它結(jié)點(diǎn)啟動(dòng)的時(shí)候,會(huì)自將輸出內(nèi)容輸出到啟動(dòng)進(jìn)程所在的結(jié)點(diǎn)。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)