查看源代码 分布协议
这个描述远非完整。如果协议更新,将会进行更新。然而,从 Erlang 节点到 Erlang 端口映射守护进程 (EPMD) 以及 Erlang 节点之间的协议,多年来都是稳定的。
分布协议可以分为四个部分
- 低层套接字连接 (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 中注册自己。下面描述的消息 ALIVE2_REQ
从节点发送到 EPMD。来自 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)
结果 = 0 -> ok,结果 > 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) 指示错误的响应,结果 > 0
或
1 | 1 | 2 | 1 | 1 | 2 | 2 | 2 | Nlen | 2 | Elen |
---|---|---|---|---|---|---|---|---|---|---|
119 | 结果 | PortNo | NodeType | Protocol | HighestVersion | LowestVersion | Nlen | NodeName | Elen | >Extra |
表:PORT2_RESP,结果 = 0
如果 Result
> 0,则数据包仅由 [119, Result]
组成。
EPMD 在发送信息后关闭套接字。
从 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
表:协议版本 5 的旧 send_name ('n')
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
用来创建其 pid、端口和引用的节点化身标识符。Name
是A
的完整节点名称,以字节字符串的形式表示。Nlen
是节点名称的字节长度,为 16 位大端。节点Name
之后的任何额外数据都必须接受并忽略。当设置了
DFLAG_NAME_ME
时,Name
必须仅是主机名(不带@)。3)
recv_status
/send_status
-B
向A
发送状态消息,指示是否允许连接。1 Slen 's'
Status
表:状态消息的格式
's' 是消息标签。
Status
是状态代码,以字符串形式表示(非空终止)。定义了以下状态代码: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
的状态消息末尾提供。在send_name
中作为Name
发送的A
的主机名将由节点B
用来生成完整的动态节点名称。1 Slen=6 2 Nlen 4 's'
Status='named:'
Nlen
Name
创建
表:'named:' 状态消息的格式
Name
是A
的完整动态节点名称,以字节字符串的形式表示。Nlen
是节点名称的字节长度,为 16 位大端。Creation
是节点B
生成的节点A
的化身标识符。节点Creation
之后的任何额外数据都必须接受并忽略。
3B)
send_status
/recv_status
- 如果状态为alive
,则节点A
用另一个包含true
的状态消息回复,表示连接继续(来自此节点的旧连接已断开),或者包含false
,表示连接将被关闭(连接尝试是一个错误)。4)
recv_challenge
/send_challenge
- 如果状态为ok
或ok_simultaneous
,则握手继续,B
向A
发送另一个消息,即挑战。挑战包含与最初从A
发送到B
的“name”消息相同类型的信息,再加上一个 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).
- 该节点可以处理扩展的 pid 和端口。此标志是强制性的。如果不存在,则拒绝连接。-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).
- 节点理解任何项作为 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、端口和引用中接受更多数据(节点容器类型版本 4)。在 pid 的情况下,在NEW_PID_EXT
中有完整的 32 位ID
和Serial
字段,在端口的情况下,在V4_PORT_EXT
中有一个 64 位整数,在引用的情况下,现在在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
返回与需要在分发通道上严格排序数据的功能对应的所有标志(按位或运算在一起)。
连接节点之间的协议
自 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 的外部格式传递的元组。Message
- 使用外部项格式发送到另一个节点的消息,使用 '!' 或 EXIT、EXIT2 或 DOWN 信号的原因。
请注意,版本号从分发头之后的项中省略。
早于 5.7.2 (OTP R13B) 的 ERTS 版本的节点不传递启用分发头的分发标志。在这种情况下,节点之间传递的消息具有以下格式
4 | 1 | n | m |
---|---|---|---|
长度 | Type | ControlMessage | Message |
表:节点之间传递的消息格式(在 ERTS 5.7.2 (OTP R13B) 之前)
Length
- 等于 1 + n + m。Type
- 等于112
(直通)。ControlMessage
- 使用 Erlang 的外部格式传递的元组。Message
- 使用 '!' (外部格式)发送到另一个节点的消息。请注意,Message
仅与编码发送 ('!') 的ControlMessage
组合传递。
ControlMessage
是一个元组,其中第一个元素指示它编码的分布式操作
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 或名称(原子)DEMONITOR_P
-{20, FromPid, ToProc, Ref}
,其中FromPid
= 监视进程,ToProc
= 被监视的进程 pid 或名称(原子)我们包含
FromPid
,以防我们想要跟踪它。MONITOR_P_EXIT
-{21, FromProc, ToPid, Ref, Reason}
,其中FromProc
= 被监视的进程 pid 或名称(原子),ToPid
= 监视进程,Reason
= 被监视进程的退出原因
Erlang/OTP 21 的新控制消息
SEND_SENDER
-{22, FromPid, ToPid}
后跟
Message
。当在连接建立握手中协商了分发标志
DFLAG_SEND_SENDER
时,此控制消息将替换SEND
控制消息并发送。注意
在建立连接之前编码的消息可能仍使用
SEND
控制消息。但是,一旦发送了SEND_SENDER
或SEND_SENDER_TT
控制消息,将不再在连接上的同一方向发送更多SEND
控制消息。SEND_SENDER_TT
-{23, FromPid, ToPid, TraceToken}
后跟
Message
。当在连接建立握手中协商了分发标志
DFLAG_SEND_SENDER
时,此控制消息将替换SEND_TT
控制消息并发送。注意
在建立连接之前编码的消息可能仍使用
SEND_TT
控制消息。但是,一旦发送了SEND_SENDER
或SEND_SENDER_TT
控制消息,将不再在连接上的同一方向发送更多SEND_TT
控制消息。
Erlang/OTP 22 的新控制消息
注意
在建立连接之前编码的消息可能仍使用非 PAYLOAD 变体。但是,一旦发送了 PAYLOAD 控制消息,将不再在连接上的同一方向发送更多非 PAYLOAD 控制消息。
PAYLOAD_EXIT
-{24, FromPid, ToPid}
后跟
Reason
。当在连接建立握手中协商了分发标志
DFLAG_EXIT_PAYLOAD
时,此控制消息将替换EXIT
控制消息并发送。PAYLOAD_EXIT_TT
-{25, FromPid, ToPid, TraceToken}
后跟
Reason
。当在连接建立握手中协商了分发标志
DFLAG_EXIT_PAYLOAD
时,此控制消息将替换EXIT_TT
控制消息并发送。PAYLOAD_EXIT2
-{26, FromPid, ToPid}
后跟
Reason
。当在连接建立握手中协商了分发标志
DFLAG_EXIT_PAYLOAD
时,此控制消息将替换EXIT2
控制消息并发送。PAYLOAD_EXIT2_TT
-{27, FromPid, ToPid, TraceToken}
后跟
Reason
。当在连接建立握手中协商了分发标志
DFLAG_EXIT_PAYLOAD
时,此控制消息将替换EXIT2_TT
控制消息并发送。PAYLOAD_MONITOR_P_EXIT
-{28, FromProc, ToPid, Ref}
后跟
Reason
。当在连接建立握手中协商了分发标志
DFLAG_EXIT_PAYLOAD
时,此控制消息将替换MONITOR_P_EXIT
控制消息并发送。
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
- 按位或运算组合的位标志字段。目前定义了以下标志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]
。Id
将由接收方在UNLINK_ID_ACK
信号中传递回发送方。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 的新 Ctrlmessage
ALIAS_SEND
-{33, FromPid, Alias}
后跟
Message
。当向由进程别名
Alias
标识的进程发送消息Message
时,将使用此控制消息。 可以处理此控制消息的节点在连接设置握手中设置分发标志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 - 链接进程的进程标识符。
活动标志 - 如果设置,则链接处于活动状态,并且进程将对由于链接而发出的传入的退出信号做出反应。 如果未设置,则链接处于非活动状态,并且由于链接而发出的传入退出信号将被忽略。 也就是说,这些进程被认为未链接。
取消链接 Id - 未完成的取消链接操作的标识符。 也就是说,尚未确认的取消链接操作。 此信息仅在未设置活动标志时使用。
只有当进程具有包含其他进程的进程标识符且活动标志设置为的有关链接的进程本地信息时,才认为该进程已链接到另一个进程。
有关链接的进程本地信息更新如下
发送
LINK
信号 - 如果链接信息尚不存在,则创建链接信息。 设置活动标志,并清除取消链接 id。 也就是说,如果我们有一个未完成的取消链接操作,我们将忽略该操作的结果并启用链接。接收到
LINK
信号 - 如果不存在链接信息,则创建它,设置活动标志并清除取消链接 id。 如果链接信息已经存在,则会静默忽略该信号,无论是否设置了活动标志。 也就是说,如果我们有一个未完成的取消链接操作,我们将不会激活该链接。 在这种情况下,LINK
信号的发送者尚未发送与我们的UNLINK_ID
信号对应的UNLINK_ID_ACK
信号,这意味着它将在发送其LINK
信号后收到我们的UNLINK_ID
信号。 这反过来意味着,最终,两个进程都将同意它们之间没有链接。发送
UNLINK_ID
信号 - 链接信息已存在且已设置活动标志(否则不会发送该信号)。 取消设置活动标志,并将信号的取消链接 id 保存在链接信息中。收到
UNLINK_ID
信号 - 如果设置了活动标志,则会删除有关链接的信息。 如果未设置活动标志(即,我们有一个未完成的取消链接操作),则有关链接的信息保持不变。发送
UNLINK_ID_ACK
信号 - 这是在接收到UNLINK_ID
信号时完成的,不会导致链接信息的进一步更改。收到
UNLINK_ID_ACK
信号 - 如果存在有关链接的信息,则未设置活动标志,并且链接信息中的取消链接 id 等于信号中的Id
,则删除链接信息;否则,忽略该信号。
当进程由于链接而接收到退出信号时,如果链接处于活动状态,该进程将首先对退出信号做出反应,然后删除有关链接的进程本地信息。
如果两个节点之间的连接丢失,则会向所有通过该连接具有链接的进程发送带有退出原因 noconnection
的退出信号。 这将导致删除有关通过该连接的链接的所有进程本地信息。
完全相同的链接协议也在 Erlang 节点内部使用。 但是,这些信号具有不同的格式,因为它们不必通过网络发送。