檢視原始碼 外部項格式

簡介

外部項格式主要用於 Erlang 的分散式機制。

由於 Erlang 具有固定數量的類型,因此程式設計師無需為某些應用程式中使用的外部格式定義規格。所有 Erlang 項都有外部表示形式,不同項的解釋取決於應用程式。

在 Erlang 中,BIF erlang:term_to_binary/1,2 用於將項轉換為外部格式。要將二進制資料編碼轉換為項,則使用 BIF erlang:binary_to_term/1

當跨節點邊界傳送訊息時,分散式機制會隱式地執行此操作。

項格式的整體格式如下

11N
131標籤資料

注意

當訊息在已連線節點之間傳遞並且使用分散式標頭時,包含版本號 (131) 的第一個位元組會從分散式標頭之後的項中省略。這是因為版本號由分散式標頭中的版本號隱含。

壓縮項格式如下

114N
13180未壓縮大小Zlib 壓縮資料

未壓縮大小(大端位元組順序的 32 位元無符號整數)是壓縮前資料的大小。壓縮資料在展開後具有以下格式

1未壓縮大小
標籤資料

編碼原子

從 ERTS 9.0 (OTP 20) 開始,原子可以包含任何 Unicode 字元。

透過節點分散式傳送的原子始終使用 UTF-8 編碼,使用 ATOM_UTF8_EXTSMALL_ATOM_UTF8_EXTATOM_CACHE_REF

使用 erlang:term_to_binary/1,2erlang:term_to_iovec/1,2 編碼的原子預設仍然對僅包含 Latin-1 字元(Unicode 代碼點 0-255)的原子使用舊的已棄用的 Latin-1 格式 ATOM_EXT。代碼點較高的原子將使用 UTF-8 編碼,使用 ATOM_UTF8_EXTSMALL_ATOM_UTF8_EXT

原子中允許的最大字元數為 255。在 UTF-8 的情況下,每個字元可能需要 4 個位元組來編碼。

分散式標頭

分散式標頭由 erlang 分散式傳送,以攜帶有關即將到來的控制訊息和潛在負載的中繼資料。它主要用於處理 Erlang 分散式中的原子快取。自 OTP-22 以來,它還用於將大型分散式訊息分段為多個較小的片段。有關分散式如何使用分散式標頭的更多資訊,請參閱已連線節點之間協定的文件,位於分散式協定文件中。

任何 ATOM_CACHE_REF 條目,其對應的 AtomCacheReferenceIndex 在分散式標頭之後的外部格式編碼的項中,都參考分散式標頭中建立的原子快取參考。範圍是 0 <= AtomCacheReferenceIndex < 255,也就是說,最多可以從以下項建立 255 個不同的原子快取參考。

正常分散式標頭

非分段分散式標頭格式如下

111NumberOfAtomCacheRefs/2+1 | 0N | 0
13168NumberOfAtomCacheRefs旗標AtomCacheRefs

FlagsNumberOfAtomCacheRefs/2+1 個位元組組成,除非 NumberOfAtomCacheRefs0。如果 NumberOfAtomCacheRefs0,則會省略 FlagsAtomCacheRefs。每個原子快取參考都有一個半位元組旗標欄位。對應於特定 AtomCacheReferenceIndex 的旗標位於旗標位元組編號 AtomCacheReferenceIndex/2 中。旗標位元組 0 是 NumberOfAtomCacheRefs 位元組之後的第一個位元組。偶數 AtomCacheReferenceIndex 的旗標位於最低有效半位元組中,奇數 AtomCacheReferenceIndex 的旗標位於最高有效半位元組中。

原子快取參考的旗標欄位具有以下格式

1 位元3 位元
NewCacheEntryFlagSegmentIndex

最高有效位元是 NewCacheEntryFlag。如果設定,則對應的快取參考是新的。三個最低有效位元是對應原子快取條目的 SegmentIndex。原子快取由 8 個區段組成,每個區段大小為 256,也就是說,原子快取可以包含 2048 個條目。

另一個半位元組旗標欄位與原子快取參考的旗標欄位一起存在。當 NumberOfAtomCacheRefs 為偶數時,此半位元組是原子快取參考之後的位元組的最低有效半位元組。當 NumberOfAtomCacheRefs 為奇數時,此半位元組是原子快取參考的最後一個位元組的最高有效半位元組(在有線狀態下,它會出現在最後一個快取參考之前)。它具有以下格式

3 位元1 位元
目前未使用LongAtoms

該半位元組中的最低有效位元是旗標 LongAtoms。如果設定,則在分散式標頭中使用 2 個位元組表示原子長度,而不是 1 個位元組。

Flags 欄位之後是 AtomCacheRefs。第一個 AtomCacheRef 是對應於 AtomCacheReferenceIndex 0 的那個。較高的索引按順序跟隨,直到索引 NumberOfAtomCacheRefs - 1

如果已設定下一個 AtomCacheRefNewCacheEntryFlag,則接下來會出現格式如下的 NewAtomCacheRef

11 | 2長度
InternalSegmentIndex長度AtomText

InternalSegmentIndexSegmentIndex 一起完全識別原子快取中原子快取條目的位置。LengthAtomText 組成的位元組數。如果已設定旗標 LongAtoms,則長度是 2 位元組大端整數,否則是 1 位元組整數。當DFLAG_UTF8_ATOMS 分散式旗標在分散式握手中在兩個節點之間交換時,AtomText 中的字元會以 UTF-8 編碼,否則以 Latin-1 編碼。與此 NewAtomCacheRef 具有相同 SegmentIndexInternalSegmentIndex 的以下 CachedAtomRef 會參考此原子,直到出現具有相同 SegmentIndexInternalSegmentIndex 的新 NewAtomCacheRef

有關原子編碼的更多資訊,請參閱上面關於 UTF-8 編碼原子的章節

如果尚未設定下一個 AtomCacheRefNewCacheEntryFlag,則接下來會出現格式如下的 CachedAtomRef

1
InternalSegmentIndex

InternalSegmentIndexSegmentIndex 一起識別原子快取中原子快取條目的位置。對應於此 CachedAtomRef 的原子是另一個先前傳遞的分散式標頭中此 CachedAtomRef 之前的最新 NewAtomCacheRef

分段訊息的分散式標頭

Erlang 節點之間傳送的訊息有時可能非常大。自 OTP-22 以來,可以將大型訊息分割成較小的片段,以便在大型訊息之間交錯較小的訊息。只有每個分散式訊息message 部分可以使用分段分割。因此,建議使用 OTP-22 中引入的PAYLOAD 控制訊息

僅當接收節點透過 DFLAG_FRAGMENTS 分散式旗標發出支援時,才會使用分段分散式訊息。

程序必須完成傳送分段訊息,然後才能開始在同一個分散式通道上傳送任何其他訊息。

分段訊息序列的開始如下所示

11881NumberOfAtomCacheRefs/2+1 | 0N | 0
13169SequenceIdFragmentIdNumberOfAtomCacheRefs旗標AtomCacheRefs

分段訊息序列的繼續如下所示

1188
13170SequenceIdFragmentId

起始分散式標頭與非分段分散式標頭非常相似。原子快取的工作方式與正常分散式標頭相同,並且對於整個序列是相同的。新增的其他欄位是序列 ID 和片段 ID。

  • 序列 ID - 序列 ID 用於唯一識別從同一個分散式連線上的一個程序傳送到另一個程序的分段訊息。這用於識別片段屬於哪個序列,因為同一個程序可以同時接收多個序列。

    由於一個程序一次只能傳送一個分段訊息,因此使用本機 PID 作為序列 ID 可能很方便。

  • 片段 ID - 片段 ID 用於為序列中的片段編號。ID 從片段總數開始,然後遞減至 1(這是最後一個片段)。因此,如果序列由 3 個片段組成,則起始標頭中的片段 ID 將為 3,然後傳送片段 2 和 1。

    必須按正確的順序傳遞片段,因此如果使用無序分散式載體,則必須在傳遞到 Erlang 執行時間之前將它們排序。

範例

舉例來說,假設我們想要使用 128 的片段大小將 {call, <0.245.2>, {set_get_state, <<0:1024>>}} 傳送到已註冊的程序 reg。若要傳送此訊息,我們需要一個分散式標頭、原子快取更新、控制訊息(在此情況下將為 {6, <0.245.2>, [], reg}),以及最後的實際訊息。這些都將編碼為

131,69,0,0,2,168,0,0,5,83,0,0,0,0,0,0,0,2,               %% Header with seq and frag id
5,4,137,9,10,5,236,3,114,101,103,9,4,99,97,108,108,      %% Atom cache updates
238,13,115,101,116,95,103,101,116,95,115,116,97,116,101,
104,4,97,6,103,82,0,0,0,0,85,0,0,0,0,2,82,1,82,2,        %% Control message
104,3,82,3,103,82,0,0,0,0,245,0,0,0,2,2,                 %% Actual message using cached atoms
104,2,82,4,109,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

131,70,0,0,2,168,0,0,5,83,0,0,0,0,0,0,0,1,               %% Cont Header with seq and frag id
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,               %% Rest of payload
0,0,0,0

讓我們將其分解為各個組成部分。首先,我們有分配標頭標籤,以及序列 ID 和片段 ID 2。

131,69,                   %% Start fragment header
0,0,2,168,0,0,5,83,       %% The sequence ID
0,0,0,0,0,0,0,2,           %% The fragment ID

接著是原子快取的更新。

5,4,137,9,  %% 5 atoms and their flags
10,5,       %% The already cached atom ids
236,3,114,101,103,  %% The atom 'reg'
9,4,99,97,108,108,  %% The atom 'call'
238,13,115,101,116,95,103,101,116,95,115,116,97,116,101, %% The atom 'set_get_state'

第一個位元組表示我們有 5 個原子是快取的一部分。然後是三個位元組,它們是原子快取參照標誌。每個標誌使用 4 位元,因此以十進制位元組形式讀取有點困難。以二進制半位元組形式,它們看起來像這樣:

0000, 0100, 1000, 1001, 1001

由於快取中前兩個原子的高位未設定,我們知道它們已在快取中,因此不必再次發送(這是接收和發送節點的節點名稱)。然後是必須發送的原子,以及它們的區段 ID。

接著是原子的列表,從 10 和 5 開始,它們是已快取原子的原子參照。然後發送新的原子。

當原子快取正確設定後,會發送控制訊息。

104,4,97,6,103,82,0,0,0,0,85,0,0,0,0,2,82,1,82,2,

請注意,到目前為止,不允許將訊息分段。整個原子快取和控制訊息必須是起始片段的一部分。控制訊息之後,會使用 128 個位元組發送訊息的有效負載。

104,3,82,3,103,82,0,0,0,0,245,0,0,0,2,2,
104,2,82,4,109,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

由於有效負載大於 128 位元組,因此將其分割為兩個片段。第二個片段沒有任何原子快取更新指令,因此簡單得多。

131,70,0,0,2,168,0,0,5,83,0,0,0,0,0,0,0,1, %% Continuation dist header 70 with seq and frag id
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, %% remaining payload
0,0,0,0

注意

128 的片段大小僅用作範例。發送分段訊息時可以使用任何片段大小。

ATOM_CACHE_REF

11
82AtomCacheReferenceIndex

參照分配標頭中具有 AtomCacheReferenceIndex 的原子。

SMALL_INTEGER_EXT

11
97Int

無符號 8 位元整數。

INTEGER_EXT

14
98Int

大端格式的有符號 32 位元整數。

FLOAT_EXT

131
99浮點數字串

有限的浮點數(即,非 inf、-inf 或 NaN)以字串格式儲存。sprintf 中用於格式化浮點數的格式為 "%.20e"(分配的位元組多於所需)。要解壓縮浮點數,請使用 sscanf,格式為 "%lf"。

此術語用於外部格式的次要版本 0 中;它已被 NEW_FLOAT_EXT 取代。

PORT_EXT

1N41
102節點ID建立

NEW_PORT_EXT 相同,但 Creation 欄位只有一個位元組,且只有兩個位元有意義,其餘位元應為 0。

NEW_PORT_EXT

1N44
89節點ID建立

V4_PORT_EXT 相同,但 ID 欄位只有四個位元組。只有 28 位元有意義;其餘位元應為 0。

NEW_PORT_EXT 在 OTP 19 中引入,但僅用於解碼和回顯。不為本地埠編碼。

在 OTP 23 中,分配標誌 DFLAG_BIG_CREATION 成為強制性。現在所有埠都使用 NEW_PORT_EXT 進行編碼,即使是從較舊節點作為 PORT_EXT 接收的外部埠也是如此。

V4_PORT_EXT

1N84
120節點ID建立

編碼埠識別符(從 erlang:open_port/2 取得)。Node 是來源節點,編碼為原子ID 是一個 64 位元的大端無符號整數。Creation 的運作方式與 NEW_PID_EXT 中的方式相同。不允許跨越節點邊界的埠操作。

在 OTP 26 中,分配標誌 DFLAG_V4_NC 以及 V4_PORT_EXT 成為強制性,接受完整的 64 位元埠進行解碼和回顯。

PID_EXT

1N441
103節點ID序號建立

NEW_PID_EXT 相同,但 Creation 欄位只有一個位元組,且只有兩個位元有意義,其餘位元應為 0。

NEW_PID_EXT

1N444
88節點ID序號建立

