查看源代码 分布协议

这个描述远非完整。如果协议更新,将会进行更新。然而,从 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 字节的长度字段。因此,整体请求格式如下所示

2n
长度请求

表:请求格式

在 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
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)

结果 = 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
1N
122NodeName

表:PORT_PLEASE2_REQ (122)

其中 N = Length - 1。

11
119结果

表:PORT2_RESP (119) 指示错误的响应,结果 > 0

11211222Nlen2Elen
119结果PortNoNodeTypeProtocolHighestVersionLowestVersionNlenNodeNameElen>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
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,因此 ABout_cookie 对应于 BAin_cookie,反之亦然。ABout_cookieABin_cookie *不必* 相同。下面,函数 out_cookie(Node) 返回当前节点对 Nodeout_cookie

in_cookie 是另一个节点在与我们通信时预期使用的 cookie,因此 ABin_cookie 对应于 BAout_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 - AB 发送初始标识,B 接收该消息。该消息可以有两种不同的格式,如下所示(数据包头已删除):

    124Nlen
    'n'Version=5FlagsName

    表:协议版本 5 的旧 send_name ('n')

    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 用来创建其 pid、端口和引用的节点化身标识符。NameA 的完整节点名称,以字节字符串的形式表示。Nlen 是节点名称的字节长度,为 16 位大端。节点 Name 之后的任何额外数据都必须接受并忽略。

    当设置了 DFLAG_NAME_ME 时,Name 必须仅是主机名(不带@)。

  • 3) recv_status/send_status - BA 发送状态消息,指示是否允许连接。

    1Slen
    's'Status

    表:状态消息的格式

    's' 是消息标签。Status 是状态代码,以字符串形式表示(非空终止)。定义了以下状态代码:

    • ok - 握手将继续。

    • ok_simultaneous - 握手将继续,但是通知 AB 有另一个正在进行的连接尝试将被关闭(同时连接,其中 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 用来生成完整的动态节点名称。

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

      表:'named:' 状态消息的格式

      NameA 的完整动态节点名称,以字节字符串的形式表示。Nlen 是节点名称的字节长度,为 16 位大端。Creation 是节点 B 生成的节点 A 的化身标识符。节点 Creation 之后的任何额外数据都必须接受并忽略。

  • 3B) send_status/recv_status - 如果状态为 alive,则节点 A 用另一个包含 true 的状态消息回复,表示连接继续(来自此节点的旧连接已断开),或者包含 false,表示连接将被关闭(连接尝试是一个错误)。

  • 4) recv_challenge/send_challenge - 如果状态为 okok_simultaneous,则握手继续,BA 发送另一个消息,即挑战。挑战包含与最初从 A 发送到 B 的“name”消息相同类型的信息,再加上一个 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 处理的挑战。DigestA 根据 B 在上一步中发送的挑战构建的 MD5 摘要。

  • 6) recv_challenge_ack/send_challenge_ack - B 检查从 A 接收的摘要是否正确,并根据从 A 接收的挑战生成摘要。然后将摘要发送给 A。消息如下:

    116
    'a'Digest

    表:challenge_ack 消息

    DigestBA 的挑战计算的摘要。

  • 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_EXTSMALL ATOM_UTF8_EXT 编码的 UTF-8 原子。此标志是强制性的。如果不存在,则拒绝连接。

  • -define(DFLAG_MAP_TAG, 16#20000). - 该节点理解映射标签 MAP_EXT。此标志是强制性的。如果不存在,则拒绝连接。

  • -define(DFLAG_BIG_CREATION, 16#40000). - 节点理解大型节点创建标签 NEW_PID_EXT, NEW_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). - 节点理解任何项作为 seqtrace 标签。

  • -define(DFLAG_EXIT_PAYLOAD, 16#400000). - 使用 PAYLOAD_EXIT, PAYLOAD_EXIT_TT, PAYLOAD_EXIT2, PAYLOAD_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_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 位 IDSerial 字段,在端口的情况下,在 V4_PORT_EXT 中有一个 64 位整数,在引用的情况下,现在在 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 返回与需要在分发通道上严格排序数据的功能对应的所有标志(按位或运算在一起)。

连接节点之间的协议

自 ERTS 5.7.2 (OTP R13B) 起,运行时系统在握手阶段传递一个分发标志,该标志允许在传递的所有消息上使用分发头。在这种情况下,节点之间传递的消息具有以下格式

4dnm
长度DistributionHeaderControlMessageMessage

表:节点之间传递的消息格式(从 ERTS 5.7.2 (OTP R13B) 起)

  • Length - 等于 d + n + m。

  • DistributionHeader - 分发头,描述原子缓存和分片分发消息。

  • ControlMessage - 使用 Erlang 的外部格式传递的元组。

  • Message - 使用外部项格式发送到另一个节点的消息,使用 '!' 或 EXIT、EXIT2 或 DOWN 信号的原因。

请注意,版本号从分发头之后的项中省略

早于 5.7.2 (OTP R13B) 的 ERTS 版本的节点不传递启用分发头的分发标志。在这种情况下,节点之间传递的消息具有以下格式

41nm
长度TypeControlMessageMessage

表:节点之间传递的消息格式(在 ERTS 5.7.2 (OTP R13B) 之前)

  • Length - 等于 1 + n + m。

  • Type - 等于 112(直通)。

  • ControlMessage - 使用 Erlang 的外部格式传递的元组。

  • Message - 使用 '!' (外部格式)发送到另一个节点的消息。请注意,Message 仅与编码发送 ('!') 的 ControlMessage 组合传递。

ControlMessage 是一个元组,其中第一个元素指示它编码的分布式操作

  • 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 或名称(原子)

  • 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_SENDERSEND_SENDER_TT 控制消息,将不再在连接上的同一方向发送更多 SEND 控制消息。

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

    后跟 Message

    当在连接建立握手中协商了分发标志 DFLAG_SEND_SENDER 时,此控制消息将替换 SEND_TT 控制消息并发送。

    注意

    在建立连接之前编码的消息可能仍使用 SEND_TT 控制消息。但是,一旦发送了 SEND_SENDERSEND_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 所在的节点上,在 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]Id 将由接收方在 UNLINK_ID_ACK 信号中传递回发送方。 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 的新 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_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 - 链接进程的进程标识符。

  • 活动标志 - 如果设置,则链接处于活动状态,并且进程将对由于链接而发出的传入的退出信号做出反应。 如果未设置,则链接处于非活动状态,并且由于链接而发出的传入退出信号将被忽略。 也就是说,这些进程被认为链接。

  • 取消链接 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 节点内部使用。 但是,这些信号具有不同的格式,因为它们不必通过网络发送。