檢視原始碼 分散式協定
此描述遠非完整。如果協定更新,將會更新此描述。然而,自 Erlang 節點到 Erlang Port Mapper Daemon (EPMD) 以及 Erlang 節點之間的協定,多年來皆保持穩定。
分散式協定可以分為四個部分
- 底層 Socket 連線 (1)
- 交握、交換節點名稱和驗證 (2)
- 驗證(由
net_kernel
完成)(3) - 已連線 (4)
節點透過(在另一主機上的)EPMD 取得另一個節點的連接埠號,以啟動連線請求。
對於每個執行分散式 Erlang 節點的主機,也必須執行 EPMD。EPMD 可以明確啟動,也可以在 Erlang 節點啟動時自動啟動。
預設情況下,EPMD 會監聽連接埠 4369。
上面的 (3) 和 (4) 在同一層級執行,但如果 net_kernel
使用無效的 cookie 通訊,則會斷開另一個節點的連線(1 秒後)。
所有多位元組欄位中的整數都以大端序排列。
警告
Erlang 分散式協定本身並不安全,也不打算如此。為了獲得安全的分散式,分散式節點應設定為使用 tls 進行分散式。請參閱 使用 SSL 進行 Erlang 分散式 使用者指南,以了解如何設定安全的分散式節點。
EPMD 協定
EPMD 協定支援各種任務
- 註冊節點
- 取消註冊節點
- 取得另一個節點的分散式連接埠
- 取得所有已註冊的名稱
- 從 EPMD 傾印所有資料
- 終止 EPMD
STOP_REQ
(未使用)
EPMD 針對這些任務提供的請求摘要如下圖所示。
---
title: Summary of EPMD Requests
---
sequenceDiagram
participant client as Client (or Node)
participant EPMD
Note over EPMD: Register a Node in EPMD
client ->> EPMD: ALIVE2_REQ
alt
EPMD -->> client: ALIVE2_X_RESP
else
EPMD -->> client: ALIVE2_RESP
end
Note over EPMD: Unregister a Node in EPMD
client ->> EPMD: ALIVE_CLOSE_REQ
Note over client: Get the Distribution Port of Another Node
client ->> EPMD: PORT_PLEASE2_REQ
EPMD -->> client: PORT2_RESP
Note over client: Get All Registered Names from EPMD
client ->> EPMD: NAMES_REQ
EPMD -->> client: NAMES_RESP
Note over EPMD: Dump all Data from EPMD
client ->> EPMD: DUMP_REQ
EPMD -->> client: DUMP_RESP
Note over EPMD: Kill EPMD
client ->> EPMD: KILL_REQ
EPMD -->> client: KILL_RESP
Note over EPMD: STOP_REQ (Not Used)
client ->> EPMD: STOP_REQ
EPMD -->> client: STOP_OK_RESP
EPMD -->> client: STOP_NOTOK_RESP
每個請求 *_REQ
都以 2 位元組的長度欄位開頭。因此,整體請求格式如下
2 | n |
---|---|
長度 | 請求 |
表格:請求格式
在 EPMD 中註冊節點
當分散式節點啟動時,它會在 EPMD 中註冊自己。從節點傳送到 EPMD 的訊息是下面描述的 ALIVE2_REQ
。來自 EPMD 的回應是 ALIVE2_X_RESP
(或 ALIVE2_RESP
)
---
title: Register a Node in EPMD
---
sequenceDiagram
participant client as Client (or Node)
participant EPMD
client ->> EPMD: ALIVE2_REQ
alt
EPMD -->> client: ALIVE2_X_RESP
else
EPMD -->> client: ALIVE2_RESP
end
1 | 2 | 1 | 1 | 2 | 2 | 2 | Nlen | 2 | Elen |
---|---|---|---|---|---|---|---|---|---|
120 | PortNo | NodeType | Protocol | HighestVersion | LowestVersion | Nlen | NodeName | Elen | Extra |
表格:ALIVE2_REQ (120)
PortNo
- 節點接受連線請求的連接埠號碼。NodeType
- 77 = 一般 Erlang 節點,72 = 隱藏節點(C 節點),...Protocol
- 0 = TCP/IPv4,...HighestVersion
- 此節點可以處理的最高分散式協定版本。在 OTP 23 及更高版本中的值為 6。較舊的節點僅支援版本 5。LowestVersion
- 此節點可以處理的最低分散式版本。在 OTP 25 及更高版本中的值為 6,因為已取消支援連接到早於 OTP 23 的節點。Nlen
-NodeName
欄位的長度(以位元組為單位)。NodeName
- 節點名稱,為Nlen
位元組的 UTF-8 編碼字串。Elen
-Extra
欄位的長度。Extra
-Elen
位元組的額外欄位。
與 EPMD 建立的連線必須保持,直到該節點是分散式節點為止。當連線關閉時,該節點會自動從 EPMD 取消註冊。
回應訊息為 ALIVE2_X_RESP
或 ALIVE2_RESP
,具體取決於分散式版本。如果節點和 EPMD 都支援分散式版本 6,則回應為 ALIVE2_X_RESP
,否則為較舊的 ALIVE2_RESP
1 | 1 | 4 |
---|---|---|
118 | 結果 | 建立 |
表格:具有 32 位元建立的 ALIVE2_X_RESP (118)
1 | 1 | 2 |
---|---|---|
121 | 結果 | 建立 |
表格:具有 16 位元建立的 ALIVE2_RESP (121)
Result = 0 -> ok, result > 0 -> 錯誤。
從 EPMD 取消註冊節點
節點會透過關閉節點註冊時與 EPMD 建立的 TCP 連線,從 EPMD 取消註冊自己
---
title: Register a Node in EPMD
---
sequenceDiagram
participant client as Client (or Node)
participant EPMD
client ->> EPMD: ALIVE_CLOSE_REQ
取得另一個節點的分散式連接埠
當一個節點想要連線到另一個節點時,它會先向該節點所在主機上的 EPMD 發出 PORT_PLEASE2_REQ
請求,以取得該節點正在監聽的分散式連接埠
---
title: Get the Distribution Port of Another Node
---
sequenceDiagram
participant client as Client (or Node)
participant EPMD
client ->> EPMD: PORT_PLEASE2_REQ
EPMD -->> client: PORT2_RESP
1 | N |
---|---|
122 | NodeName |
表格:PORT_PLEASE2_REQ (122)
其中 N = Length
- 1。
1 | 1 |
---|---|
119 | 結果 |
表格:PORT2_RESP (119) 回應指出錯誤,Result > 0
或
1 | 1 | 2 | 1 | 1 | 2 | 2 | 2 | Nlen | 2 | Elen |
---|---|---|---|---|---|---|---|---|---|---|
119 | 結果 | PortNo | NodeType | Protocol | HighestVersion | LowestVersion | Nlen | NodeName | Elen | >Extra |
表格:PORT2_RESP,Result = 0
如果 Result
> 0,則封包僅包含 [119, Result]
。
當 EPMD 發送資訊後,會關閉 socket。
從 EPMD 取得所有已註冊的名稱
此請求透過 Erlang 函數 net_adm:names/1,2
使用。開啟到 EPMD 的 TCP 連線並發送此請求
---
title: Get All Registered Names from EPMD
---
sequenceDiagram
participant client as Client (or Node)
participant EPMD
client ->> EPMD: NAMES_REQ
EPMD -->> client: NAMES_RESP
1 |
---|
110 |
表格:NAMES_REQ (110)
NAMES_REQ
的回應如下
4 | |
---|---|
EPMDPortNo | NodeInfo* |
表格:NAMES_RESP
NodeInfo
是針對每個活動節點寫入的字串。當所有 NodeInfo
都已寫入時,EPMD 會關閉連線。
NodeInfo
在 Erlang 中表示為
io:format("name ~ts at port ~p~n", [NodeName, Port]).
從 EPMD 傾印所有資料
此請求實際上並未使用,它被視為偵錯功能。
---
title: Dump All Data from EPMD
---
sequenceDiagram
participant client as Client (or Node)
participant EPMD
client ->> EPMD: DUMP_REQ
EPMD -->> client: DUMP_RESP
1 |
---|
100 |
表格:DUMP_REQ
DUMP_REQ
的回應如下
4 | |
---|---|
EPMDPortNo | NodeInfo* |
表格:DUMP_RESP
NodeInfo
是針對 EPMD 中保留的每個節點寫入的字串。當所有 NodeInfo
都已寫入時,EPMD 會關閉連線。
NodeInfo
在 Erlang 中表示為
io:format("active name ~ts at port ~p, fd = ~p~n",
[NodeName, Port, Fd]).
或
io:format("old/unused name ~ts at port ~p, fd = ~p ~n",
[NodeName, Port, Fd]).
終止 EPMD
此請求會終止正在執行的 EPMD。它幾乎從未使用過。
---
title: Kill EPMD
---
sequenceDiagram
participant client as Client (or Node)
participant EPMD
client ->> EPMD: KILL_REQ
EPMD -->> client: KILL_RESP
1 |
---|
107 |
表格:KILL_REQ
KILL_REQ
的回應如下
2 |
---|
OKString |
表格:KILL_RESP
其中 OKString
為 "OK"。
STOP_REQ(未使用)
---
title: STOP_REQ (Not Used)
---
sequenceDiagram
participant client as Client (or Node)
participant EPMD
client ->> EPMD: STOP_REQ
EPMD -->> client: STOP_OK_RESP
EPMD -->> client: STOP_NOTOK_RESP
1 | n |
---|---|
115 | NodeName |
表格:STOP_REQ
其中 n = Length
- 1。
STOP_REQ
的回應如下
7 |
---|
OKString |
表格:STOP_RESP
其中 OKString
為 "STOPPED"。
負面回應可能如下所示
7 |
---|
NOKString |
表格:STOP_NOTOK_RESP
其中 NOKString
為 "NOEXIST"。
分散式交握
本節介紹節點之間用於建立連線的分散式交握協定。該協定在 Erlang/OTP R6 中引入,並在 OTP 23 中進行了修改。從 OTP 25 開始,已取消對舊協定的支援。因此,OTP 25 節點無法連接到早於 OTP 23 的節點。此文件僅描述 OTP 25 使用的協定部分。
注意
OTP 25.0 中引入的錯誤可能會導致 OTP 25 節點拒絕來自未使用
epmd
來取得遠端節點版本資訊的 OTP 23 和 24 節點的連線嘗試。此問題已在 OTP 25.3 中修復。
一般
TCP/IP 分散式使用一個交握,該交握需要基於連線的協定,也就是說,該協定在交握程序後不包含任何驗證。
這並非完全安全,因為它容易受到接管攻擊,但這是公平安全和效能之間的權衡。
cookie 永遠不會以純文字發送,並且交握程序需要用戶端(稱為 A
)首先證明它可以產生足夠的摘要。摘要是使用 MD5 訊息摘要演算法產生的,並且挑戰預期為隨機數。
定義
挑戰是按大端序排列的 32 位元整數。下面,函數 gen_challenge()
會傳回用作挑戰的隨機 32 位元整數。
摘要是 cookie(以文字形式)與挑戰(以文字形式)串聯的(16 位元組)MD5 雜湊值。下面,函數 gen_digest(Challenge, Cookie)
會產生如上所述的摘要。
out_cookie
是用於與特定節點進行外向通訊的 cookie,因此 A
對於 B
的 out_cookie
對應於 B
對於 A
的 in_cookie
,反之亦然。 A
對於 B
的 out_cookie
和 A
對於 B
的 in_cookie
*不一定* 相同。下面,函數 out_cookie(Node)
會傳回目前節點對於 Node
的 out_cookie
。
in_cookie
是另一個節點與我們通訊時預期使用的 cookie,因此 A
對於 B
的 in_cookie
對應於 B
對於 A
的 out_cookie
。下面,函數 in_cookie(Node)
會傳回目前節點對於 Node
的 in_cookie
。
cookie 是可以被視為密碼的文字字串。
交握中的每個訊息都以一個 16 位元的大端整數開始,其中包含訊息長度(不計算最初的兩個位元組)。在 Erlang 中,這對應於 gen_tcp
中的選項 {packet, 2}
。請注意,在交握之後,分散式會切換為 4 位元組封包標頭。
交握詳情
假設有兩個節點,A
啟動交握,而 B
接受連線。
1) 連線/接受 -
A
透過 TCP/IP 連線到B
,而B
接受連線。2)
send_name
/receive_name
-A
將初始識別碼傳送到B
,而B
會收到該訊息。該訊息可以有兩種不同的格式,如下所示(已移除封包標頭)1 2 4 Nlen 'n'
Version=5
Flags
Name
表格:舊版 send_name ('n'),適用於協定版本 5
1 8 4 2 Nlen 'N'
Flags
建立
Nlen
Name
表格:協定版本 6 的新 send_name ('N')
舊的
send_name
格式僅從未使用epmd
的 OTP 23 和 24 節點傳送,因此不曉得遠端節點是否僅支援協定版本 5。Version
是一個 16 位元大端整數,且必須始終為值 5(即使節點A
支援版本 6)。Flags
是節點A
的能力旗標,以 32 位元大端格式表示。必須設定旗標位元DFLAG_HANDSHAKE_23
(因為節點A
必須支援版本 6)。Name
是A
的完整節點名稱,為位元組字串(封包長度表示其長度)。新的
send_name
會傳送到已知支援版本 6 的節點。Flags
是節點A
的能力旗標,以 64 位元大端格式表示。旗標位元DFLAG_HANDSHAKE_23
必須始終設定。Creation
是節點A
用來建立其 pids、ports 和 references 的節點化身識別符。Name
是A
的完整節點名稱,為位元組字串。Nlen
是節點名稱的位元組長度,以 16 位元大端格式表示。節點Name
後面的任何額外資料都必須接受並忽略。當設定
DFLAG_NAME_ME
時,Name
必須只是主機名稱(不含 @)。3)
recv_status
/send_status
-B
向A
傳送狀態訊息,指示是否允許連線。1 Slen 's'
Status
表格:狀態訊息的格式
's' 是訊息標籤。
Status
是狀態碼,以字串形式表示(非 Null 終止)。定義了以下狀態碼ok
- 交握將繼續。ok_simultaneous
- 交握將繼續,但會通知A
,B
還有另一個進行中的連線嘗試將會關閉(同步連線,其中A
的名稱大於B
的名稱,以字面比較)。nok
- 交握將不會繼續,因為B
已經有正在進行的交握,而該交握本身已啟動(同步連線,其中B
的名稱大於A
的名稱)。not_allowed
- 由於某些(未指定的)安全性原因,不允許連線。alive
- 與節點的連線已啟用,這表示節點A
感到混淆,或是先前具有此名稱的節點的 TCP 連線中斷尚未到達節點B
。請參閱以下步驟 3B。named:
- 交握將繼續,但A
透過設定旗標DFLAG_NAME_ME
要求動態節點名稱。A
的動態節點名稱會由B
從狀態訊息的結尾提供。A
的主機名稱在send_name
中以Name
傳送,將由節點B
用來產生完整的動態節點名稱。1 Slen=6 2 Nlen 4 's'
Status='named:'
Nlen
Name
建立
表格:'named:' 狀態訊息的格式
Name
是A
的完整動態節點名稱,為位元組字串。Nlen
是節點名稱的位元組長度,以 16 位元大端格式表示。Creation
是節點A
的化身識別符,由節點B
產生。節點Creation
後面的任何額外資料都必須接受並忽略。
3B)
send_status
/recv_status
- 如果狀態為alive
,節點A
會使用另一個狀態訊息回應,其中包含true
(表示連線將繼續(來自此節點的舊連線已中斷)),或false
(表示連線將關閉(連線嘗試是錯誤))。4)
recv_challenge
/send_challenge
- 如果狀態為ok
或ok_simultaneous
,交握會繼續,B
會向A
傳送另一個訊息,也就是挑戰。挑戰包含與A
最初傳送到B
的「名稱」訊息相同類型的資訊,加上一個 32 位元挑戰。1 8 4 4 2 Nlen 'N'
Flags
Challenge
建立
Nlen
Name
表格:新的挑戰訊息格式(版本 6)
Challenge
是一個 32 位元大端整數。其他欄位則是節點B
的旗標、建立和完整節點名稱,與send_name
訊息類似。節點Name
後面的任何額外資料都必須接受並忽略。4B)
send_complement
/recv_complement
- 只有在節點A
最初傳送舊名稱訊息時,才會從A
向B
傳送補充訊息。其中包含節點A
最初的舊名稱訊息中遺失的補充資訊。1 4 4 'c'
FlagsHigh
建立
表格:補充訊息
FlagsHigh
是節點A
的高能力旗標(位元 33-64),以 32 位元大端整數表示。Creation
是節點A
的化身識別符。5)
send_challenge_reply
/recv_challenge_reply
- 現在A
已產生摘要及其自身的挑戰。這些內容會一起以封包形式傳送至B
1 4 16 'r'
Challenge
Digest
表格:challenge_reply 訊息
Challenge
是A
針對B
要處理的挑戰。Digest
是A
從B
在上一個步驟中傳送的挑戰建構的 MD5 摘要。6)
recv_challenge_ack
/send_challenge_ack
-B
會檢查從A
收到的摘要是否正確,並根據從A
收到的挑戰產生摘要。接著,摘要會傳送至A
。訊息如下1 16 'a'
Digest
表格:challenge_ack 訊息
Digest
是B
針對A
的挑戰所計算的摘要。7) 檢查 -
A
會檢查來自B
的摘要,且連線已啟動。
半圖形檢視
A (initiator) B (acceptor)
TCP connect ------------------------------------>
TCP accept
send_name -------------------------------------->
recv_name
<---------------------------------------------- send_status
recv_status
(if status was 'alive'
send_status - - - - - - - - - - - - - - - - - ->
recv_status)
(ChB) ChB = gen_challenge()
<---------------------------------------------- send_challenge
recv_challenge
(if old send_name
send_complement - - - - - - - - - - - - - - - ->
recv_complement)
ChA = gen_challenge(),
OCA = out_cookie(B),
DiA = gen_digest(ChB, OCA)
(ChA, DiA)
send_challenge_reply --------------------------->
recv_challenge_reply
ICB = in_cookie(A),
check:
DiA == gen_digest (ChB, ICB)?
- if OK:
OCB = out_cookie(A),
DiB = gen_digest (ChA, OCB)
(DiB)
<----------------------------------------------- send_challenge_ack
recv_challenge_ack DONE
ICA = in_cookie(B), - else:
check: CLOSE
DiB == gen_digest(ChA, ICA)?
- if OK:
DONE
- else:
CLOSE
分配旗標
在分配交握初期,兩個參與的節點會交換能力旗標。這麼做是為了判斷這兩個節點之間的通訊應如何執行。兩個節點所呈現的功能的交集,定義了將使用的功能。定義了以下能力旗標
-define(DFLAG_PUBLISHED,16#1).
- 節點要發佈並成為全域命名空間的一部分。-define(DFLAG_ATOM_CACHE,16#2).
- 節點實作原子快取(已過時)。-define(DFLAG_EXTENDED_REFERENCES,16#4).
- 節點實作延伸 (3 × 32 位元) 參考。此旗標為必要項目。如果不存在,則會拒絕連線。-define(DFLAG_DIST_MONITOR,16#8).
- 節點實作分散式程序監控。-define(DFLAG_FUN_TAGS,16#10).
- 節點在分配協定中使用不同的函數(lambda)標籤。此旗標為必要項目。如果不存在,則會拒絕連線。-define(DFLAG_DIST_MONITOR_NAME,16#20).
- 節點實作分散式具名程序監控。-define(DFLAG_HIDDEN_ATOM_CACHE,16#40).
- (隱藏)節點實作原子快取(已過時)。-define(DFLAG_NEW_FUN_TAGS,16#80).
- 節點了解NEW_FUN_EXT
標籤。此旗標為必要項目。如果不存在,則會拒絕連線。-define(DFLAG_EXTENDED_PIDS_PORTS,16#100).
- 節點可以處理擴充的 pids 和 ports。此旗標為必要項目。如果不存在,則會拒絕連線。-define(DFLAG_EXPORT_PTR_TAG,16#200).
- 節點了解EXPORT_EXT
標籤。此旗標為必要項目。如果不存在,則會拒絕連線。-define(DFLAG_BIT_BINARIES,16#400).
- 節點了解BIT_BINARY_EXT
標籤。此旗標為必要項目。如果不存在,則會拒絕連線。-define(DFLAG_NEW_FLOATS,16#800).
- 節點了解NEW_FLOAT_EXT
標籤。此旗標為必要項目。如果不存在,則會拒絕連線。-define(DFLAG_UNICODE_IO,16#1000).
-define(DFLAG_DIST_HDR_ATOM_CACHE,16#2000).
- 節點在分配標頭中實作原子快取。-define(DFLAG_SMALL_ATOM_TAGS, 16#4000).
- 節點了解SMALL_ATOM_EXT
標籤。-define(DFLAG_UTF8_ATOMS, 16#10000).
- 節點了解使用ATOM_UTF8_EXT
和SMALL ATOM_UTF8_EXT
編碼的 UTF-8 原子。此旗標為必要項目。如果不存在,則會拒絕連線。-define(DFLAG_MAP_TAG, 16#20000).
- 節點了解對應標籤MAP_EXT
。此旗標為必要項目。如果不存在,則會拒絕連線。-define(DFLAG_BIG_CREATION, 16#40000).
- 節點理解大型節點建立標籤NEW_PID_EXT
、NEW_PORT_EXT
和NEWER_REFERENCE_EXT
。此旗標為必要。若不存在,連線將會被拒絕。-define(DFLAG_SEND_SENDER, 16#80000).
- 使用SEND_SENDER
控制訊息 取代SEND
控制訊息,並使用SEND_SENDER_TT
控制訊息取代SEND_TT
控制訊息。-define(DFLAG_BIG_SEQTRACE_LABELS, 16#100000).
- 節點將任何 term 理解為 seqtrace 標籤。-define(DFLAG_EXIT_PAYLOAD, 16#400000).
- 使用PAYLOAD_EXIT
、PAYLOAD_EXIT_TT
、PAYLOAD_EXIT2
、PAYLOAD_EXIT2_TT
和PAYLOAD_MONITOR_P_EXIT
控制訊息 取代非 PAYLOAD 的變體。-define(DFLAG_FRAGMENTS, 16#800000).
- 使用分段的分散式訊息來傳送大型訊息。-define(DFLAG_HANDSHAKE_23, 16#1000000).
- 節點支援在 OTP 23 中引入的新連線設定交握(版本 6)。此旗標為必要(從 OTP 25 開始)。若不存在,連線將會被拒絕。-define(DFLAG_UNLINK_ID, 16#2000000).
- 使用新的連結協定。注意
從 OTP 26 開始,此旗標為必要。
-define(DFLAG_MANDATORY_25_DIGEST, (1 bsl 36)).
- 節點支援在 OTP 25 中所有必要的功能。在 OTP 25 中引入。注意
此旗標將在 OTP 27 中成為必要。
-define(DFLAG_SPAWN, (1 bsl 32)).
- 如果支援SPAWN_REQUEST
、SPAWN_REQUEST_TT
、SPAWN_REPLY
、SPAWN_REPLY_TT
控制訊息,則設定此旗標。-define(DFLAG_NAME_ME, (1 bsl 33)).
- 動態節點名稱。這不是一種功能,而是由連線節點發出的請求,要求從接受節點接收其節點名稱作為交握的一部分。-define(DFLAG_V4_NC, (1 bsl 34)).
- 節點在 pid、port 和 reference 中接受更多數據(節點容器類型版本 4)。在 pid 的情況下,NEW_PID_EXT
中完整的 32 位元ID
和Serial
欄位;在 port 的情況下,V4_PORT_EXT
中為 64 位元整數;在 reference 的情況下,NEWER_REFERENCE_EXT
中現在接受最多 5 個 32 位元 ID 字組。此旗標在 OTP 24 中引入,並在 OTP 26 中成為必要。-define(DFLAG_ALIAS, (1 bsl 35)).
- 節點支援程序別名,因此可以處理ALIAS_SEND
和ALIAS_SEND_TT
控制訊息。在 OTP 24 中引入。
還有一個函式 dist_util:strict_order_flags/0
,會傳回所有對應於需要透過分散式通道嚴格排序資料的功能的旗標(以位元 OR 運算)。
已連線節點之間的協定
自 ERTS 5.7.2 (OTP R13B) 起,執行階段系統會在交握階段傳遞一個分散式旗標,該旗標允許在傳遞的所有訊息上使用分散式標頭。在這種情況下,節點之間傳遞的訊息具有以下格式
4 | d | n | m |
---|---|---|---|
長度 | DistributionHeader | ControlMessage | Message |
表格:節點之間傳遞的訊息格式(自 ERTS 5.7.2 (OTP R13B) 開始)
Length
- 等於 d + n + m。DistributionHeader
- 描述原子快取和分散式訊息分段的分散式標頭。ControlMessage
- 使用 Erlang 的外部格式傳遞的 tuple。Message
- 使用 '!' 發送到另一個節點的訊息,或使用外部 term 格式發出 EXIT、EXIT2 或 DOWN 信號的原因。
ERT 版本的節點早於 5.7.2 (OTP R13B),不會傳遞啟用分散式標頭的分散式旗標。在這種情況下,節點之間傳遞的訊息具有以下格式
4 | 1 | n | m |
---|---|---|---|
長度 | Type | ControlMessage | Message |
表格:節點之間傳遞的訊息格式(在 ERTS 5.7.2 (OTP R13B) 之前)
Length
- 等於 1 + n + m。Type
- 等於112
(直接傳遞)。ControlMessage
- 使用 Erlang 的外部格式傳遞的 tuple。Message
- 使用 '!'(外部格式)發送到另一個節點的訊息。請注意,Message
僅與編碼發送('!')的ControlMessage
組合傳遞。
ControlMessage
是一個 tuple,其中第一個元素指示它編碼哪個分散式操作
LINK
-{1, FromPid, ToPid}
此信號由
FromPid
發送,以便在FromPid
和ToPid
之間建立連結。SEND
-{2, Unused, ToPid}
後接
Message
。Unused
保留用於回溯相容性。EXIT
-{3, FromPid, ToPid, Reason}
當連結已中斷時,會發送此信號
UNLINK
(已過時) -{4, FromPid, ToPid}
警告
從 OTP 26 開始,此信號已過時且不受支援。如需更多資訊,請參閱連結協定的文件。
NODE_LINK
-{5}
REG_SEND
-{6, FromPid, Unused, ToName}
後接
Message
。Unused
保留用於回溯相容性。GROUP_LEADER
-{7, FromPid, ToPid}
EXIT2
-{8, FromPid, ToPid, Reason}
此信號由呼叫 erlang:exit/2 bif 發送
SEND_TT
-{12, Unused, ToPid, TraceToken}
後接
Message
。Unused
保留用於回溯相容性。EXIT_TT
-{13, FromPid, ToPid, TraceToken, Reason}
REG_SEND_TT
-{16, FromPid, Unused, ToName, TraceToken}
後接
Message
。Unused
保留用於回溯相容性。EXIT2_TT
-{18, FromPid, ToPid, TraceToken, Reason}
MONITOR_P
-{19, FromPid, ToProc, Ref}
,其中FromPid
= 監控程序,而ToProc
= 受監控的程序 pid 或名稱(atom)DEMONITOR_P
-{20, FromPid, ToProc, Ref}
,其中FromPid
= 監控程序,而ToProc
= 受監控的程序 pid 或名稱(atom)我們包含
FromPid
只是以防我們想要追蹤此情況。MONITOR_P_EXIT
-{21, FromProc, ToPid, Ref, Reason}
,其中FromProc
= 受監控的程序 pid 或名稱(atom)、ToPid
= 監控程序,而Reason
= 受監控程序的結束原因
Erlang/OTP 21 的新 Ctrlmessages
SEND_SENDER
-{22, FromPid, ToPid}
後接
Message
。此控制訊息取代
SEND
控制訊息,並且當在連線設定交握中協商了分散式旗標DFLAG_SEND_SENDER
時,將會發送此控制訊息。注意
在設定連線之前編碼的訊息可能仍會使用
SEND
控制訊息。然而,一旦發送了SEND_SENDER
或SEND_SENDER_TT
控制訊息,將不會在連線上的相同方向發送更多SEND
控制訊息。SEND_SENDER_TT
-{23, FromPid, ToPid, TraceToken}
後接
Message
。此控制訊息取代
SEND_TT
控制訊息,並且當在連線設定交握中協商了分散式旗標DFLAG_SEND_SENDER
時,將會發送此控制訊息。注意
在設定連線之前編碼的訊息可能仍會使用
SEND_TT
控制訊息。然而,一旦發送了SEND_SENDER
或SEND_SENDER_TT
控制訊息,將不會在連線上的相同方向發送更多SEND_TT
控制訊息。
Erlang/OTP 22 的新 Ctrlmessages
注意
在設定連線之前編碼的訊息可能仍會使用非 PAYLOAD 變體。然而,一旦發送了 PAYLOAD 控制訊息,將不會在連線上的相同方向發送更多非 PAYLOAD 控制訊息。
PAYLOAD_EXIT
-{24, FromPid, ToPid}
後接
Reason
。此控制訊息取代
EXIT
控制訊息,並且當在連線設定交握中協商了分散式旗標DFLAG_EXIT_PAYLOAD
時,將會發送此控制訊息。PAYLOAD_EXIT_TT
-{25, FromPid, ToPid, TraceToken}
後接
Reason
。此控制訊息取代
EXIT_TT
控制訊息,並且當在連線設定交握中協商了分散式旗標DFLAG_EXIT_PAYLOAD
時,將會發送此控制訊息。PAYLOAD_EXIT2
-{26, FromPid, ToPid}
後接
Reason
。此控制訊息取代
EXIT2
控制訊息,並且會在連線設定握手中協商了分佈旗標DFLAG_EXIT_PAYLOAD
時傳送。PAYLOAD_EXIT2_TT
-{27, FromPid, ToPid, TraceToken}
後接
Reason
。此控制訊息取代
EXIT2_TT
控制訊息,並且會在連線設定握手中協商了分佈旗標DFLAG_EXIT_PAYLOAD
時傳送。PAYLOAD_MONITOR_P_EXIT
-{28, FromProc, ToPid, Ref}
後接
Reason
。此控制訊息取代
MONITOR_P_EXIT
控制訊息,並且會在連線設定握手中協商了分佈旗標DFLAG_EXIT_PAYLOAD
時傳送。
Erlang/OTP 23 的新控制訊息
SPAWN_REQUEST
-{29, ReqId, From, GroupLeader, {Module, Function, Arity}, OptList}
接著是
ArgList
。此訊號由
spawn_request()
BIF 發送。ReqId :: reference()
- 請求識別碼。如果已傳遞monitor
選項,則也用作監控參考。From :: pid()
- 發出請求的進程的進程識別碼。也就是說,成為父進程。GroupLeader :: pid()
- 新建立的進程的群組領導者的進程識別碼。{Module :: atom(), Function :: atom(), Arity :: integer() >= 0}
- 新進程的進入點。OptList :: [term()]
- 產生時使用的產生選項的正確列表。ArgList :: [term()]
- 在調用進入點時使用的參數的正確列表。
僅在傳遞
DFLAG_SPAWN
分佈旗標時才支援。SPAWN_REQUEST_TT
-{30, ReqId, From, GroupLeader, {Module, Function, Arity}, OptList, Token}
接著是
ArgList
。與
SPAWN_REQUEST
相同,但還帶有循序追蹤Token
。僅在傳遞
DFLAG_SPAWN
分佈旗標時才支援。SPAWN_REPLY
-{31, ReqId, To, Flags, Result}
此訊號作為對先前傳送
SPAWN_REQUEST
訊號的進程的回覆而傳送。ReqId :: reference()
- 請求識別碼。如果已傳遞monitor
選項,則也用作監控參考。To :: pid()
- 發出產生請求的進程的進程識別碼。Flags :: integer() >= 0
- 位元旗標欄位,其中位元旗標以位元邏輯 OR 運算組合在一起。目前定義了以下旗標1
- 在Result
所在的節點上建立了To
和Result
之間的連結。2
- 在Result
所在的節點上建立了從To
到Result
的監控。
Result :: pid() | atom()
- 操作結果。如果Result
是進程識別碼,則操作成功,並且進程識別碼是新建立的進程的識別碼。如果Result
是原子,則操作失敗,且原子會識別失敗原因。
僅在傳遞
DFLAG_SPAWN
分佈旗標時才支援。SPAWN_REPLY_TT
-{32, ReqId, To, Flags, Result, Token}
與
SPAWN_REPLY
相同,但還帶有循序追蹤Token
。僅在傳遞
DFLAG_SPAWN
分佈旗標時才支援。UNLINK_ID
-{35, Id, FromPid, ToPid}
此訊號由
FromPid
發送,以移除FromPid
和ToPid
之間的連結。此取消連結訊號取代UNLINK
訊號。除了發送者和接收者的進程識別碼之外,UNLINK_ID
訊號還包含一個整數識別碼Id
。Id
的有效範圍是[1, (1 bsl 64) - 1]
。接收者應在UNLINK_ID_ACK
訊號中將Id
傳回給發送者。Id
必須在FromPid
到ToPid
的所有尚未確認的UNLINK_ID
訊號中唯一識別該UNLINK_ID
訊號。此訊號是 OTP 26 開始強制實施的新連結協定的一部分。
UNLINK_ID_ACK
-{36, Id, FromPid, ToPid}
取消連結確認訊號。此訊號是作為接收到
UNLINK_ID
訊號的確認而傳送。Id
元素應與UNLINK_ID
訊號中存在的Id
相同。FromPid
識別UNLINK_ID_ACK
訊號的發送者,而ToPid
識別UNLINK_ID
訊號的發送者。此訊號是 OTP 26 開始強制實施的新連結協定的一部分。
Erlang/OTP 24 的新控制訊息
ALIAS_SEND
-{33, FromPid, Alias}
後接
Message
。當將訊息
Message
傳送至由進程別名Alias
識別的進程時,會使用此控制訊息。可以處理此控制訊息的節點會在連線設定握手中設定分佈旗標DFLAG_ALIAS
。ALIAS_SEND_TT
-{34, FromPid, Alias, Token}
後接
Message
。與
ALIAS_SEND
相同,但還帶有循序追蹤Token
。
連結協定
在 OTP 23.3 中引入的新連結協定在 OTP 26 開始成為強制實施。從 OTP 26 開始,OTP 節點將因此拒絕連線到未指示它們使用 DFLAG_UNLINK_ID
分佈旗標支援新連結協定的節點。
新連結協定引入了兩個新訊號,UNLINK_ID
和 UNLINK_ID_ACK
,它們取代了舊的 UNLINK
訊號。仍然會傳送舊的 LINK
訊號以建立連結,但在接收時的處理方式不同。
為了建立連結,會將 LINK
訊號從啟動操作的進程傳送至它想要連結的進程。為了移除連結,會將 UNLINK_ID
訊號從啟動操作的進程傳送至連結的進程。UNLINK_ID
訊號的接收者會以 UNLINK_ID_ACK
訊號回應。收到 UNLINK_ID
訊號後,必須先傳送對應的 UNLINK_ID_ACK
訊號,然後才能將任何其他訊號傳送至 UNLINK_ID
訊號的發送者。結合 Erlang 的 訊號順序保證,這使得 UNLINK_ID
訊號的發送者可以知道其他訊號的順序,這對協定至關重要。UNLINK_ID_ACK
訊號應包含與正在確認的 UNLINK_ID
訊號中包含的 Id
相同的 Id
。
進程還需要維護有關連結的進程本機資訊。當傳送和接收以上訊號時,此進程本機資訊的狀態會變更。此進程本機資訊還會決定當進程呼叫 link/1
或 unlink/1
時是否應傳送訊號。只有當根據進程本機資訊,進程之間目前不存在活動連結時,才會傳送 LINK
訊號,並且只有當根據進程本機資訊,進程之間目前存在活動連結時,才會傳送 UNLINK_ID
訊號。
有關連結的進程本機資訊包含
Pid - 連結的進程的進程識別碼。
Active Flag - 如果設定,則連結處於活動狀態,且進程將會對由於連結而發出的傳入結束訊號做出反應。如果未設定,則連結處於非活動狀態,並且由於連結而發出的傳入結束訊號將會被忽略。也就是說,進程被視為未連結。
Unlink Id - 尚未完成的取消連結操作的識別碼。也就是說,尚未確認的取消連結操作。僅當未設定活動旗標時,才會使用此資訊。
僅當進程具有包含其他進程的進程識別碼且活動旗標已設定的連結的進程本機資訊時,進程才被視為連結到另一個進程。
有關連結的進程本機資訊會依以下方式更新
傳送
LINK
訊號 - 如果連結資訊尚不存在,則會建立該資訊。會設定活動旗標,且會清除取消連結 ID。也就是說,如果我們有尚未完成的取消連結操作,我們將忽略該操作的結果並啟用連結。收到
LINK
信號 - 如果鏈接資訊尚不存在,則會建立鏈接資訊,並設定 active 旗標,同時清除 unlink id。如果鏈接資訊已存在,則無論 active 旗標是否已設定,都會靜默忽略此信號。也就是說,如果我們有一個尚未完成的 unlink 操作,我們將不會啟用該鏈接。在此情況下,LINK
信號的發送者尚未發送與我們的UNLINK_ID
信號對應的UNLINK_ID_ACK
信號,這表示它會在發送其LINK
信號後收到我們的UNLINK_ID
信號。這反過來意味著,最終兩個處理程序都會同意它們之間沒有鏈接。發送
UNLINK_ID
信號 - 鏈接資訊已存在且 active 旗標已設定(否則不會發送信號)。active 旗標會被取消設定,且該信號的 unlink id 會儲存在鏈接資訊中。收到
UNLINK_ID
信號 - 如果 active 旗標已設定,則會移除鏈接的相關資訊。如果 active 旗標未設定(也就是說,我們有一個尚未完成的 unlink 操作),則鏈接的相關資訊將保持不變。發送
UNLINK_ID_ACK
信號 - 當收到UNLINK_ID
信號時會發送此信號,且不會進一步變更鏈接資訊。收到
UNLINK_ID_ACK
信號 - 如果鏈接資訊存在、active 旗標未設定,且鏈接資訊中的 unlink id 等於信號中的Id
,則會移除鏈接資訊;否則,會忽略該信號。
當處理程序因為鏈接而收到退出信號時,如果該鏈接處於 active 狀態,處理程序會先對退出信號做出反應,然後再移除該處理程序本機的鏈接資訊。
如果兩個節點之間的連線中斷,則會向所有透過該連線建立鏈接的處理程序發送包含退出原因 noconnection
的退出信號。這會導致移除該連線上所有鏈接的處理程序本機資訊。
在 Erlang 節點內部也完全使用相同的鏈接協定。但是,這些信號具有不同的格式,因為它們不必透過網路傳送。