編碼 Erlang 程序識別符物件。

  • Node - 來源節點的名稱,編碼為原子

  • ID - 一個 32 位元的大端無符號整數。

  • Serial - 一個 32 位元的大端無符號整數。

  • Creation - 一個 32 位元的大端無符號整數。來自同一節點化身的所有識別符都必須具有相同的 Creation 值。這使得可以區分來自舊(崩潰)節點和新節點的識別符。數值零已保留,必須避免用於正常操作。

NEW_PID_EXT 在 OTP 19 中引入,但僅用於解碼和回顯。不為本地程序編碼。

在 OTP 23 中,分配標誌 DFLAG_BIG_CREATION 成為強制性。現在所有 pid 都使用 NEW_PID_EXT 進行編碼,即使是從較舊節點作為 PID_EXT 接收的外部 pid 也是如此。

在 OTP 26 中,分配標誌 DFLAG_V4_NC 成為強制性,接受完整的 64 位元 pid 進行解碼和回顯。

SMALL_TUPLE_EXT

11N
104Arity元素

編碼元組。Arity 欄位是一個無符號位元組,用於確定 Elements 區段中後續的元素數量。

LARGE_TUPLE_EXT

14N
105Arity元素

SMALL_TUPLE_EXT 相同,但 Arity 是一個大端格式的無符號 4 位元組整數。

MAP_EXT

14N
116Arity配對

編碼映射。Arity 欄位是一個大端格式的無符號 4 位元組整數,用於確定映射中鍵值對的數量。鍵值對 (Ki => Vi) 按以下順序在 Pairs 區段中編碼:K1, V1, K2, V2,..., Kn, Vn。不允許在同一映射中出現重複的鍵。

Erlang/OTP 17.0 起

NIL_EXT

1
106

空列表的表示形式,即 Erlang 語法 []

STRING_EXT

12Len
107長度字元

字串沒有對應的 Erlang 表示形式,但它是用於透過分配更有效率地發送位元組列表(範圍 0-255 中的整數)的一種最佳化。由於欄位 Length 是一個無符號的 2 位元組整數(大端),實作必須確保長度超過 65535 個元素的列表編碼為 LIST_EXT

LIST_EXT

14
108長度元素尾部

LengthElements 區段中後續的元素數量。Tail 是列表的最終尾部;對於正常的列表,它是 NIL_EXT,但如果列表不正常(例如,[a|b]),則可以是任何類型。

BINARY_EXT

14Len
109Len資料

二進位檔案是使用位元語法表達式或使用 erlang:list_to_binary/1erlang:term_to_binary/1 或從二進位埠輸入產生的。Len 長度欄位是一個無符號的 4 位元組整數(大端)。

SMALL_BIG_EXT

111n
110n符號d(0) ... d(n-1)

大數以一元形式儲存,並帶有 Sign 位元組,即,如果大數為正數則為 0,如果為負數則為 1。數字以最低有效位元組先儲存。若要計算整數,可以使用以下公式

B = 256
(d0*B^0 + d1*B^1 + d2*B^2 + ... d(N-1)*B^(n-1))

LARGE_BIG_EXT

141n
111n符號d(0) ... d(n-1)

SMALL_BIG_EXT 相同,但長度欄位是一個無符號的 4 位元組整數。

REFERENCE_EXT(已棄用)

1N41
101節點ID建立

NEW_REFERENCE_EXT 相同,但 ID 只有一個字組 (Len = 1)。

NEW_REFERENCE_EXT

12N1N'
114Len節點建立ID ...

NEWER_REFERENCE_EXT 相同,除了

  • ID - 在 ID 的第一個字組(4 位元組)中,只有 18 位元有意義,其餘位元必須為 0。

  • Creation - 只有一個位元組長,且只有兩個位元有意義,其餘位元必須為 0。

NEWER_REFERENCE_EXT

12N4N'
90Len節點建立ID ...

編碼使用 erlang:make_ref/0 產生的參照術語。

  • Node - 來源節點的名稱,編碼為原子

  • Len - 一個不超過 5 的 16 位元大端無符號整數。

  • ID - 一個 Len 個大端無符號整數的序列(每個 4 位元組,因此 N' = 4 * Len),但應視為未解釋的資料。

  • Creation - 其運作方式與 NEW_PID_EXT 中的方式相同。

NEWER_REFERENCE_EXT 在 OTP 19 中引入,但僅用於解碼和回顯。不為本地參照編碼。

在 OTP 23 中,分配標誌 DFLAG_BIG_CREATION 成為強制性。現在所有參照都使用 NEWER_REFERENCE_EXT 進行編碼,即使是從較舊節點作為 NEW_REFERENCE_EXT 接收的外部參照也是如此。

在 OTP 26 發行版本中,旗標 DFLAG_V4_NC 成為強制性的。參考現在最多可以包含 5 個 ID 字組。

FUN_EXT (已移除)

14N1N2N3N4N5
117NumFreePidModuleIndexUniqFree vars ...

自 OTP R8 以來未發射,自 OTP 23 以來未解碼。

NEW_FUN_EXT

1411644N1N2N3N4N5
112SizeArityUniqIndexNumFreeModuleOldIndexOldUniqPidFree Vars

這是內部函數的編碼:fun F/Afun(Arg1,..) -> ... end

  • Size - 總位元組數,包括欄位 Size

  • Arity - 實作 fun 的函數的 arity。

  • Uniq - Beam 檔案重要部分的 16 位元組 MD5。

  • Index - 索引編號。模組內的每個 fun 都有唯一的索引。Index 以大端位元組順序儲存。

  • NumFree - 自由變數的數量。

  • Module - 實作 fun 的模組,編碼為 atom

  • OldIndex - 使用 SMALL_INTEGER_EXTINTEGER_EXT 編碼的整數。通常是模組 fun 表格中的一個小索引。

  • OldUniq - 使用 SMALL_INTEGER_EXTINTEGER_EXT 編碼的整數。Uniq 是 fun 的剖析樹的雜湊值。

  • Pid - 如 PID_EXT 中的程序識別碼。代表 fun 在其中建立的程序。

  • Free vars - NumFree 個術語,每個術語根據其類型進行編碼。

EXPORT_EXT

1N1N2N3
113ModuleFunctionArity

此術語是外部函數的編碼:fun M:F/A

ModuleFunction 編碼為原子

Arity 是使用 SMALL_INTEGER_EXT 編碼的整數。

BIT_BINARY_EXT

141Len
77LenBits資料

此術語表示一個位元字串,其位元長度不必是 8 的倍數。Len 欄位是一個 4 位元組的無符號整數(大端)。Bits 欄位是資料欄位最後一個位元組中使用的位元數 (1-8),從最高有效位元數到最低有效位元數計算。

NEW_FLOAT_EXT

18
70IEEE float

有限浮點數(即非 inf、-inf 或 NaN)以大端 IEEE 格式儲存為 8 個位元組。

此術語用於外部格式的次要版本 1。

ATOM_UTF8_EXT

12Len
118LenAtomName

一個原子儲存一個以大端順序排列的 2 位元組無符號長度,後跟 Len 個位元組,其中包含以 UTF-8 編碼的 AtomName

如需更多資訊,請參閱本頁開頭的 關於編碼原子的章節

SMALL_ATOM_UTF8_EXT

11Len
119LenAtomName

一個原子儲存一個 1 位元組無符號長度,後跟 Len 個位元組,其中包含以 UTF-8 編碼的 AtomName。使用 ATOM_UTF8_EXT 可以表示以 UTF-8 編碼的較長原子。

如需更多資訊,請參閱本頁開頭的 關於編碼原子的章節

ATOM_EXT (已棄用)

12Len
100LenAtomName

一個原子儲存一個以大端順序排列的 2 位元組無符號長度,後跟 Len 個 8 位元 Latin-1 字元,形成 AtomNameLen 允許的最大值為 255。

SMALL_ATOM_EXT (已棄用)

11Len
115LenAtomName

一個原子儲存一個 1 位元組無符號長度,後跟 Len 個 8 位元 Latin-1 字元,形成 AtomName

注意

SMALL_ATOM_EXT 在 ERTS 5.7.2 中引入,需要在 分散式交握中交換分散式旗標 DFLAG_SMALL_ATOM_TAGS

LOCAL_EXT

1...
121...

標記此項在替代的本機外部術語格式上進行編碼,僅供特定的本機解碼器解碼。從這裡開始的後續位元組可能包含任何未指定的術語編碼類型。使用者有責任僅嘗試解碼由匹配的編碼器產生的本機外部術語格式上的術語。

local 選項傳遞給 term_to_binary/2 時,Erlang 執行系統在編碼本機外部術語格式時使用此標籤,但其他編碼器也可以使用此標籤來提供類似的功能。Erlang 執行系統緊接在 LOCAL_EXT 標籤之後新增一個雜湊,該雜湊在解碼時進行驗證,以驗證編碼器和解碼器是否匹配,這可能是一個好的做法。這很可能會發現使用者犯的錯誤,但不能保證,並且不打算阻止對本機外部術語格式上故意偽造的編碼進行解碼。

LOCAL_EXT 在 OTP 26.0 中引入。