鍍金池/ 教程/ 區(qū)塊鏈/ 分布式編程
注冊(cè)進(jìn)程名稱
錯(cuò)誤處理
完整示例
分布式編程
消息傳遞
if 與 case
健壯性
映射 (Map)
高階函數(shù) (Fun)
輸出至終端
更多關(guān)于列表的內(nèi)容
內(nèi)置函數(shù) (BIF)
模塊與函數(shù)
將大程序分在多個(gè)文件中
匹配、Guards 與變量的作用域
超時(shí)
列表
完整示例
頭文件
標(biāo)準(zhǔn)模塊與使用手冊(cè)
進(jìn)程
記錄
增加健壯性后的完整示例
Erlang Shell
原子類型

分布式編程

下面我們進(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è)備訪問(wèn)本機(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 文件只有文件擁者可以訪問(wèn)。這個(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ù)根本就沒(méi)有發(fā)生任何改變,無(wú)論 “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í)符,無(wú)論進(jìn)程是運(yùn)行在本地結(jié)點(diǎn)上還是其它結(jié)點(diǎn)上面,"!" 操作符都可以將消息發(fā)送到該進(jìn)程。

要想通過(guò)進(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)上通過(guò) 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)。

上一篇:錯(cuò)誤處理下一篇:列表