下面我們進(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 文件:
.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)。
更多建議: