RFC 2 带读:Host Software——从 RFC 1 的草图走向可实现的主机协议

前言

RFC 1 刚刚提出“Host Software 应该怎么做”两天左右,SRI 的 Bill Duvall 就给出了 RFC 2。

两篇文档的标题都被 RFC Editor 收录为 Host software,讨论的也是同一批问题,但风格明显不同。

RFC 1 更像一张设计草图:它解释为什么需要 Host-to-Host Software,提出把 Link 0 留给控制消息,用其他 Link 模拟远程 TTY,并讨论文件传输、Host-to-Host checksum 和 DEL 终端前端。

RFC 2 则开始把这些想法往“操作系统究竟要实现什么”推进。它不再停留在“应该有一个连接机制”,而是开始回答:

  • Link 应该分成哪些类型?
  • 一个用户和远端 Host 可以有几条主连接?
  • 两个 Host 同时抢同一个 Link 时谁让步?
  • 建链超时后怎么办?
  • IMP 已经给出 RFNM,为什么还需要远端 HOST 的 positive verification?
  • Host 的 Monitor 要维护哪些网络状态?
  • 用户程序应该调用哪些 Executive Primitive?
  • 如果要绕过正常处理流程直接调试网络,操作系统要不要提供“透明 I/O”入口?
  • 网络第一次真正联调时,应该如何把复杂度压到最低?

从今天看,这些具体协议已经全部成为历史。但 RFC 2 的阅读价值并不低。

它非常直观地展示了一个协议从“概念讨论”走向“可实现系统接口”时会发生什么:抽象必须被拆成状态、超时、冲突处理、错误路径、API 和调试手段。

这也是软件开发者今天阅读 RFC 2 最值得关注的主线。

RFC 基本信息

项目 内容
RFC RFC 2
官方收录标题 Host software
作者 Bill Duvall
机构 SRI(Stanford Research Institute)
工作组 Network Working Group
RFC Editor 当前状态 Unknown
Datatracker Stream Legacy
RFC Editor 信息页 https://www.rfc-editor.org/info/rfc2/
RFC 原文 https://www.rfc-editor.org/rfc/rfc2.html

这里必须先说明两个档案层面的问题。

1. 现存机器可读版缺少原始第一页

RFC 2 当前档案开头直接写着:

1
2
3
[unknown title]

[page 1 missing]

也就是说,我们今天看到的机器可读文本并不是一份完整保存下来的原始排版件。

RFC Editor 和 IETF Datatracker 目前都把它收录为 Host software,但正文存档本身已经缺失最初的标题页。

因此,阅读 RFC 2 时需要接受一个现实:它的历史档案并不像现代 RFC 那样完整、规范。

2. 发布日期存在历史元数据差异

当前 RFC Editor 信息页显示:

1
Date published: 1 April 1969

但后来的历史资料给出了另一个日期:

  • RFC 100 在 1971 年的 RFC 分类表中记录 RFC 2 为 9 April 1969
  • RFC 2555《30 Years of RFCs》也明确写道,RFC 2 发布于 April 9, 1969,即 RFC 1 之后两天。

因此更稳妥的说法是:

RFC 2 属于 1969 年 4 月最早期的 RFC 文档;当前 RFC Editor 元数据与后来的 RFC 历史回顾在具体日期上存在差异。

这类问题也提醒我们:对于 1969 年最早期 RFC,不能把现代数据库中的每一个元数据字段都当成绝对无争议的原始记录。

另外,IETF Datatracker 将 RFC 2 标记为 Legacy,并明确说明它产生于正式 RFC Stream 和现代 IETF 标准流程建立之前,因此没有今天 IETF Standards Track 意义上的正式地位。

为什么会有这篇 RFC

RFC 2 几乎不能脱离 RFC 1 单独理解。

RFC 1 的作者 Steve Crocker 在 1969 年 4 月提出了一套非常初步的 Host Software 方案。核心框架是:

1
2
3
4
5
IMP 网络

├── Link 0:Host OS 之间的控制

└── 其他 Link:TTY-like / File-like 通信

并明确说其中很多内容还没有定稿,希望其他站点继续反馈。

RFC 2 正是这种反馈。

RFC 2555 后来回顾这段历史时直接指出:RFC 2 是对 RFC 1 建议的回应,从这里开始,RFC 之间真正形成了持续的技术对话。

从“想法”变成“实现约束”

RFC 1 已经说了:

  • Link 0 做控制;
  • 普通 Link 模拟 TTY;
  • 大文件需要另一类 Link;
  • 建链发生冲突时按照 Host ID 决定优先级;
  • Host 之间最好再做 checksum;
  • 操作系统需要向用户程序暴露若干 primitives。

但对于真正写 Host 网络软件的人来说,这还远远不够。

例如:

1
“用 Link 0 建立连接”

马上会变成一组实现问题:

1
2
3
4
5
6
7
8
Link 0 什么时候创建?
它会不会断?
每对 Host 有几个?
一个用户能开几个主连接?
请求发出去后等多久?
收到同一个 Link 的反向请求怎么办?
收到完全不符合预期的消息怎么办?
远端没回应,是重试、忽略还是认为远端故障?

RFC 2 的价值就在这里。

它开始把网络协议写成一种操作系统行为规范

当时面对的不是同构机器

早期 ARPANET 的 Host 并不是一批统一架构服务器。

后来的 RFC 33 给出的首批节点例子包括:

  • SRI:XDS 940
  • UCSB:IBM 360/75
  • UCLA:XDS SIGMA-7
  • Utah:DEC PDP-10

这些机器的 CPU 字长、操作系统、字符处理、I/O 模型、系统调用和内部数据格式都可能不同。

所以 RFC 2 不只是在解决“网络线路能不能通”,它还在解决:

异构操作系统如何实现一套足够一致的 Host-to-Host 行为。

这也是为什么 RFC 2 会花大量篇幅讨论 checksum 的字段长度、字符转换、Monitor Function 和 Executive Primitive。

核心内容

RFC 2 的正文可以按六个主题理解:

1
2
3
4
5
6
1. LINKS
2. MANIPULATION OF LINKS
3. ERROR CHECKING
4. MONITOR FUNCTIONS
5. EXECUTIVE PRIMITIVES
6. INITIAL CHECKOUT

这六部分恰好构成一个很完整的软件实现视角:

flowchart TD
    A[Link Model<br/>连接抽象]
    B[Link Manipulation<br/>状态与建链流程]
    C[Error Checking<br/>完整性与确认]
    D[Monitor Functions<br/>OS 网络子系统职责]
    E[Executive Primitives<br/>用户态 API]
    F[Initial Checkout<br/>联调与验证]

    A --> B
    B --> C
    C --> D
    D --> E
    E --> F

一、三种 Link:Control、Primary、Auxiliary

RFC 2 不再只把 Link 分成“Link 0”和“其他 Link”,而是给出了更清晰的语义分类。

Link 类型 用途 生命周期 数量特征
Control Link Host OS / Monitor 控制通信 系统装载时建立 每对 Host 只有一条
Primary Link 用户与远端 Host 的主交互通道 用户显式建立和关闭 一个用户对每个远端 Host 最多一条
Auxiliary Link 子系统额外数据通道 随子系统存在 可以建立多条

这已经不再只是“拿一个数字字段传消息”,而是开始出现明确的连接角色模型

RFC 2 延续 RFC 1 的设计:

1
Logical Link 0 = Control Link

任意两个 Host 之间只允许有一条 Control Link。

如果网络里一共有 n 个 Host,那么从某一台 Host 的视角看:

1
Control Links = n - 1

Control Link 用于:

  1. 建立 user link;
  2. 断开 user link;
  3. 传递关于 Link 或程序状态的 interrupt;
  4. Monitor 之间通信。

RFC 2 还提出:网络中的 IMP 可以自动 trace 所有经过 Link 0 的消息。

这说明控制通道从一开始就不仅承担协议控制,也天然适合作为调试和测量重点。

三、Primary Link:远程 TTY 的主会话

一个用户到每一个其他 Host:

1
最多只能有 1 条 Primary Link

而且:

1
Primary Link 必须是该用户与远端 Host 建立的第一条 Link

它具有一个很明确的远程登录语义:

1
2
3
Remote Host
把 Primary Link
视为一条普通 data-phone / direct-line TTY

因此用户会通过它:

  • 使用远端 Host 原有的 login procedure;
  • 发送用户控制信息;
  • 像拨号终端一样操作远端系统。

RFC 2 还说明 Primary Link 对用户而言是 global 的:某个 user program 打开 Primary Link 后,这条 Link 会保持,直到被明确关闭。

这和 Auxiliary Link 的生命周期形成鲜明对比。

四、Auxiliary Link:附属于子系统的额外通道

一个 user program 可以与远端 Host 中的 user program 建立任意数量的 Auxiliary Link。

它可以传输:

1
2
3
binary

character data

而且它是 local to the sub-system

1
2
3
4
5
6
7
8
进入某子系统

├── 建立 Auxiliary Link A
├── 建立 Auxiliary Link B

退出子系统

└── Auxiliary Links 一并关闭

这相当于把长期存在的用户控制会话,与某项具体应用工作所需的数据通道区分开。

五、标准字符集:Full 8-bit ASCII

RFC 2 明确提出 Control Link 和 Primary Link 使用统一的标准传输字符集。

原文写作:

1
full (8 bit) ASCII

今天看这句话会有一点奇怪,因为 ASCII 的经典字符编码通常被描述为 7-bit。

阅读这里最安全的方式是保留 RFC 2 的原始术语:

RFC 2 当时希望在 Control / Primary Link 上约定一个统一的 8-bit 传输字符表示,并把它称作 full (8 bit) ASCII。

它反映的核心问题不是“现代 ASCII 到底几位”,而是:

异构 Host 如果连字符表示都不统一,远程终端协议根本无法工作。

关键机制与工作流程

RFC 2 给出的 get link 流程比 RFC 1 明确得多。

假设:

1
Host A → Host B

A 要建立 Primary Link。

首先,A 从本地 allocation table 中选择一条当前未使用的 Link:

1
A.selectUnusedLink(B)

随后通过 Link 0 发送:

