下面我們進一步對 ping pong 示例程序進行改進。 這一次,我們要讓 “ping”、“pong” 進程分別位于不同的計算機上。要想讓這個程序工作,你首先的搭建一下分布式的系統(tǒng)環(huán)境。分布式 Erlang 系統(tǒng)的實現(xiàn)提供了基本的安全機制,它阻止未授權(quán)的外部設(shè)備訪問本機的 Erlang 系統(tǒng)。同一個系統(tǒng)中的 Erlang 要想相互通信需要設(shè)置相同的 magic cookie。設(shè)置 magic cookie 最便捷地實現(xiàn)方式就是在你打算運行分布式 Erlang 系統(tǒng)的所有計算機的 home 目錄下創(chuàng)建一個 .erlang.cookie 文件:
.erlang.cookie 文件只有一行內(nèi)容,這一行包含一個原子值。例如,在 Linux 或 UNIX 系統(tǒng)的 shell 執(zhí)行如下命令:
$ cd
$ cat > .erlang.cookie
this_is_very_secret
$ chmod 400 .erlang.cookie
使用 chmod 命令讓 .erlang.cookie 文件只有文件擁者可以訪問。這個是必須設(shè)置的。
當你想要啟動 erlang 系統(tǒng)與其它 erlang 系統(tǒng)通信時,你需要給 erlang 系統(tǒng)一個名稱,例如:
$erl -sname my_name
在后面你還會看到更加詳細的內(nèi)容。如果你想嘗試一下分布式 Erlang 系統(tǒng),而又只有一臺計算機,你可以在同一臺計算機上分別啟動兩個 Erlang 系統(tǒng),并分別賦予不同的名稱即可。運行在每個計算機上的 Erlang 被稱為一個 Erang 結(jié)點(Erlang Node)。
(注意:erl -sname 要求所有的結(jié)點在同一個 IP 域內(nèi)。如果我們的 Erlang 結(jié)點位于不同的 IP 域中,則我們需要使用 -name,而且需要指定所有的 IP 地址。)
下面這個修改后的 ping pong 示例程序可以分別運行在兩個結(jié)點之上:
-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è)這兩臺計算分別稱之為 gollum 與 kosken。在 kosken 上啟動結(jié)點 ping。在 gollum 上啟動結(jié)點 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 上啟動 "pong" 進程:
(pong@gollum)1> tut17:start_pong().
true
然后在 kosken 上啟動 “ping” 進程(從上面的代碼中可以看出,start_ping 的函數(shù)的其中一個參數(shù)為 “pong” 進程所在結(jié)點的名稱):
(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)開始運行了。在 “pong” 的這一端:
(pong@gollum)2>
Pong received ping
Pong received ping
Pong received ping
Pong finished
(pong@gollum)2>
再看一下 tut17 的代碼,你可以看到 pong 函數(shù)根本就沒有發(fā)生任何改變,無論 “ping” 進程運行在哪個結(jié)點下,下面這一行代碼都可以正確的工作:
{ping, Ping_PID} ->
io:format("Pong received ping~n", []),
Ping_PID ! pong,
因此,Erlang 的進程標識符中包含了程序運行在哪個結(jié)點上的位置信息。所以,如果你知道了進程的進程標識符,無論進程是運行在本地結(jié)點上還是其它結(jié)點上面,"!" 操作符都可以將消息發(fā)送到該進程。
要想通過進程注冊的名稱向其它結(jié)點上的進程發(fā)送消息,這時候就有一些不同之處了:
{pong, Pong_Node} ! {ping, self()},
這個時候,我們就不能再只用 registered_name 作為參數(shù)了,而需要使用元組 {registered_name,node_name} 作為注冊進程的名稱參數(shù)。
在之前的代碼中了,“ping”、“pong” 進程是在兩個獨立的 Erlang 結(jié)點上通過 shell 啟動的。 spawn 也可以在其它結(jié)點(非本地結(jié)點)啟動新的進程。
下面這段示例代碼也是一個 ping pong 程序,但是這一次 “ping” 是在異地結(jié)點上啟動的:
-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é)點(注意不是進程 “ping”)已經(jīng)在 kosken 中啟動(譯注:可以理解 Erlang 結(jié)點已經(jīng)啟動),則在 gollum 會有如下的輸出:
<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é)點上。這是因為 I/O 系統(tǒng)發(fā)現(xiàn)進程是由其它結(jié)點啟動的時候,會自將輸出內(nèi)容輸出到啟動進程所在的結(jié)點。
更多建議: