RFC 6 带读:Conversation with Bob Kahn——Host 与 IMP 的边界到底应该有多聪明
前言
RFC 6 可能是最不像“标准文档”的 RFC 之一。
它没有章节,没有正式协议语法,也没有长篇背景说明。Steve Crocker 只是在开头写了一句:
他昨天和 BBN 的 Bob Kahn 聊了一次。
随后把谈话中的几个技术结论记了下来。
全文真正讨论的内容只有三类:
1 | |
看起来很零散。
但如果把它放回 1969 年 4 月的 ARPANET 现场,这些问题一点都不小。
当时各站点连接到网络的 Host 彼此高度异构:
- 有的使用 6-bit 字符编码;
- 有的使用 7-bit;
- 有的使用 8-bit;
- 还有的可能使用 9-bit 表示;
- CPU 字长、操作系统、终端和 I/O 机制也完全不同。
而 BBN 正在设计的 **IMP(Interface Message Processor,接口报文处理机)**不仅要转发数据,还必须和这些不同 Host 直接通信。
于是一个非常基础的问题出现了:
当两个 Host 连自己的字符都不是用同一种方式表示时,网络应该只是“搬运 bit”,还是应该主动帮它们做转换?
RFC 6 记录的正是这个边界仍未确定的阶段。
更有意思的是,它还列出一组 Host 可以交给 IMP 的控制信息:
1 | |
反方向,IMP 也需要告诉 Host:
1 | |
如果用今天的软件架构语言重新描述,这已经不是一个简单的“网卡接口”。
它同时在探索:
1 | |
究竟哪些功能应该属于网络,哪些应该属于端系统。
后来的互联网会越来越强调端到端协议、通用数据传输和更清晰的分层边界;但 RFC 6 保存的是这些边界还没有稳定下来时的一次真实讨论。
这也是这篇只有一页的 RFC 最值得读的地方。
RFC 基本信息
| 项目 | 内容 |
|---|---|
| RFC | RFC 6 |
| 官方英文标题 | Conversation with Bob Kahn |
| 作者 | Steve Crocker(S. D. Crocker) |
| 所属机构 | UCLA |
| 文档类型 | Network Working Note |
| 原文日期 | 10 April 1969 |
| RFC Editor 当前 Status | Unknown |
| Stream | Legacy |
| 1971 年历史分类 | Obsolete |
| RFC Editor 信息页 | RFC 6: Conversation with Bob Kahn |
| RFC 原文 | RFC 6 原文 |
RFC Editor 的发布日期与原文日期不一致
RFC 6 原文头部明确写:
1 | |
1971 年的 RFC 100 在回顾早期 NWG/RFC 时,也记录:
1 | |
但今天 RFC Editor 的信息页元数据中显示:
1 | |
对于最早一批 RFC,这种元数据差异并不罕见。
因此本文采用原文自身和 RFC 100 都一致记录的:
1 | |
作为主要历史日期。
RFC 6 今天已经没有现行协议意义
RFC Editor 当前 Status 显示:
1 | |
但这不意味着 RFC 6 仍然是一份待实现的有效规范。
1971 年的:
RFC 100 — Categorization and Guide to NWG/RFCs
已经明确把 RFC 6 标为:
1 | |
因此 RFC 6 今天的价值主要是:
1 | |
而不是实现规范。
为什么会有这篇 RFC
RFC 6 不能脱离 RFC 1、RFC 2 以及当时 BBN 的 IMP 设计来理解。
一、IMP 已经成为 Host 与网络之间的明确边界
ARPANET 的基本结构是:
flowchart LR
HA[HOST A]
IA[IMP A]
N[ARPANET IMP Network]
IB[IMP B]
HB[HOST B]
HA --> IA
IA --> N
N --> IB
IB --> HB
Host 不直接操作远端线路。
它先把 Message 交给本地 IMP。
IMP 再负责:
- 网络内部传输;
- 分包和重组;
- 链路控制;
- 部分状态反馈。
所以一个核心架构问题就是:
Host 和 IMP 之间的接口,究竟应该有多复杂?
如果接口极简:
1 | |
那么:
- 字符转换;
- 错误解释;
- 状态维护;
- 远端故障检测;
全部需要 Host 自己做。
如果 IMP 更“聪明”,则可以承担:
1 | |
RFC 6 正在讨论第二种方向能走多远。
二、字符编码不统一是一个现实阻碍
现代开发者习惯:
1 | |
以至于容易低估 1969 年的字符互操作问题。
当时不同计算机可能使用完全不同的内部字符表示。
RFC 6 直接列出:
1 | |
字符码。
这意味着:
1 | |
不一定能够被:
1 | |
直接理解。
所以即使底层网络能把每一个 bit 完整送达:
1 | |
也不代表:
1 | |
已经实现。
三、到底谁来做转换
可以有三种基本路线:
1 | |
RFC 6 记录的 BBN 方案明显倾向第二种:
IMP 将不同 Host 的字符码转换成 8-bit ASCII 进行网络传输,再由目的端 IMP 转回。
这意味着 IMP 不再只是透明搬运 bit。
它开始理解:
1 | |
这是一个很强的网络内部语义。
四、RFC 1 和 RFC 2 已经提出,但还没有稳定
RFC 1 曾经提出一个 16-bit Header:
1 | |
RFC 2 又进一步讨论:
- Primary Link;
- Auxiliary Link;
- Control Link;
- 字符转换;
- Host 状态;
- IMP 状态。
RFC 6 则突然说:
1 | |
也就是:
1 | |
这和 RFC 1 中的:
1 | |
并不一致。
而一个月后的 RFC 7 又再次给出:
1 | |
这非常重要。
它说明 1969 年 4~5 月时:
Host-IMP Interface 还处在高速讨论和修改阶段。
不能把任何一篇早期 RFC 中的字段布局当成已经冻结的正式 wire format。
核心内容
RFC 6 虽然只有一页,但可以拆成三个核心主题。
flowchart TD
A[RFC 6]
B[Character Code Conversion]
C[HOST → IMP Control]
D[IMP → HOST Status]
E[Host / Network Boundary]
A --> B
A --> C
A --> D
B --> E
C --> E
D --> E
一、IMP 内的字符转换
RFC 6 记录:
BBN 准备让 IMP 把:
1 | |
字符码统一转换成:
1 | |
用于网络传输。
目的端 IMP 在 Message 重组之后,再做一次转换。
基本流程可以表示为:
flowchart LR
A[Source HOST<br/>Local Character Code]
B[Source IMP]
C[8-bit ASCII<br/>Network Representation]
D[Destination IMP]
E[Destination HOST<br/>Local Character Code]
A --> B
B -->|Convert| C
C --> D
D -->|Convert Again| E
这是一种:
1 | |
的设计。
二、每个 Host 拥有自己的转换表
BBN 计划采用:
1 | |
即:
每个 Host 有自己的转换表,并尽量使用一对一转换。
可以抽象成:
1 | |
这和今天协议中的:
1 | |
思想有一点相似。
不同系统内部可以保留自己的表示,只需要在通信边界统一。
但是 RFC 6 把转换放在:
1 | |
而不是应用或 Host 协议层。
这是后面会发生变化的地方。
三、6-bit Host 的 Case Shifting 问题
Steve Crocker 特别提出:
使用 6-bit code 的站点可能还需要 case shifting。
原因很好理解。
6 bit 只能表示:
1 | |
个不同值。
如果希望同时表示:
- 大写字母;
- 小写字母;
- 数字;
- 标点;
- 控制符;
64 个码位通常并不充裕。
一种常见办法是使用:
1 | |
改变后续字符的解释状态。
概念上类似:
1 | |
问题来了。
如果 ASCII 里的每个字符都直接拥有独立码位,但目标 Host 使用 6-bit shift code,那么:
1 | |
转换回目标 Host 时,可能需要额外插入:
1 | |
字符。
于是:
1 | |
不一定还能得到:
1 | |
而可能变成:
1 | |
这就会产生 RFC 6 所说的:
1 | |
四、对 Overflow 的处理非常“早期互联网”
Bob Kahn 担心:
1 | |
Steve Crocker 的回答不是设计一个复杂的动态缓冲协议,而是:
这种情况应该很少,可以接受 overflow indication,而不是要求系统保证永远不会 overflow。
也就是:
1 | |
和:
1 | |
之间做取舍。
这是一个非常工程化的决定:
1 | |
注意,这不是说:
1 | |
而是:
系统至少要能够明确告诉上层“转换失败/溢出”,而不是默默生成错误结果。
五、Conversion 不只是一个动作,还需要控制位
RFC 6 接着说:
1 | |
也就是说,Host-IMP 通信接口中准备提供:
1 | |
Host 可以告诉 IMP:
1 | |
而不是所有数据都强制转换。
这很重要。
因为网络传输的不可能永远都是文本。
如果对:
1 | |
也进行字符编码转换,数据会直接被破坏。
因此即使设计者倾向 IMP 帮忙做转换,也必须有机制区分:
1 | |
和:
1 | |
六、还考虑过两个 Conversion Bit
RFC 6 接着提出另一种可能:
1 | |
其中一个控制:
1 | |
另一个控制:
1 | |
结合前文,可以理解为希望分别控制:
1 | |
与:
1 | |
这样可以让系统获得更细的控制能力。
RFC 6 原文说:
1 | |
这里的 handle 不是今天 API 里的对象句柄。
更接近:
多一个可以观测、测试或控制传输行为的开关。
七、Host 可以向 IMP 指定九类信息
RFC 6 最重要的一部分,是它列出的:
1 | |
控制能力。
| 序号 | 原文 | 可以怎样理解 |
|---|---|---|
| 1 | Tracing | 要求追踪消息 |
| 2 | Conversion | 是否进行字符转换 |
| 3 | Whether message is for destination IMP or HOST | 消息目标是远端 IMP 还是远端 Host |
| 4 | Send RFNM | RFNM 相关控制 |
| 5 | HOST up or down | Host 上下线状态 |
| 6 | Synchronization | 同步控制 |
| 7 | Format Error Messages | 格式错误相关消息 |
| 8 | Master Link Clear | Link 清理/重置类控制 |
| 9 | Status Requested | 请求状态 |
必须强调:
RFC 6 只列出了这些项目。
它没有为每一种控制定义完整:
- bit layout;
- command code;
- state machine;
- timeout;
- failure semantics。
所以阅读时不能把上表扩写成不存在的正式协议。
八、IMP 也要向 Host 返回七类状态
反方向:
1 | |
RFC 6 列出:
| 序号 | 原文 | 含义 |
|---|---|---|
| 1 | Conversion | 转换相关信息 |
| 2 | REFNM Arrived | RFNM 到达通知 |
| 3 | IMP up or down | IMP 上下线状态 |
| 4 | Synchornization | 同步信息 |
| 5 | Called HOST not Responding | 被呼叫 Host 没有响应 |
| 6 | Format Error | 格式错误 |
| 7 | Status in IMP | IMP 内部状态 |
这里有两个历史文本细节:
原文写的是:
1 | |
但早期 ARPANET 中通用术语是:
1 | |
因此这里一般理解为 RFNM 的拼写变体或错误。
同样:
1 | |
也是原文拼写。
阅读历史 RFC 时最好:
1 | |
而不是:
1 | |
关键机制与工作流程
RFC 6 没有完整流程图。
但根据原文可以重构出三条主要路径。
一、文本消息经过 IMP 字符转换
假设:
1 | |
Host A 希望发送文本。
sequenceDiagram
participant A as HOST A
participant IA as IMP A
participant IB as IMP B
participant B as HOST B
A->>IA: Message + Conversion Request
IA->>IA: Host A Table<br/>Local Code → 8-bit ASCII
IA->>IB: Network Transmission
IB->>IB: Host B Table<br/>8-bit ASCII → Local Code
IB->>B: Converted Message
对于不需要字符转换的数据:
1 | |
则理想行为应该是保持:
1 | |
虽然 RFC 6 没有详细定义二进制模式,但 Conversion Flag 的存在本身就说明:
字符转换不能无条件应用于所有 Message。
二、6-bit Case Shift 的长度扩张
假设目标系统只有 6-bit 字符码。
ASCII 中:
1 | |
可能在目标编码中需要类似:
1 | |
实际编码当然取决于具体 Host。
这里仅用于说明 RFC 6 所担心的结构问题:
1 | |
未必总能:
1 | |
严格一对一映射。
于是目标转换缓冲区可能发生:
flowchart LR
A[ASCII Input<br/>N units]
B[Target Code Conversion]
C{Need Shift Codes?}
D[Output ≈ N units]
E[Output > N units]
F{Buffer Enough?}
G[Success]
H[Overflow Indication]
A --> B --> C
C -->|No| D --> G
C -->|Yes| E --> F
F -->|Yes| G
F -->|No| H
Crocker 倾向:
1 | |
三、Host-IMP 的控制闭环
把 RFC 6 两张清单合起来,可以看到一个很早期的控制闭环:
sequenceDiagram
participant H as HOST
participant I as Local IMP
participant N as Network / Remote Side
H->>I: Data / Control
Note over H,I: Trace / Conversion /<br/>Status Request / Sync / etc.
I->>N: Process / Forward
N-->>I: Network Result / RFNM / Status
I-->>H: RFNM Arrived
I-->>H: Remote HOST not Responding
I-->>H: Format Error
I-->>H: IMP Status
这已经说明:
1 | |
不是:
1 | |
这么简单。
它还要交换:
1 | |
四、RFNM:最容易被现代读者误解的反馈
RFC 6 再次出现:
1 | |
它来自:
Request for Next Message。
早期 ARPANET 的 Link 有一个重要限制:
1 | |
可以抽象成:
sequenceDiagram
participant H as Sending HOST
participant I1 as Source IMP
participant I2 as Destination IMP
participant R as Receiving HOST
H->>I1: Message #1
I1->>I2: Transfer
I2->>R: Deliver Message
I2-->>I1: RFNM
I1-->>H: RFNM Arrived
H->>I1: Message #2
这里一定不能理解成:
1 | |
更不能理解成:
1 | |
它主要是 IMP 网络的传输控制反馈。
RFC 33 后来仍然清楚地解释:
1 | |
五、Tracing 表明网络可观测性已经进入接口设计
RFC 1 就已经有:
1 | |
RFC 6 再次把:
1 | |
放进 Host → IMP 的控制能力。
说明:
网络不是只要能传就够了,还必须能观察发生了什么。
对于一个刚刚准备上线的实验网络:
1 | |
尤其重要。
没有 trace,出现:
1 | |
时,很难判断:
1 | |
六、Host Up / Down 与 IMP Up / Down 是双向健康状态
RFC 6 设计了两个方向:
1 | |
以及:
1 | |
说明两侧需要互相知道:
1 | |
可以表示为:
1 | |
这不是现代:
1 | |
或:
1 | |
但它体现出相同的基础问题:
分布式系统中的参与者必须知道相邻组件是否还具备通信能力。
七、Called HOST not Responding:开始区分网络故障与端点故障
IMP 返回:
1 | |
非常值得注意。
因为:
1 | |
至少可能意味着:
1 | |
如果 IMP 能告诉本地 Host:
1 | |
上层就获得了比:
1 | |
更有价值的故障语义。
现代协议中的:
1 | |
虽然不是从 RFC 6 直接演化来的,但都体现同一类需要:
错误应该尽量告诉调用者“失败发生在哪个层次”。
八、Destination IMP or HOST:控制消息不一定都是用户数据
RFC 6 允许 Host 指定:
1 | |
这意味着同一条 Host-IMP 通信路径上,不只是:
1 | |
还可能存在:
1 | |
这样的消息类型。
于是网络里至少开始出现两类语义:
1 | |
和:
1 | |
虽然 RFC 6 还没有形成现代意义上的完整 control plane / data plane 架构,但这种区别已经开始显现。
九、Synchronization:两侧必须共享通信状态
RFC 6 同时在:
1 | |
和:
1 | |
清单里列出:
1 | |
原文没有继续定义算法。
所以不能编造:
- sequence number;
- handshake;
- clock sync;
- retransmission protocol。
更合理的理解是:
Host 与 IMP 的接口存在需要双方保持一致的通信状态,因此需要显式同步能力。
对任何有状态协议而言,这都是基础问题:
1 | |
十、Format Error:接口本身也要可验证
Host 可以涉及:
1 | |
IMP 也可以返回:
1 | |
这说明接口并不是假设:
1 | |
它需要能够检测:
1 | |
并给出明确错误。
从软件工程角度,这是一种非常重要的边界意识:
内部组件之间的接口,也需要校验输入。
重点概念与术语
Bob Kahn
RFC 6 中的 Bob Kahn 当时在:
1 | |
参与 ARPANET 的 IMP 工作。
今天人们更熟悉他后来与 Vint Cerf 在 TCP/IP 发展中的重要角色。
但阅读 RFC 6 时不要把后来的 TCP/IP 架构倒灌回来。
这里的 Bob Kahn 正在讨论的是:
1 | |
而不是 TCP。
BBN / BB&N
Bolt, Beranek and Newman。
RFC 6 原文写作:
1 | |
BBN 是 ARPANET IMP 的主要承包和开发方。
HOST
连接到 ARPANET 的大型主机系统。
它可以有完全不同的:
- CPU;
- OS;
- 字长;
- 字符集;
- I/O 机制。
IMP
Interface Message Processor(接口报文处理机)。
ARPANET 的分组交换节点。
它既连接本地 Host,也参与 IMP 网络内部的数据转发。
Code Conversion
Code Conversion(代码/字符编码转换)。
RFC 6 讨论的是不同 Host 字符表示之间的转换。
目标网络表示是:
1 | |
8-bit ASCII
RFC 6 使用这个术语描述网络传输字符形式。
几个月后的 RFC 20 会给出更明确的规范:
1 | |
因此不要把 RFC 6 的“8-bit ASCII”直接理解成某种现代独立的 256 字符 ASCII 标准。
更安全的理解是:
当时希望以 8-bit 传输单元承载统一的 ASCII 网络字符表示。
One-for-One Conversion
一对一转换。
理想情况下:
1 | |
以及:
1 | |
但 case shifting 会破坏这一假设。
Case Shifting
通过 Shift 状态扩展有限字符码空间。
类似:
1 | |
使相同的 6-bit 值在不同状态下代表不同字符。
Overflow Indication
字符转换产生扩张以后,输出空间不足时:
1 | |
而不是承诺任何输入都必然转换成功。
Link
ARPANET IMP 网络中的逻辑传输路径。
RFC 6 写:
1 | |
但这一点很快又发生变化。
不能把它当成稳定规范。
Conversion Indicator
Host-IMP 接口中的转换标志。
用于告诉 IMP:
1 | |
RFNM
Request for Next Message。
IMP 网络中的流量反馈机制。
发送 Host 在特定 Link 发出一条消息以后,需要等 RFNM 才能继续在该 Link 发送。
Tracing
要求网络记录或提供消息传输相关状态,用于测量、调试和故障定位。
Synchronization
Host 与 IMP 之间用于维持通信状态一致性的控制概念。
RFC 6 只列出名字,没有定义完整机制。
Format Error
消息格式不符合接口或协议预期时产生的错误状态。
Master Link Clear
RFC 6 列出的一个 Host → IMP 控制项。
原文没有进一步说明具体:
- 编码;
- 作用范围;
- 状态机;
- 恢复流程。
从名称上可以看出它与 Link 清理或复位有关,但不应该在没有原文依据的情况下把它扩写成某个具体现代 reset protocol。
Status Requested
Host 请求 IMP 返回状态信息。
它和 IMP → Host 的:
1 | |
形成自然的一问一答关系。
需要特别注意的点
1. RFC 6 不是一篇正式协议规范
它的标题就是:
1 | |
本质是一份谈话记录。
它没有定义完整:
1 | |
所以不能把清单中的九个 Host→IMP 项目直接当成最终协议命令集。
2. RFC 100 已经把 RFC 6 标为 Obsolete
1971 年的 RFC 100 明确记录:
1 | |
因此它只有历史和设计研究价值。
3. 五位 Link Field 与前后 RFC 都不一致
这是 RFC 6 最值得注意的技术细节之一。
RFC 1:
1 | |
RFC 6:
1 | |
RFC 7:
1 | |
因此:
不要为了让历史看起来整齐,就把 RFC 6 的 five bit 擅自改成 eight bit。
这段不一致本身就是事实。
它证明接口设计仍在变。
4. REFNM 是原文写法,但概念应理解为 RFNM
RFC 6 的 IMP→Host 清单写:
1 | |
早期其他 RFC 使用的正式缩写是:
1 | |
文章中应该保留原文拼写事实,同时使用 RFNM 解释概念。
5. Synchornization 也是原文拼写
不要根据这个单词去创造一个特殊的:
1 | |
它只是早期工作文档里的拼写错误。
6. RFC 6 中的 8-bit ASCII 不是现代“扩展 ASCII”
RFC 6 的重点是:
1 | |
后来的 RFC 20 才明确提出:
1 | |
所以不要把 RFC 6 的:
1 | |
解释成后来各种互不兼容的“Extended ASCII Code Page”。
7. IMP 做字符转换的方向后来并没有成为互联网的普遍核心原则
RFC 6 记录 BBN 准备让 IMP:
1 | |
后来网络协议设计逐渐更倾向:
1 | |
RFC 33 在 1970 年甚至明确强调:
1 | |
也就是说:
网络应尽量避免对上层字符集和编程语言强加限制。
所以 RFC 6 反映的是一条被认真探索过、但后来没有成为互联网主线的设计方向。
8. 但“Canonical Wire Representation”这个问题并没有消失
不能因为 IMP 不再负责字符转换,就认为 RFC 6 的问题已经消失。
今天各种协议仍然需要解决:
1 | |
例如:
1 | |
变化的是:
转换通常放在哪一层。
9. Case Shift Overflow 是数据表示问题,不是网络拥塞
RFC 6 的:
1 | |
来源是字符转换后数据可能扩张。
它不是:
1 | |
不要把两个完全不同的“overflow”混在一起。
10. Destination IMP or HOST 不等于现代 IP 的 Router Alert
它说明消息可能面向:
1 | |
或者:
1 | |
但它不是:
- ICMP;
- Router Alert;
- SNMP;
- SDN control message;
中的任何一种。
只能在抽象设计层做类比。
11. HOST up or down 也不是现代健康检查 API
RFC 6 只有一个状态概念列表。
它没有现代:
1 | |
这些成熟语义。
不能过度现代化。
12. Master Link Clear 的详细语义原文没有给
这个名字很诱人,很容易让人脑补:
1 | |
但 RFC 6 没有明确定义。
严谨阅读时只能说:
它显然属于 Link 清理/控制类能力,但具体行为需要结合其他当时 IMP 规范研究,不能仅凭 RFC 6 确定。
13. RFC 6 最后的“我向 Bob 总结了 Notes 1、2、3”非常有历史感
原文最后一句:
1 | |
这提醒我们:
RFC 当时还不是一个:
1 | |
就能随时阅读的全球数据库。
很多技术信息仍通过:
1 | |
传播。
RFC 本身正是在逐步解决这个知识同步问题。
从今天的视角重新看这篇 RFC
RFC 6 已经完全过时。
但它讨论的几个架构问题一点都没有消失。
一、网络层到底应该有多“聪明”
RFC 6 的一个核心提议是:
1 | |
这意味着网络内部了解较多上层语义。
今天设计分布式系统时仍然经常遇到类似争论:
1 | |
基本取舍仍然是:
1 | |
对比:
1 | |
RFC 6 就是在非常早期面对这条轴。
二、Representation 与 Transport 应该分层
字符转换最终没有成为 IMP 的核心职责,原因之一就是:
1 | |
和:
1 | |
是不同问题。
今天更常见:
flowchart TD
A[Application Data Model]
B[Serialization / Encoding]
C[Transport Protocol]
D[Network]
E[Transport Protocol]
F[Decode / Deserialize]
G[Remote Application Model]
A --> B --> C --> D --> E --> F --> G
而不是:
1 | |
这也是互联网后来保持通用性的关键之一。
三、但 Wire Format 必须统一
虽然转换不一定属于网络内部,RFC 6 指出的基础问题永远存在:
1 | |
所以协议必须定义:
1 | |
比如网络字节序:
1 | |
或者文本协议:
1 | |
或者结构化序列化:
1 | |
本质上都是在解决:
不同机器怎样共享一种中立表示。
四、转换操作必须显式,而不是偷偷进行
RFC 6 给 Conversion 单独设计 bit,是很重要的。
因为中间层如果“猜测”数据是不是文本然后自动转换:
1 | |
很容易被破坏。
现代系统里也是一样:
1 | |
这些字段的价值,就是让转换:
1 | |
而不是:
1 | |
五、任何会改变 Payload 长度的转换都要考虑资源模型
Case shifting 导致的 overflow 今天非常熟悉。
任何转换都可能:
1 | |
例如:
1 | |
系统必须明确:
1 | |
RFC 6 的答案很简单:
1 | |
现代系统会做得更成熟,但问题完全一样。
六、健康状态应该是显式协议语义
RFC 6 已经列出:
1 | |
今天我们知道,一个布尔值往往不够。
所以状态发展成:
1 | |
但核心原则没变:
系统状态不能只靠调用失败去猜。
七、错误要分层
RFC 6 的 IMP 可以告诉 Host:
1 | |
这比统一返回:
1 | |
有用得多。
现代 API 设计也应该尽量区分:
1 | |
否则调用者无法正确:
1 | |
八、控制面信息和用户数据最终会自然分离
RFC 6 同一接口中已经出现:
1 | |
这些信息具有完全不同语义。
当系统变复杂以后,往往自然形成:
1 | |
RFC 6 还没有这些成熟名词,但已经出现了这些职责的胚胎。
九、可观测性从设计一开始就应该存在
Tracing 不是网络上线以后再补的。
RFC 1 和 RFC 6 都在最早接口设计阶段讨论 trace。
这个原则对现代系统尤其重要:
1 | |
十、不要在基础设施层绑定过多上层语义
RFC 6 设想 IMP 了解 Host 字符集。
这在四台、十几台机器的小型研究网络里看起来很实用:
1 | |
但如果网络扩展到:
1 | |
网络核心继续理解所有表示语义会变得不可扩展。
这也是互联网后来趋向:
1 | |
的一个重要工程理由。
不能说 RFC 6 单独导致了这个结论。
但从它出发,可以非常直观地看到:
小型网络里的便利功能,扩展到全球网络时可能变成核心耦合。
十一、RFC 6 是“边界还没定”的最好样本
现代教材会把层次画得很漂亮:
1 | |
看起来像这些边界一开始就存在。
RFC 6 告诉我们完全不是这样。
真实历史是:
1 | |
分层不是从天上掉下来的。
它是一次次工程冲突以后形成的。
这篇 RFC 带来的影响
RFC 6 没有留下一个今天继续使用的协议。
1971 年它就被 RFC 100 明确标记为:
1 | |
但它在早期协议演化链里有几个很重要的作用。
一、它记录了 BBN 与 Host Software 团队之间的接口协商
RFC 6 的背景是:
1 | |
和:
1 | |
之间的技术同步。
这两边分别更关注:
1 | |
和:
1 | |
RFC 6 就处在接口边界。
任何大型系统都有类似问题:
1 | |
而真正容易出问题的往往正是:
1 | |
RFC 6 的价值之一,就是把口头讨论写进公共记录,让其他站点也能看到双方正在考虑什么。
二、它直接体现了 Host-IMP Interface 仍在变化
从:
1 | |
到:
1 | |
再到:
1 | |
字段和职责都在变化。
RFC 7 随后继续系统讨论:
1 | |
并给出一个 16-bit Host heading:
1 | |
所以 RFC 6 可以看成:
正式写 Host-IMP Interface 文档之前的一次设计同步快照。
三、字符表示问题很快继续演化
RFC 6 讨论:
1 | |
RFC 7 又具体提到:
1 | |
到 1969 年 10 月,RFC 20:
ASCII format for Network Interchange
开始给出更明确的网络字符约定:
1 | |
并指定它用于:
1 | |
这说明问题逐渐从:
1 | |
转向:
1 | |
职责开始向端系统协议上移。
四、到 RFC 33,协议设计明显更强调通用性
1970 年的 RFC 33:
New HOST-HOST Protocol
明确写出一条非常值得对照 RFC 6 阅读的原则:
1 | |
设计者希望尽量不给网络用户强加:
1 | |
等限制。
这和 RFC 6 中:
1 | |
形成很有意思的演化对照。
不是说二者完全冲突。
而是说明:
早期设计正在逐渐把“网络必须理解什么”重新收敛。
五、Host-IMP 控制接口最终继续发展成更系统的网络接口
RFC 6 只有几个控制项名字。
后续文档开始详细讨论:
- Message leader;
- Link;
- RFNM;
- Host status;
- Control Message;
- Handler;
- Network Program;
- Buffering;
- Flow control。
后来 Host-Host Protocol 又发展出:
1 | |
所以 RFC 6 不是一个孤立的小纸条。
它记录的是:
1 | |
过程中非常早的一步。
六、Bob Kahn 在这里出现时,互联网还远没有 TCP/IP
今天看到:
1 | |
很容易马上想到:
1 | |
但 RFC 6 发布于:
1 | |
这时 ARPANET 第一条著名 Host-to-Host 通信甚至还没有发生。
RFC 6 里的 Kahn 正在讨论的是:
1 | |
这非常能说明互联网协议不是突然从:
1 | |
开始的。
在 TCP/IP 之前,已经有很多年:
1 | |
七、它最重要的遗产是“边界问题必须被显式讨论”
如果一定要从 RFC 6 提炼一个最有现代价值的结论,那不是:
1 | |
也不是:
1 | |
这些都已经过时。
更值得保留的是这几个问题:
1 | |
这就是架构设计真正困难的地方。
总结
RFC 6 只有短短一页。
它甚至不是一篇真正完成的协议设计,只是一份:
Conversation with Bob Kahn。
但这一页里,几乎全是“系统边界”问题。
首先是字符表示。
不同 Host 使用:
1 | |
字符码。
BBN 准备让 IMP 把这些表示转换成:
1 | |
用于网络传输,再由目的 IMP 转回目标 Host 的本地表示。
转换计划采用:
1 | |
但 6-bit code 的:
1 | |
可能导致输出膨胀和 overflow。
Crocker 的建议是:
1 | |
接着是 Host-IMP Interface。
RFC 6 列出 Host 可以向 IMP 指定:
1 | |
而 IMP 可以告诉 Host:
1 | |
这些内容意味着 Host 与 IMP 的接口从一开始就不是简单的:
1 | |
它还需要:
1 | |
更值得注意的是:
RFC 6 写:
1 | |
RFC 1 却是:
1 | |
随后 RFC 7 又回到:
1 | |
这不是一个应该被“修正”的尴尬。
它正是 RFC 6 最有价值的地方:
我们正在看到协议边界实时变化,而不是只看到后来整理好的最终答案。
字符转换也经历同样变化。
RFC 6 的方向是:
1 | |
几个月后的 RFC 20 开始明确网络交换用:
1 | |
到 RFC 33,Host-Host Protocol 更进一步强调:
1 | |
从今天回头看,互联网最终更接近:
1 | |
而不是:
1 | |
所以 RFC 6 真正值得软件开发者带走的,并不是它的某个字段。
而是一组直到今天仍然存在的架构问题:
1 | |
1969 年,Steve Crocker 和 Bob Kahn 讨论这些问题时,ARPANET 甚至还没有完成第一次著名的 Host-to-Host 登录。
五十多年以后,我们在设计:
1 | |
时,依然在反复问同样的问题。
变化的是技术。
没怎么变化的是:
边界放在哪里。