1
LINK-CONNECT(link=k)

然后 A 进入等待。

sequenceDiagram
    participant U as User / Executive
    participant A as HOST A Monitor
    participant B as HOST B Monitor

    U->>A: Establish Primary Link(B)
    A->>A: 从 allocation table 选择 unused Link k
    A->>B: Link 0: LINK-CONNECT(k)
    A->>A: 等待 response 或 timeout
    B-->>A: VERIFY(k)
    A-->>U: success, return Link k

如果 B 返回对该 Link 的 verification,Primary Link 建立成功,并把 Link number 返回给调用者。

真正有意思的是 race condition。

假设:

1
2
A 选择 Link 12
B 也同时选择 Link 12

双方都通过 Control Link 请求建立。

RFC 2 明确使用 Host 的 Network ID Number 解决冲突。

设:

1
2
Na = HOST A 的 Network ID
Nb = HOST B 的 Network ID

规则是:

1
2
3
4
Na > Nb
→ A 获胜
→ A 忽略 B 的建链请求
→ A 继续等 B 确认自己的请求

反过来:

1
2
3
4
5
6
Na < Nb
→ A 让步
→ A 接受 B 的请求
→ A 给 B 发送 verification
→ A 放弃自己的请求
→ A 重新开始 link allocation
flowchart TD
    A[双方同时请求同一个 Link]
    B{Na > Nb ?}
    C[A 保留自己的请求]
    D[A 接受 B 的请求]
    E[A 等待 B 对自己请求的确认]
    F[A 向 B 发送 verification]
    G[A abort 自己的请求]
    H[A 重新选择 Link]

    A --> B
    B -->|Yes| C
    C --> E
    B -->|No| D
    D --> F
    F --> G
    G --> H

这是 RFC 1 中“用 Host ID 排序”的具体化。

从今天的分布式系统视角看,它是一种很经典的做法:

用所有参与者都能独立计算的全序规则解决同时发生的竞争。

没有锁服务,没有 leader election,也没有复杂共识协议。网络规模很小时,这种做法足够简单。

三、收到不符合状态机预期的消息

RFC 2 对异常处理很“硬”。

A 请求某条 Link 后,如果 B 返回的既不是 verification,也不是 B 对同一 Link 的建立请求,那么 RFC 2 认为可能发生:

  1. A 错误地选择了一条已经分配的 Link;
  2. B 正在未分配的 Link 上发数据;
  3. 分配相关消息在传输中被破坏。

A 的处理方式是:

1
2
3
4
5
发送 LINK-DISCONNECT

把 HOST B 视为 error state

进入 panic routine

同样,如果在规定时间内没有收到 B 的任何相关响应:

1
2
3
4
5
6
7
timeout

认为 B 处于 error state

发送 disconnect

panic routine

这说明 RFC 2 已经意识到一个协议不能只有 happy path。

但它的恢复策略还非常原始:

1
unexpected state ≈ peer error ≈ panic

今天的网络软件显然不会这么轻易把远端 Host 判死。

Auxiliary Link 必须由两端 user program 都发起请求。

而真正负责主动建立 Link 的是哪一边,仍然由 Host ID 决定:

1
2
Na > Nb
→ A 主动执行 getlink
1
2
Na < Nb
→ A 等待 B 建立

等待时存在 timeout。

更有意思的是,RFC 2 明确说:Host A 和 B 中的程序应该能够指定等待 timeout 的时间。

也就是说,timeout 并不完全被硬编码在 Host 协议内部,而是开始暴露给上层。

这已经有一点今天 API 中:

1
2
3
connectTimeout
readTimeout
operationTimeout

的影子。当然,这只是设计上的相似性,不代表现代 timeout API 来源于 RFC 2。

五、两层确认:RFNM 与 HOST Verification

RFC 2 对“消息已经送到哪里”做了一个非常值得注意的区分。

发送端在 acknowledge mode 下,会等待两个事件:

1
2
1. destination IMP 返回 RFNM
2. destination HOST 通过 Control Link 返回 response

流程可以画成:

sequenceDiagram
    participant A as HOST A
    participant IA as IMP A
    participant IB as IMP B
    participant B as HOST B

    A->>IA: Data Message
    IA->>IB: Network delivery
    IB->>B: Deliver Message
    IB-->>IA: RFNM
    IA-->>A: RFNM
    B-->>A: Control Link: positive verification

两者含义不同:

信号 谁产生 说明什么
RFNM 目标 IMP 网络侧允许该 Link 继续发送下一条 Message
Positive Verification 目标 HOST Host 已收到并通过 Host 级检查

这正是阅读 RFC 2 时最不能混淆的地方。

1
RFNM ≠ Remote Host ACK

更不等于:

1
Remote User Process 已经完成业务处理

六、Checksum:不仅要发现 bit error,还要适应异构字长

RFC 2 继续强化 RFC 1 的 Host-to-Host Error Checking。

它要求所有网络消息在初期都进行 error check,主要目的甚至写得非常工程化:帮助隔离 software bug 和 hardware bug。

也就是说,这里的 checksum 不只是“通信可靠性机制”,也是调试手段。

