檢視原始碼 分散式協定

此描述遠非完整。如果協定更新,將會更新此描述。然而,自 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 位元組的長度欄位開頭。因此,整體請求格式如下

2n
長度請求

表格:請求格式

在 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
1211222Nlen2Elen
120PortNoNodeTypeProtocolHighestVersionLowestVersionNlenNodeNameElenExtra

表格: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_RESPALIVE2_RESP,具體取決於分散式版本。如果節點和 EPMD 都支援分散式版本 6,則回應為 ALIVE2_X_RESP,否則為較舊的 ALIVE2_RESP

114
118結果建立

表格:具有 32 位元建立的 ALIVE2_X_RESP (118)

112
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
1N
122NodeName

表格:PORT_PLEASE2_REQ (122)

其中 N = Length - 1。

11
119結果

表格:PORT2_RESP (119) 回應指出錯誤,Result > 0

11211222Nlen2Elen
119結果PortNoNodeTypeProtocolHighestVersionLowestVersionNlenNodeNameElen>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
EPMDPortNoNodeInfo*

表格: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
EPMDPortNoNodeInfo*

表格: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
1n
115NodeName

表格: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 對於 Bout_cookie 對應於 B 對於 Ain_cookie,反之亦然。 A 對於 Bout_cookieA 對於 Bin_cookie *不一定* 相同。下面,函數 out_cookie(Node) 會傳回目前節點對於 Nodeout_cookie

in_cookie 是另一個節點與我們通訊時預期使用的 cookie,因此 A 對於 Bin_cookie 對應於 B 對於 Aout_cookie。下面,函數 in_cookie(Node) 會傳回目前節點對於 Nodein_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 會收到該訊息。該訊息可以有兩種不同的格式,如下所示(已移除封包標頭)

    124Nlen
    'n'Version=5FlagsName

    表格:舊版 send_name ('n'),適用於協定版本 5

    1842Nlen
    'N'Flags建立NlenName

    表格:協定版本 6 的新 send_name ('N')

    舊的 send_name 格式僅從未使用 epmd 的 OTP 23 和 24 節點傳送,因此不曉得遠端節點是否僅支援協定版本 5。Version 是一個 16 位元大端整數,且必須始終為值 5(即使節點 A 支援版本 6)。Flags 是節點 A能力旗標,以 32 位元大端格式表示。必須設定旗標位元 DFLAG_HANDSHAKE_23(因為節點 A 必須支援版本 6)。NameA 的完整節點名稱,為位元組字串(封包長度表示其長度)。

    新的 send_name 會傳送到已知支援版本 6 的節點。Flags 是節點 A能力旗標,以 64 位元大端格式表示。旗標位元 DFLAG_HANDSHAKE_23 必須始終設定。Creation 是節點 A 用來建立其 pids、ports 和 references 的節點化身識別符。NameA 的完整節點名稱,為位元組字串。Nlen 是節點名稱的位元組長度,以 16 位元大端格式表示。節點 Name 後面的任何額外資料都必須接受並忽略。

    當設定 DFLAG_NAME_ME 時,Name 必須只是主機名稱(不含 @)。

  • 3) recv_status/send_status - BA 傳送狀態訊息,指示是否允許連線。

    1Slen
    's'Status

    表格:狀態訊息的格式

    's' 是訊息標籤。Status 是狀態碼,以字串形式表示(非 Null 終止)。定義了以下狀態碼

    • ok - 交握將繼續。

    • ok_simultaneous - 交握將繼續,但會通知 AB 還有另一個進行中的連線嘗試將會關閉(同步連線,其中 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 用來產生完整的動態節點名稱。

      1Slen=62Nlen4
      's'Status='named:'NlenName建立

      表格:'named:' 狀態訊息的格式

      NameA 的完整動態節點名稱,為位元組字串。Nlen 是節點名稱的位元組長度,以 16 位元大端格式表示。Creation 是節點 A 的化身識別符,由節點 B 產生。節點 Creation 後面的任何額外資料都必須接受並忽略。

  • 3B) send_status/recv_status - 如果狀態為 alive,節點 A 會使用另一個狀態訊息回應,其中包含 true(表示連線將繼續(來自此節點的舊連線已中斷)),或 false(表示連線將關閉(連線嘗試是錯誤))。

  • 4) recv_challenge/send_challenge - 如果狀態為 okok_simultaneous,交握會繼續,B 會向 A 傳送另一個訊息,也就是挑戰。挑戰包含與 A 最初傳送到 B 的「名稱」訊息相同類型的資訊,加上一個 32 位元挑戰。

    18442Nlen
    'N'FlagsChallenge建立NlenName

    表格:新的挑戰訊息格式(版本 6)

    Challenge 是一個 32 位元大端整數。其他欄位則是節點 B 的旗標、建立和完整節點名稱,與 send_name 訊息類似。節點 Name 後面的任何額外資料都必須接受並忽略。

  • 4B) send_complement/recv_complement - 只有在節點 A 最初傳送舊名稱訊息時,才會從 AB 傳送補充訊息。其中包含節點 A 最初的舊名稱訊息中遺失的補充資訊。

    144
    'c'FlagsHigh建立

    表格:補充訊息

    FlagsHigh 是節點 A 的高能力旗標(位元 33-64),以 32 位元大端整數表示。Creation 是節點 A 的化身識別符。

  • 5) send_challenge_reply/recv_challenge_reply - 現在 A 已產生摘要及其自身的挑戰。這些內容會一起以封包形式傳送至 B

    1416
    'r'ChallengeDigest

    表格:challenge_reply 訊息

    ChallengeA 針對 B 要處理的挑戰。DigestAB 在上一個步驟中傳送的挑戰建構的 MD5 摘要。

  • 6) recv_challenge_ack/send_challenge_ack - B 會檢查從 A 收到的摘要是否正確,並根據從 A 收到的挑戰產生摘要。接著,摘要會傳送至 A。訊息如下

    116
    'a'Digest

    表格:challenge_ack 訊息

    DigestB 針對 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_EXTSMALL ATOM_UTF8_EXT 編碼的 UTF-8 原子。此旗標為必要項目。如果不存在,則會拒絕連線。

  • -define(DFLAG_MAP_TAG, 16#20000). - 節點了解對應標籤 MAP_EXT。此旗標為必要項目。如果不存在,則會拒絕連線。

  • -define(DFLAG_BIG_CREATION, 16#40000). - 節點理解大型節點建立標籤 NEW_PID_EXTNEW_PORT_EXTNEWER_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_EXITPAYLOAD_EXIT_TTPAYLOAD_EXIT2PAYLOAD_EXIT2_TTPAYLOAD_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_REQUESTSPAWN_REQUEST_TTSPAWN_REPLYSPAWN_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 位元 IDSerial 欄位;在 port 的情況下,V4_PORT_EXT 中為 64 位元整數;在 reference 的情況下,NEWER_REFERENCE_EXT 中現在接受最多 5 個 32 位元 ID 字組。此旗標在 OTP 24 中引入,並在 OTP 26 中成為必要。

  • -define(DFLAG_ALIAS, (1 bsl 35)). - 節點支援程序別名,因此可以處理 ALIAS_SENDALIAS_SEND_TT 控制訊息。在 OTP 24 中引入。

還有一個函式 dist_util:strict_order_flags/0,會傳回所有對應於需要透過分散式通道嚴格排序資料的功能的旗標(以位元 OR 運算)。

已連線節點之間的協定

自 ERTS 5.7.2 (OTP R13B) 起,執行階段系統會在交握階段傳遞一個分散式旗標,該旗標允許在傳遞的所有訊息上使用分散式標頭。在這種情況下,節點之間傳遞的訊息具有以下格式

4dnm
長度DistributionHeaderControlMessageMessage

表格:節點之間傳遞的訊息格式(自 ERTS 5.7.2 (OTP R13B) 開始)

請注意,從分散式標頭之後的 term 中省略了版本號碼

ERT 版本的節點早於 5.7.2 (OTP R13B),不會傳遞啟用分散式標頭的分散式旗標。在這種情況下,節點之間傳遞的訊息具有以下格式

41nm
長度TypeControlMessageMessage

表格:節點之間傳遞的訊息格式(在 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 發送,以便在 FromPidToPid 之間建立連結。

  • 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_SENDERSEND_SENDER_TT 控制訊息,將不會在連線上的相同方向發送更多 SEND 控制訊息。

  • SEND_SENDER_TT - {23, FromPid, ToPid, TraceToken}

    後接 Message

    此控制訊息取代 SEND_TT 控制訊息,並且當在連線設定交握中協商了分散式旗標 DFLAG_SEND_SENDER 時,將會發送此控制訊息。

    注意

    在設定連線之前編碼的訊息可能仍會使用 SEND_TT 控制訊息。然而,一旦發送了 SEND_SENDERSEND_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 所在的節點上建立了 ToResult 之間的連結。

      • 2 - 在 Result 所在的節點上建立了從 ToResult 的監控。

    • 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 發送,以移除 FromPidToPid 之間的連結。此取消連結訊號取代 UNLINK 訊號。除了發送者和接收者的進程識別碼之外,UNLINK_ID 訊號還包含一個整數識別碼 IdId 的有效範圍是 [1, (1 bsl 64) - 1]。接收者應在 UNLINK_ID_ACK 訊號中將 Id 傳回給發送者。Id 必須在 FromPidToPid 的所有尚未確認的 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_IDUNLINK_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/1unlink/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 節點內部也完全使用相同的鏈接協定。但是,這些信號具有不同的格式,因為它們不必透過網路傳送。