RFC 2 希望 checksum 具有 order dependent(顺序相关) 的性质,避免简单的字段交换仍然得到相同结果。

其核心计算思想是 folding

1
2
3
4
5
6
7
FIELD 1
+
FIELD 2
+
carry 回卷到低位
=
RESULT

用现代伪代码近似表示:

1
2
3
4
sum = field1 + field2

while carry exists:
sum = low_bits(sum) + carry(sum)

RFC 2 还希望可以把若干字段并行相加,然后再 folding,以便不同字长机器充分利用本机硬件。

它给出两个非常具体的数字:

1
2
Field length    = 288 bits
Checksum length = 8 bits

为什么是 288 bits

原文解释它来自:

1
2
3
24
32
36

三种机器字长的共同长度。

从数学上看:

1
LCM(24, 32, 36) = 288

也就是说,RFC 2 正面面对的不是今天常见的 x86_64 ↔ ARM64,而是多个字长差异非常明显的计算机系统。

协议算法甚至要考虑:

怎样让 24-bit、32-bit、36-bit Host 都能比较高效地算同一个 checksum。

七、顺序相关 checksum

简单加法 checksum 有一个明显问题:

1
A + B = B + A

所以字段顺序交换时可能无法检测。

RFC 2 提出把消息分组,对各组 checksum 进行 shift,再依次加入下一组的 checksum,从而让最终结果和数据顺序相关。

概念上可以理解为:

1
2
3
4
5
group1 -> checksum -> shift
\
group2 -> checksum ------ + -> shift
\
group3 -> checksum ------------- + -> final

它不是现代密码学 hash,也不是今天应该重新实现的完整性算法。

阅读价值在于:设计者已经意识到“能发现随机 bit error”与“能感知数据排列变化”是不同要求。

八、字符转换应该放在哪里

RFC 2 在 Monitor Functions 中提出一个现在看起来依旧熟悉的架构问题:

1
2
3
character translation
到底在 HOST 做
还是在 IMP 做?

原文甚至认为很有可能由 IMP 来做 character translation。

但马上又提出两个未解决问题:

  1. 用 table 还是 algorithm?
  2. 哪些消息需要转换?显然不应该转换所有消息。

第二个问题尤其重要。

因为如果把 binary data 也当成字符去转换,结果当然会被破坏。

所以系统必须区分:

1
2
3
text
vs
binary / opaque payload

后来 Host-Host Protocol 的设计逐渐减少了这种对统一字符集的强约束。RFC 33 已经明确提出,网络应该尽量避免对 character set、programming language 等做不必要限制。

这说明 RFC 2 中的“IMP 负责字符转换”并没有成为后来 Host-Host 协议的核心方向。

九、Monitor 的输入路径

RFC 2 给出的输入路径可以整理成:

flowchart TD
    A[Receive Message from IMP]
    B[Verify Checksum]
    C{Acknowledge Mode?}
    D[通过 Control Link<br/>发送 Message Received]
    E[Character Translation<br/>如适用]
    F[Decode Header]
    G[按 Source + Link<br/>分发给正确 Recipient]

    A --> B
    B --> C
    C -->|Yes| D
    C -->|No| E
    D --> E
    E --> F
    F --> G

这实际上已经很像一个操作系统网络栈的接收 pipeline:

1
2
3
4
5
6
driver receive
→ integrity check
→ optional transform
→ header decode
→ demultiplex
→ deliver

十、Monitor 的输出路径

输出则是:

flowchart TD
    A[Build Header]
    B[Character Translation<br/>如适用]
    C[Create Checksum]
    D[Check Link Status]
    E{上一个 Message 的 RFNM 到了吗?}
    F[Wait RFNM]
    G[Transmit Message to IMP]
    H{Acknowledge Mode?}
    I[Wait RFNM]
    J[Wait destination HOST response]

    A --> B
    B --> C
    C --> D
    D --> E
    E -->|No| F
    F --> G
    E -->|Yes| G
    G --> H
    H -->|Yes| I
    I --> J

RFC 2 在这里已经把 header construction、encoding/translation、checksum、flow control、network transmission 和 acknowledgement 串成了一条完整发送路径。

十一、Monitor 还要维护 Network Status

RFC 2 不认为网络软件只是“收包和发包”。

Monitor 还应该:

  • 维护其他 HOST 的状态;
  • 如果某个 IMP down,则认为它对应的 HOST down;
  • 维护 Control Link 状态;
  • 回答其他 HOST 的 status query;
  • 当 Primary / Auxiliary Link 状态变化时通知其他 HOST;
  • 报告使用这些 Link 的程序状态。

从今天看,这里已经混合了:

1
2
3
4
5
data plane
control plane
health checking
service status
process status

多个概念。

后来的协议会把这些职责拆得更清楚,但 RFC 2 已经认识到:

真正可运行的网络系统不仅需要数据传输,还需要状态管理。

十二、Executive Primitives:给程序员的网络 API

RFC 2 列出了一组很具体的 Executive Primitives。

Primary Link:

1
2
3
4
5
6
Establish primary link
Connect controlling teletype to primary link
INPUT/OUTPUT over primary link
Interrogate status of primary link
Disconnect controlling teletype from primary link
Kill primary link

Auxiliary Link:

1
2
3
4
Establish auxiliary link
INPUT/OUTPUT over auxiliary link
Interrogate status auxiliary link
Kill auxiliary link

其中 Establish Auxiliary Link 会返回一个:

1
logical link number

RFC 2 说它:

1
similar to a file index

随后所有 Auxiliary routines 都使用这个数字作为参数。

这对今天的软件开发者非常熟悉:

1
2
3
4
fd = open(...)
read(fd, ...)
write(fd, ...)
close(fd)

当然,RFC 2 的 logical link number 不是 Unix file descriptor。

但抽象方式已经非常接近:

创建资源 → 返回 handle → 后续 API 都通过 handle 操作资源。

十三、Transparent I/O:给调试留一个逃生舱

RFC 2 还定义了一个特殊 Executive Function:

1
Transparent INPUT/OUTPUT over link

Monitor 不对 buffer 做任何修改,而是:

1
2
3
buffer

└── 直接交给 IMP

这意味着调用者自己必须准备:

  • header;
  • control information;
  • payload。

RFC 2 明确说:

1
intended use = network debugging

这可以类比今天系统里常见的 raw socket、packet injection、bypass normal codec、debug transport、low-level diagnostic API。

重要的不是具体 API,而是一个持续至今的工程经验:

高层抽象很好,但网络实现必须保留足够底层的诊断入口。

十四、Initial Checkout:第一次联调要尽量简单

RFC 2 最后一部分不是再加协议功能,而是讨论怎样测试网络。

初始 checkout 使用:

1
simulated data-phone mode

并且:

1
每条 Message 只有 1 个字符

Link 对 Monitor 透明,由 user program 通过特殊 primitive 直接控制。

文档给出的示例是:

1
DDT ↔ DDT

即两个不同 HOST 上的用户程序直接通信。

RFC 2 并没有在这里解释 DDT 缩写,所以没有必要为了现代读者强行扩展它;真正重要的是测试策略:

1
2
3
4
5
6
7
8
9
复杂 Host Software

先绕开大部分抽象

只发单字符

两端用户程序直接验证

另开电话线路同步确认现象

原文甚至要求测试同时配一条:

1
telephone link or similar

这很有 1969 年的味道,但工程思想一点都不过时:

第一次系统联调,不要同时验证十层抽象;先把测试路径压缩到最小。

重点概念与术语

HOST

ARPANET 中连接到网络的计算机系统。

它不是今天狭义的“服务器”,而往往是一整套大型计算机和操作系统环境。

IMP

IMP(Interface Message Processor,接口报文处理机)

ARPANET 的分组交换节点,负责 Host 接入和网络内部消息传递。

RFC 2 仍然建立在:

1
2
3
4
5
HOST

IMP

ARPANET

这个结构之上。

Monitor

RFC 2 中的 Monitor 可以理解为 Host 操作系统中的特权监控/执行环境。

网络 I/O driver、Link 状态、Host 状态和 Executive Primitive 都由它承担或管理。

不要直接把它等同于今天某个具体 kernel module。

逻辑 Link 0

每对 Host 一条,用于 Host/Monitor 之间的连接管理、状态、interrupt 和其他控制通信。

一个用户与某个远端 Host 的主要会话连接。

特征是:

1
每用户、每远端 Host,最多 1 条

它被远端当作普通 TTY 用户。

用户程序在已有关系基础上建立的附加 Link。

可以多条,用于 binary 或 character transmission,并与具体 subsystem 生命周期绑定。

RFNM

RFNM(Request for Next Message)

由 IMP 产生,表示发送方可以在相应 Link 上继续发送下一条 Message。

它是网络/IMP 层机制,不是远端应用层业务确认。

Positive Verification

两个 HOST 可以进入一种模式,对所有 Message 要求 Host 级正向确认。

确认走:

1
Control Link

它与 RFNM 是不同层次的反馈。

Folding

RFC 2 checksum 算法中的 carry folding:

1
2
3
高位 carry
重新加回
低位

用于形成适应不同机器字长的加法型 checksum。

Network ID Number

HOST 的网络编号。

RFC 2 使用:

1
Na > Nb

这种确定性排序来解决两端同时抢同一 Link 的竞争。

Executive Primitive

操作系统暴露给 user program 的基础网络操作。

大致相当于“系统层网络 API”的早期形式。

Transparent I/O

绕过 Monitor 的普通修改和封装逻辑,把调用者构造好的 buffer 直接送往 IMP。

主要用于:

1
network debugging

需要特别注意的点

1. RFC 2 不是现代意义上的 IETF 标准

Datatracker 明确将其标记为 Legacy

它产生于正式 IETF 标准流程之前,不能把它当成今天可以实现和部署的 Standards Track RFC。

2. 当前档案第一页缺失

这是阅读 RFC 2 时必须记住的事实。

现存文本开头写着:

1
2
[unknown title]
[page 1 missing]

因此文档最初的完整标题页和可能存在的导言内容已经无法从当前机器可读档案中直接恢复。

3. 发布日期存在 4 月 1 日与 4 月 9 日两种记录

当前 RFC Editor 信息页:

1
1 April 1969

RFC 100 和 RFC 2555:

1
9 April 1969

写历史文章时最好把这种差异明确告诉读者,而不是悄悄选一个日期。

4. RFC 2 的绝大多数 Host Software 设计很快就过时了

RFC 100 在 1971 年对早期 RFC 进行分类时,对 RFC 2 的结论是:

1
C.5, otherwise obsolete

这里的 C.5 是:

1
Error Control and Status Testing

RFC 100 认为 RFC 2 在 error checking、checksum 和 positive verification 方面仍有历史参考价值,但其他部分已经 obsolete。

这意味着 RFC 2 不是一个后来长期运行的 Host-Host Protocol 定稿。

5. 后来的 Host-Host Protocol 已经换了一套更完整的模型

RFC 100 总结早期协议演化时指出:

  • RFC 9、11、22 属于更早的一轮 Host-Host Protocol;
  • 那套方案因为能力不足,在 1969 年 12 月网络会议后被放弃;
  • RFC 33 成为后来协议的基础;
  • RFC 54 提出了 initial official protocol;
  • 后续又经历大量修改。

RFC 33 中已经出现:

  • Network Control Program(NCP);
  • connection;
  • socket;
  • control command;
  • system call;
  • flow control;

等更成熟抽象。

所以不要把 RFC 2 当成 NCP 的最终规范。

它们建立在 ARPANET IMP 的 logical link 机制之上。

与 TCP 相比,层次、连接状态、流控语义和端点命名都不同。

不能简单写成:

1
2
Primary Link = TCP connection
Auxiliary Link = another TCP connection

这样的类比会掩盖历史协议真正的结构。

7. RFNM 不是 TCP ACK,更不是业务 ACK

这一点值得再次强调。

RFC 2 在 acknowledge mode 下甚至明确同时等待:

1
2
3
RFNM from destination IMP
+
response from destination HOST

如果两者是一回事,就根本不需要同时存在。

8. 8-bit ASCII 应按原文语境理解

RFC 2 原文确实写:

1
full (8 bit) ASCII

现代读者不要把这里变成一场“ASCII 到底是 7 bit 还是 8 bit”的编码史争论。

它真正要解决的是:Control / Primary Link 上需要统一字符表示。

9. 288-bit 是为异构机器服务的

RFC 2 为 checksum 提出的:

1
Field length = 288 bits

并不是一个神秘网络常数。

它与:

1
2
3
24-bit
32-bit
36-bit

机器字长直接相关。

这是一种典型的“协议适配当时硬件现实”的设计。

10. checksum 不是安全机制

RFC 2 的 checksum 目标是:

  • 检测传输或软件错误;
  • 帮助定位 hardware / software bug。

它不具备现代密码学完整性保证。

不能用于防篡改、身份认证或抵御主动攻击。

11. “对端没响应就 panic”反映了非常早期的故障模型

RFC 2 在一些异常条件下直接:

1
2
consider HOST B in error
→ panic routine

现代网络必须处理 packet loss、transient failure、congestion、process restart、network partition、duplicate、delayed message、retry、idempotency 等问题。

RFC 2 还没有形成这些完整的故障恢复模型。

12. Character Translation 放进 IMP 的想法后来并没有成为主线

RFC 2 对“IMP 是否负责字符转换”保持开放态度。

但后来的 RFC 33 已明确强调应尽量避免强加 character set、programming language 等限制。

从协议演化看,网络核心逐渐更倾向于传输通用数据,而不是承担越来越多上层表示转换。

13. 文档中的术语本身还没有完全稳定

RFC 2 前文主要使用:

1
2
Primary Link
Auxilliary Link

后面的 Network Status 部分又出现:

1
primary and secondary links

再加上 Auxilliary 本身是旧文档中的拼写形式。

这些不一致很能说明:

这是一份工作中的协议草案,而不是经过现代 RFC 编辑流程精修的标准。

从今天的视角重新看这篇 RFC

一、协议设计真正困难的是状态,而不是报文格式

很多开发者第一次设计协议时,最先想到的是:

1
2
header 放什么字段?
message 怎么编码?

RFC 2 告诉我们,真正麻烦的部分很快就会变成:

1
2
3
4
5
6
7
谁先发?
谁确认?
同时发怎么办?
超时怎么办?
收到意外消息怎么办?
资源什么时候释放?
状态由谁维护?

也就是:

Protocol = Message Format + State Machine + Failure Handling

RFC 2 虽然还没有画出正式状态机,但它已经完全进入了这个阶段。

二、一个确定性的 tie-breaker 可以消灭一类分布式竞争

RFC 2 用:

1
Na > Nb

解决同时抢 Link 的问题。

这种方法极其朴素,但非常有效:

1
2
3
4
5
所有节点看到同一组 ID

执行同一比较规则

得到同一个赢家

今天在设计 leader conflict、simultaneous connect、duplicate ownership、collision resolution 时仍然经常会使用某种稳定排序作为 tie-breaker。

区别只是现代系统通常还要叠加 epoch、term、lease、fencing token 等机制。

三、Control Channel 与 Data Channel 的分离非常自然

RFC 2 中:

1
2
Control Link
Primary / Auxiliary Links

承担不同职责。

今天的软件里随处可见类似模式:

1
2
3
4
control plane / data plane
command channel / data channel
metadata stream / payload stream
signaling / media

不能说现代架构源自 RFC 2,但这说明一个很普遍的设计规律:

控制协议和大规模数据传输往往具有不同的可靠性、延迟、解析和生命周期需求。

四、网络 API 不应只暴露 send / receive

RFC 2 的 Executive Primitives 已经覆盖:

  • establish;
  • attach terminal;
  • I/O;
  • interrogate status;
  • detach;
  • kill;
  • transparent I/O。

也就是说,网络编程接口需要同时解决:

1
2
3
4
5
6
7
resource lifecycle
+
data movement
+
status inspection
+
debugging

今天 Socket、Netty Channel、QUIC Connection、RPC Client 等 API 也都绕不开这些维度。

五、可观测性和调试能力不能等上线后再补

RFC 2 有两个非常典型的设计:

1
Link 0 可自动 trace

以及:

1
Transparent I/O 专门用于 network debugging

再加上:

1
2
所有消息初期都做 error checking
以帮助隔离 software / hardware bugs

可以看到早期网络工程师非常清楚:

一个新网络首先不是要“跑得优雅”,而是要“出了问题能知道坏在哪”。

对今天的分布式系统仍然适用:trace、metrics、logs、packet capture、health check、debug endpoint 不是装饰,而是系统的一部分。

六、分层确认比一个模糊的“成功”更有价值

RFC 2 同时区分:

1
2
IMP RFNM
HOST positive verification

这让“成功”不再是一个含糊布尔值。

现代系统里也应该经常问:

1
2
3
4
5
6
7
8
9
10
11
TCP write success
是否等于对端 read?

对端 read
是否等于应用处理?

应用处理
是否等于事务 commit?

事务 commit
是否等于下游已经消费?

不同层级应该拥有不同确认语义。

RFC 2 在非常早期就展示了这种边界意识。

七、协议与硬件现实之间永远存在张力

288-bit checksum field 的设计今天看起来很怪。

但站在当时:

1
2
3
24-bit machine
32-bit machine
36-bit machine

同时联网的环境里,它非常合理。

今天我们面对的是另一组约束:MTU、cache line、SIMD、zero-copy、NIC offload、mobile power、QUIC userspace、TLS hardware acceleration。

协议从来不是在真空里设计的。

八、最小化联调路径是一种高级工程能力

RFC 2 的 Initial Checkout 很值得软件开发者单独记住:

1
2
3
先发单字符
先让 user program 直接控制 Link
先用电话保持两边人工同步

它是在主动减少变量。

现代复杂系统第一次联调也应该如此:

1
2
3
4
5
6
7
8
9
10
11
12
不要一上来:
Gateway
→ Service Mesh
→ Auth
→ MQ
→ DB
→ Cache
→ Async Worker
→ Callback

而是先验证:
A → B

RFC 2 甚至可以当成一份 1969 年的“最小可验证链路”案例。

这篇 RFC 带来的影响

一、它让 RFC 从“单篇建议”真正变成了对话

RFC 2555 对 RFC 2 的历史评价非常关键:RFC 2 是对 RFC 1 建议的回应,RFC 的技术对话由此真正开始。

RFC 系列最重要的文化并不是:

1
2
某位专家写标准
其他人照做

而是:

1
2
3
4
5
6
proposal
→ response
→ revision
→ disagreement
→ experiment
→ new proposal

RFC 2 是这条链条中第一个非常鲜明的“response”。

二、它把 RFC 1 的 Host Software 概念推进到了操作系统实现层

RFC 1 提出了 primitives。

RFC 2 则继续补上:

  • Monitor I/O pipeline;
  • Link status;
  • Host status;
  • timeout;
  • error path;
  • handle-like link number;
  • transparent debug I/O。

这意味着讨论开始从:

1
网络应该有什么能力

推进到:

1
Host OS 到底怎样实现这些能力

这一步对任何协议落地都不可缺少。

三、它保留下来的最重要技术主题是 Error Control / Status Testing

RFC 100 在 1971 年明确说:

1
RFC 2: C.5, otherwise obsolete

而 C.5 正是:

1
Error Control and Status Testing

RFC 100 特别总结了 RFC 2 的两点:

  • error checking / checksum;
  • 两个 Host 可以进入“所有消息都要求 positive verification”的模式。

随后 RFC 37、39、40、46、48、54 等继续发展 error reporting、Host status、query / reply、echo testing 和更完整的 control command。

也就是说,RFC 2 的具体 Link 模型很快被淘汰,但:

Host 级错误检测、状态测试和确认语义

成为后续 Host-Host Protocol 持续讨论的主题。

四、后来的 NCP 把这些早期想法重新组织成更完整的 Host-Host Protocol

到 RFC 33,Host-Host Protocol 已经发展出:

1
2
3
4
5
6
7
8
9
10
Process


Network Control Program

├── system calls
├── connections
├── sockets
├── control commands
└── flow control

RFC 54 则进一步尝试形成 initial official protocol,并明确区分:

1
network-wide protocol

和:

1
local NCP implementation / system calls

这是一个非常重要的成熟过程。

RFC 2 还在把协议要求、OS 实现、API、调试策略大量混在一份文档里。

到 RFC 54,设计者已经明确意识到:

哪些东西必须全网统一,哪些东西应该由每台 Host 自己实现,是两个不同的问题。

这也是互联网协议后来能够跨操作系统扩展的关键思维之一。

五、Host-Host Protocol 最终也只是过渡阶段

后来保存为 RFC 6529 的历史 Host/Host Protocol 文档说明,这套协议从 January 1972 开始成为 ARPA Network 的正式 Host-Host Protocol,并一直使用到 January 1983 切换到 TCP/IP。

因此 RFC 2 在技术演化链上的位置更适合写成:

flowchart LR
    A[RFC 1<br/>Host Software]
    B[RFC 2<br/>实现细化与回应]
    C[早期 Host-Host 实验<br/>RFC 9 / 11 / 22]
    D[RFC 33<br/>New HOST-HOST Protocol]
    E[RFC 54<br/>Initial Official Protocol Proposal]
    F[NCP / Host-Host Protocol<br/>ARPANET]
    G[TCP/IP]

    A --> B --> C --> D --> E --> F --> G

这不是一条“代码版本升级链”。

中间发生过放弃旧方案、重构抽象、重新定义连接、新增 socket、修改 flow control、重新定义 control commands。

所以更准确的理解是:

RFC 2 贡献的是早期设计探索,而不是后来协议的固定 wire format。

六、它还记录了在线协作本身的早期历史

RFC 2555 记载,RFC 2 是通过 SRI 的 NLS(oN-Line System) 在线制作,并进入 SRI NLS Journal 的。

这件事很有象征意义。

RFC 1 讨论如何通过网络访问 SRI 的 NLS 文档系统;RFC 2 本身又是在 NLS 中制作出来的。

换句话说:

网络协作工具和网络协议文档,从一开始就在相互推动。

今天开发者通过 Git、GitHub、issue、pull request、mailing list、collaborative docs 共同设计协议,看起来再自然不过。

但 RFC 2 所处的年代,这种“在线共同推进技术文档”的工作方式本身就是新事物。

总结

如果 RFC 1 是:

“我们大概要设计一种 Host Software。”

那么 RFC 2 已经开始问:

“很好,现在告诉我操作系统具体要维护什么状态、提供什么 API、遇到竞争和错误时到底做什么。”

它最重要的设计可以压缩成下面这张表:

问题 RFC 2 的回答
Host OS 间如何控制 使用 Control Link / Link 0
用户如何保持主远程会话 使用 Primary Link
子系统如何增加数据通道 使用 Auxiliary Link
两边同时抢 Link 怎么办 Network ID 决定优先级
等不到远端怎么办 timeout → disconnect → error/panic
IMP 确认是否足够 不够,可增加 HOST positive verification
如何发现软件/硬件错误 每条 Message 做 Host 级 checksum
异构字长怎么办 设计适配 24/32/36-bit 机器的 folding checksum
网络 I/O 谁管理 Host 的 Monitor
用户程序如何访问网络 Executive Primitives
如何调试底层网络 Transparent I/O
第一次联调怎么做 单字符、最短路径、用户程序直控 Link

它的大多数具体设计很快被后续 Host-Host Protocol 淘汰。

1971 年的 RFC 100 已经把 RFC 2 总结为:

1
C.5, otherwise obsolete

真正保留下来继续发展的,是 Error Control、Host Status、Positive Verification 等问题。

但如果今天的软件开发者只因为“协议已经 obsolete”就跳过 RFC 2,会错过一些很有价值的东西。

RFC 2 展示了协议工程从概念走向实现时必然出现的几类问题:

1
2
3
4
5
6
7
8
9
10
抽象要变成状态
状态要处理竞争
竞争需要确定性规则
等待必须有 timeout
失败必须有恢复路径
协议必须暴露 API
API 必须管理生命周期
网络必须提供调试入口
不同层必须区分确认语义
异构系统必须找到共同表示

这些问题,2026 年的分布式系统依然一个都没有消失。

变化的只是答案。

1969 年,答案可能是:

1
2
3
4
5
6
Link 0
Primary Link
Auxiliary Link
RFNM
288-bit checksum field
panic routine

今天,答案可能是:

1
2
3
4
5
6
7
8
9
10
TCP / QUIC
stream
RPC
deadline
health check
trace
retry
idempotency
mTLS
structured error

协议技术不断变化,但协议设计真正困难的部分一直很稳定:

让两个彼此独立、可能同时行动、可能失败、内部实现完全不同的系统,对“现在发生了什么”达成足够一致的理解。

RFC 2,就是互联网工程师非常早的一次认真尝试。


RFC 2 带读:Host Software——从 RFC 1 的草图走向可实现的主机协议
https://allendericdalexander.github.io/2026/08/12/rfc/rfc00002-host-software/
作者
AtLuoFu
发布于
2026年8月12日
许可协议