RFC 9 带读:Host Software——从 Logical Link 到 Service Call,早期 Host-Host 协议开始进入操作系统
前言
RFC 9 的标题再次回到一个看起来很普通的名字:Host Software。
但如果把它和前面的 RFC 1、RFC 2、RFC 7、RFC 8 放在一起看,会发现它已经明显进入了另一个阶段。
RFC 1 还在讨论:
1 | |
RFC 7 开始讨论:
1 | |
RFC 8 则把:
1 | |
重新整理成一套阶段性的功能规格。
到了 RFC 9,Gérard Deloche 把重点进一步推向:
Host-Host Protocol 到底怎样映射成操作系统可以调用、可以管理、可以存储状态的一套软件结构?
所以 RFC 9 最值得软件开发者读的地方,不是它又一次定义了 Link,而是它第一次比较系统地把三层东西连了起来:
1 | |
这已经非常接近今天我们熟悉的:
1 | |
RFC 9 还把此前的 32 条 Link 扩展成:
1 | |
并明确区分:
1 | |
同时,它给出了:
1 | |
一组非常早期的 Network Service Calls(网络服务调用)。
也就是说,一个普通 User Program 不再需要理解:
1 | |
而可以通过操作系统提供的服务接口来请求:
1 | |
这就是 RFC 9 真正的变化:
网络正在从“硬件设施”变成“操作系统提供给应用程序的公共服务”。
不过,这套设计并没有活很久。
1971 年的 RFC 100 对早期 Host/Host Protocol 的总结非常明确:
1 | |
随后 RFC 33 会建立一套更成熟的:
1 | |
模型。
所以今天阅读 RFC 9 的正确姿势不是“学习一套旧协议”。
而是观察:
一个网络接口怎样从 Link 和控制字符,逐渐长成应用可以调用的操作系统网络 API。
RFC 基本信息
| 项目 | 内容 |
|---|---|
| RFC | RFC 9 |
| 官方英文标题 | Host Software |
| 作者 | G. Deloche(Gérard Deloche) |
| 所属机构 | UCLA |
| 原文日期 | 1 May 1969 |
| RFC Editor 当前状态 | Unknown |
| Stream | Legacy |
| 后续内容关系 | RFC 11 supersedes RFC 9 |
| 所属历史阶段 | 早期 Host/Host Protocol |
| RFC Editor 信息页 | RFC 9: Host Software |
| RFC 原文 | RFC 9 原文 |
RFC 9 的日期比 RFC 8 还早
RFC 9 手稿首页是:
1 | |
而 RFC 8 是:
1 | |
所以再次可以看到:
1 | |
早期 RFC 的编号与起草、完成、分发、编号分配并不是今天软件版本号那样严格同步。
RFC 9 的历史状态不是简单一句 Obsolete
1971 年的 RFC 100 在索引中把 RFC 9 标记为:
1 | |
也就是它仍然被归类到 Host/Host Protocol 的连接建立、终止与控制消息相关主题中。
但 RFC 100 在正文里又明确说明:
1 | |
因此今天比较准确的理解是:
1 | |
RFC 9 同样来自早期扫描材料
RFC Editor 当前把 RFC 9 作为:
1 | |
展示。
当前网页上的全文内容来自对扫描页的整理和转写,其中一些图形和控制字符示例仍然比较难读。
因此本文只对原文明确可以确认的字段、流程、API 和数据结构做解释。
对于 Figure 3 中某些已经难以准确辨认的控制字符,不会为了“补齐协议”擅自创造定义。
为什么会有这篇 RFC
一、Link 已经有了,但应用程序怎么用还不够清楚
在 RFC 8 中,我们已经看到:
1 | |
用户通过远程登录,再启动应用。
但真正写一个应用程序时,还有一堆非常现实的问题:
1 | |
如果这些事情全部让应用自己做,那么:
1 | |
这显然不可接受。
所以 RFC 9 的目标之一,就是把这些复杂度收进:
1 | |
再向用户提供:
1 | |
二、网络开始成为操作系统的公共能力
RFC 9 在 Service Calls 一章明确说:
User Program 通过:
1 | |
访问网络设施。
服务调用会:
1 | |
由 Monitor 解释并执行,然后把控制权返回用户程序。
这个结构可以画成:
sequenceDiagram
participant U as User Program
participant M as Monitor Service Routine
participant N as Network Program
participant I as IMP / Network
U->>M: Service Call
M->>N: Execute Network Operation
N->>I: Host-Host / IMP Interaction
I-->>N: Result / Control
N-->>M: Status
M-->>U: Return
这已经非常明确地表现出:
1 | |
而必须通过:
1 | |
三、256 Link 带来更大的状态管理问题
RFC 8 还是:
1 | |
RFC 9 已经改成:
1 | |
这不是简单把字段变大。
它马上带来:
1 | |
所以 RFC 9 必须引入:
1 | |
换句话说:
协议状态一旦变复杂,数据结构设计就会成为协议实现的一部分。
四、连接模型也进一步从“Link”变成“用户资源”
RFC 9 明确区分两种 ID:
1 | |
和:
1 | |
也就是说:
1 | |
不一定等于:
1 | |
这非常重要。
它开始建立一个典型的软件抽象:
1 | |
现代开发者对此非常熟悉:
1 | |
RFC 9 已经开始沿着这个方向走。
核心内容
RFC 9 的目录包括:
1 | |
整体结构非常像今天一份网络子系统设计文档:
flowchart TD
P[Protocol Model]
A[Application API]
D[Kernel / Monitor Data Structures]
I[Network Program Implementation]
P --> A
A --> D
D --> I
一、IMP 被看成 Local Center 与 Trunk Network 的接口
RFC 9 先从 IMP 讲起。
原文说:
1 | |
本地一个 IMP 最多可以服务:
1 | |
而对于其中每一个 Host:
1 | |
因此从 Host 的视角:
1 | |
二、256 Logical Links,但并不是全都能同时激活
RFC 9 紧接着指出:
1 | |
也就是说:
1 | |
如果一个 Local Center 有:
1 | |
则这:
1 | |
需要在:
1 | |
之间共享。
这非常值得注意。
因为它已经明确区分:
1 | |
和:
1 | |
现代系统也经常如此:
1 | |
三、Link 0 仍然是 Control Link
256 条 Link 里:
1 | |
有特殊地位。
用于:
1 | |
其余:
1 | |
分成:
1 | |
两种使用方式。
四、Primary Link:每次 Host-Host 对话的第一条 Link
RFC 9 明确说:
1 | |
它是:
1 | |
具有这些特征:
1 | |
这里最重要的一个数字是:
1 | |
这让 Primary Link 的定位非常明确:
它本质上就是面向人类终端交互的低速控制通道。
五、Primary Link 的主要用途是远程 Login
RFC 9 明确举例:
1 | |
也就是说:
1 | |
首先不是高吞吐数据连接,而是:
1 | |
的交互入口。
流程:
1 | |
这也直接埋下了这套协议后来被放弃的核心原因。
因为:
如果所有 inter-host communication 都依赖“先登录远端 Host”,就很难支持真正机器到机器的自动进程通信。
RFC 100 后来正是因此批评这套协议“不够强”。
六、Auxiliary Link:大容量数据传输
Auxiliary Link 用于:
1 | |
它:
1 | |
并且必须满足:
1 | |
同时可以传:
1 | |
所以:
flowchart LR
A[HOST X]
B[HOST Y]
A <-->|Primary<br/>TTY / Control| B
A <-->|Auxiliary<br/>Bulk Binary / Character Data| B
这比 RFC 8 的:
1 | |
命名更系统一些。
七、Primary / Auxiliary 已经形成“控制 + 数据”的组合
一个典型远程应用会:
1 | |
然后:
1 | |
所以整个 Session 不再是一条 Link。
而是一组有角色关系的 Link。
这已经开始从:
1 | |
转向:
1 | |
八、通用 Link 建立流程
RFC 9 定义:
1. Establish Primary Link
通过:
1 | |
建立 Primary Link。
建立成功后:
1 | |
远端 Host 等待标准 Login Procedure。
2. Login Sequence
通过 Primary Link:
1 | |
完成远端登录。
3. Establish Auxiliary Link
需要:
1 | |
仍然通过:
1 | |
完成控制。
4. Send / Receive Text over Auxiliary Link
之后批量数据才走:
1 | |
九、RFC 9 的示例使用 HOST ID 8 和 5
原文示例设:
1 | |
这个数字后面用于决定:
1 | |
由于:
1 | |
所以:
1 | |
负责主动建立。
这延续了前面 RFC 中:
1 | |
的做法。
十、Control Link 上已经开始出现真正的控制消息格式
RFC 9 的 Figure 3 给出了一个具体建链示例。
其中可以明确识别:
1 | |
用于:
1 | |
随后消息中包含:
1 | |
表示:
1 | |
以及:
1 | |
和:
1 | |
远端返回:
1 | |
表示:
1 | |
确认 Link 建立。
也可能返回:
1 | |
表示:
1 | |
RFC 9 还说明:
1 | |
这已经很像一个非常原始的:
1 | |
协议。
十一、连续 NAK 还会触发 Emergency Procedure
RFC 9 说明:
如果某个请求被 NAK:
1 | |
可以重复发送消息:
1 | |
如果连续发生太多次 NAK,则:
1 | |
会启动。
原文扫描件中关于具体阈值和某些细节不够清晰,因此不能把它写成一个现代精确重试算法。
但可以确认的是:
RFC 9 已经开始明确考虑“请求失败—重试—连续失败升级处理”的控制流。
十二、Auxiliary Link 示例使用 Link 25
RFC 9 的例子中:
1 | |
在登录远端以后请求一个应用程序:
1 | |
原文说明:
1 | |
随后双方建立:
1 | |
然后:
1 | |
这说明 Auxiliary Link 的典型用途就是:
1 | |
十三、最后还要显式 Free Links
会话结束时:
1 | |
释放之前建立的 Link。
RFC 9 示例里可以辨认出:
1 | |
相关控制内容。
也就是说:
1 | |
是一种需要明确:
1 | |
的资源。
关键机制与工作流程
RFC 9 真正比 RFC 8 更进一步的地方,是:
它开始把协议过程直接暴露成操作系统 API。
一、Network Service Call 是怎样工作的
原文说明:
1 | |
服务调用:
1 | |
由 Monitor 完成操作,再:
1 | |
流程:
sequenceDiagram
participant U as User Program
participant M as Monitor
participant N as Network Program
participant R as Remote HOST
U->>M: Network Service Call
M->>N: Parse / Execute
N->>R: Host-Host Protocol
R-->>N: Control / Data
N-->>M: Result
M-->>U: Return / Interrupt / Status
这就是:
1 | |
的早期形态。
二、OPENPRIM:打开 Primary Link
RFC 9 给出的第一类调用是:
1 | |
可以从原文辨认出的主要参数包括:
1 | |
可以整理为:
| 参数 | 含义 |
|---|---|
| PRIMID | 用户给这条 Primary Link 的标识 |
| HOSTID | Remote Host ID |
| BUFFADDR | Incoming Message Buffer 地址 |
| INTPT-CODE | 收到消息导致用户程序被中断时返回的 Code |
| OPT | 可选参数 |
OPT 可能包括:
1 | |
这里非常值得注意:
用户自己提供一个
PRIMID,而 Network Program 内部会再分配真正的 Network Link ID。
也就是说:
1 | |
三、OPENAUX:打开 Auxiliary Link
第二个调用:
1 | |
原文可以确认的核心参数包括:
1 | |
其中:
1 | |
引用:
1 | |
所以 Auxiliary Link 不是完全独立资源。
它属于某个已有 Primary Link 的上下文。
可以理解成:
1 | |
四、TRANSLINK:通过某条 Link 传输
RFC 9 给出:
1 | |
含义大致是:
1 | |
这个形式已经非常像现代:
1 | |
五、MODIFLINK:修改 Link 属性
RFC 9 还定义:
1 | |
用于修改已经存在 Link 的一些行为。
虽然原文没有发展成现代成熟 Option Model,但它表达了一个非常重要的 API 思路:
连接建立后仍然可以调整其运行属性。
六、CLOSSLINK:关闭 Link
还有:
1 | |
可以关闭:
1 | |
并且某些 Option 可以:
1 | |
这已经是明确的:
1 | |
七、这些 Service Calls 已经构成一个早期 Network API
如果把原文压缩成现代伪接口:
1 | |
非常值得强调:
这不是说 RFC 9 发明了 BSD Socket API。
两者没有直接继承关系。
但从软件抽象角度看,RFC 9 已经非常清楚地走向:
1 | |
这种系统 API 模式。
八、协议状态需要三张表管理
RFC 9 的 Data Structure 一章特别重要。
它说:
1 | |
分别是:
1 | |
这三张表分别从三个查询维度组织状态。
九、HOST Table:以 Remote Host 为索引的 Link Bitmap
HOST Table 是:
1 | |
对于一个给定 Remote Host:
1 | |
分别表示:
1 | |
其中:
1 | |
如果 Link 已经占用:
1 | |
于是:
1 | |
就能快速表示:
1 | |
更重要的是,RFC 9 还要求:
1 | |
对应 IMP 的实际并发 Link 限制。
十、这是一个非常早的 Bitmap Resource Allocator
从现代视角:
1 | |
就是非常典型的:
1 | |
查找第一个:
1 | |
就可以找到:
1 | |
十一、LINK Table:按实际正在使用的 Link 存状态
LINK Table:
1 | |
也就是说它不是为全部 256 × Host 预分配完整对象。
只为:
1 | |
创建条目。
RFC 9 还说明:
1 | |
换句话说:
1 | |
已经是一种典型的运行时状态索引结构。
十二、USER Table:从 User 反查其所有 Link
USER Table:
1 | |
包含:
1 | |
并且:
1 | |
于是三张表分别支持:
1 | |
可以画成:
flowchart TD
H[HOST Table<br/>Remote HOST → Free/Used Link Bitmap]
L[LINK Table<br/>Network Link → Runtime State]
U[USER Table<br/>User → Link Set]
H --> L
U --> L
十三、一个 Link 有两个 Identification
RFC 9 特别强调:
1 | |
User Identification
由用户在:
1 | |
中传入。
Network Identification
由:
1 | |
分配。
这可以表示为:
1 | |
这已经是非常明确的:
1 | |
映射。
十四、OPENPRIM 内部到底怎么执行
RFC 9 最后还具体解释:
1 | |
的内部过程。
假设:
1 | |
第一步:HOSTID 作为 HOST Table Index
1 | |
第二步:找第一个空闲 Link
搜索:
1 | |
设找到:
1 | |
第三步:形成 Network Link Identity
1 | |
共同确定:
1 | |
第四步:Hash 到 LINK Table
用:
1 | |
作为 Hash 输入,创建新的 LINK Table Section。
第五步:先标记“我们已经发起打开”
条目里会记录:
1 | |
但此时:
1 | |
十五、ACK 回来以后才能把 Link 标为 Established
RFC 9 明确说:
只有当:
1 | |
以后:
1 | |
然后才设置:
1 | |
这个细节非常重要。
状态不是:
1 | |
而是:
1 | |
可以画成:
stateDiagram-v2
[*] --> Free
Free --> Allocated: 选择空闲 Link
Allocated --> OpenRequested: 发送 Link 0 Request
OpenRequested --> Established: Remote ACK
OpenRequested --> Failed: NAK / Error
Established --> Closing: CLOSSLINK
Closing --> Free: Release
十六、RFC 9 已经在数据结构里保存协议状态机
LINK Table 中不是只存:
1 | |
还要保存:
1 | |
这意味着:
协议状态机最终一定要落到某种可查询的运行时状态对象里。
这是 RFC 9 非常现代的一点。
重点概念与术语
HOST-HOST Protocol
两个 Host 之间用于建立连接、传输数据、维护状态和终止连接的协议。
注意:
RFC 9 属于一套非常早期的 Host-Host Protocol。
它不是后来成熟的 NCP 最终协议。
Logical Link
IMP 向 Host 暴露的逻辑通信路径。
RFC 9:
1 | |
但最多:
1 | |
Control Link
1 | |
专门用于:
1 | |
Primary Link
Host-Host 会话建立的第一条 Link。
特点:
1 | |
Auxiliary Link
与 Primary Link 并行建立的额外数据 Link。
用于:
1 | |
Pre-login State
Primary Link 已经建立,但远端 OS 登录尚未完成的状态。
ENQ
Enquiry。
RFC 9 示例中用于发起 Link Establishment 的控制字符之一。
ACK
Positive Acknowledgement。
表示控制请求被接受。
NAK
Negative Acknowledgement。
表示请求被拒绝。
RFC 9 还允许伴随原因字符。
HOST Identification Number
Host 的网络编号。
RFC 9 用它作为:
1 | |
的确定性排序依据。
Network Service Call
User Program 向 Monitor / Network Program 请求网络功能的系统级调用。
OPENPRIM
打开 Primary Link 的 Service Call。
OPENAUX
基于已有 Primary Link 打开 Auxiliary Link。
TRANSLINK
在指定 Link 上发送数据。
MODIFLINK
修改 Link 的 Option / 行为。
CLOSSLINK
关闭一条或一组 Link。
Monitor Service Routine
处理 User Program Service Call 的操作系统特权例程。
HOST Table
按 Remote Host 组织:
1 | |
LINK Table
存储所有:
1 | |
并通过 Network Link ID Hash 定位。
USER Table
以:
1 | |
为索引,记录该用户正在使用的 Link。
User Link ID
用户程序自己使用的逻辑标识。
Network Link ID
Network Program 实际分配的网络 Link 标识。
Hashing
RFC 9 明确使用 Hash 技术来动态检索:
1 | |
这说明网络状态数据结构已经开始考虑:
1 | |
而不只是线性扫描。
URSA
RFC 9 示例中假设存在于 HOST(Y) 的:
1 | |
它只是示例应用名。
不应把它当成后来互联网标准协议。
需要特别注意的点
1. RFC 9 不是后来正式 NCP 的最终协议
这是最重要的一点。
RFC 100 明确说:
1 | |
这套协议后来被整体放弃。
2. RFC 11 supersedes RFC 9
RFC 100 明确记录:
1 | |
RFC 11 会更完整地把:
1 | |
和 UCLA 的:
1 | |
结合起来。
所以 RFC 9 更像:
1 | |
RFC 11 更接近:
1 | |
3. RFC 11 后来又被 RFC 33 正式废弃
RFC Editor 当前对 RFC 11 明确标注:
1 | |
RFC 33:
1 | |
代表 1969 年 12 月网络会议后的一次大重构。
4. 旧协议被放弃的核心原因非常重要
RFC 100 对这套协议失败原因写得非常清楚:
1 | |
也就是说:
Host A 的程序如果想和 Host B 的程序通信,模型高度依赖“先像一个 TTY 用户一样登录 Host B”。
这对于:
1 | |
还算自然。
但对于:
1 | |
就过于受限。
这是早期网络从:
1 | |
转向:
1 | |
的一个关键转折。
5. Primary Link 不是 TCP Control Connection
Primary Link 的远端 Echo、Break Character、Login 和 <20 chars/s 都带有强烈的 TTY 语义。
不能拿 TCP 的通用字节流直接替换理解。
6. Auxiliary Link 也不是 FTP Data Connection
它确实有:
1 | |
两类 Link。
很容易联想到后来 FTP 的:
1 | |
但历史上不能直接说:
1 | |
只能说两者都面对:
1 | |
这一类共同问题。
7. 256 Link 不等于 256 个并发连接
RFC 9 明确:
1 | |
但:
1 | |
所以:
1 | |
8. 64 Active Link 限制甚至要在多个 Local Host 之间共享
如果一台 IMP 挂:
1 | |
那么:
1 | |
是这些 Host 一起共享的。
这意味着:
IMP 是一个真实的共享受限资源,不是无限能力的逻辑交换机。
9. RFC 9 的 ACK / NAK 不是现代 TCP ACK
这些:
1 | |
是 Host-Host Control Message 的协议控制字符。
和:
1 | |
不是一个层次。
更和 TCP Sequence ACK 不同。
10. RFNM 仍然是 IMP 层的 Flow Control
虽然 RFC 9 重点不在 RFNM,但 Network Program 仍负责:
1 | |
必须继续区分:
1 | |
11. User Link ID 和 Network Link ID 是两个不同命名空间
这是 RFC 9 最值得注意的软件设计细节之一。
不要假设:
1 | |
就表示:
1 | |
Network Program 会做映射。
12. HOST Table 的 256-bit Bitmap 只是当时实现方案
不要把它理解成协议线上格式。
它属于:
1 | |
而不是:
1 | |
13. Hash Table 也属于实现层
RFC 9 对:
1 | |
提出 Hash。
这是:
1 | |
不是远端 Host 必须遵循的 wire protocol。
14. Service Call 名称不是现代标准 API
1 | |
是 RFC 9 这套设计的接口。
它们不是 POSIX,也不是后来 BSD Socket。
15. OPEN 被调用以后并不代表网络状态已经完全建立
RFC 9 的内部描述强调:
1 | |
所以:
1 | |
这个区别很重要。
16. 原文 Figure 3 的某些控制字符不宜过度解码
当前扫描和机器转写中:
1 | |
等核心含义可以确认。
但部分括号字符、ASCII 数字表达和 Option 示例已经不够清晰。
写博客时保留可以验证的协议结构即可。
17. URSA 是示例,不是标准应用协议
RFC 9 只是说:
1 | |
不能围绕它脑补一套不存在的标准。
18. 这套协议最大的问题不是“字段设计不漂亮”,而是抽象层次错了
RFC 100 并没有说:
1 | |
而是指出:
1 | |
这说明真正导致协议被推倒重来的,是:
通信模型本身不够通用。
从今天的视角重新看这篇 RFC
一、RFC 9 很像“网络 API 从零长出来”的现场记录
如果只看前面的 RFC,网络还是:
1 | |
RFC 9 开始出现:
1 | |
这已经是应用开发者熟悉的资源 API。
一个网络能力只有变成:
1 | |
才真正能被大规模软件使用。
二、协议对象应该有稳定的 User Handle
RFC 9 的双 ID:
1 | |
是很现代的抽象。
为什么不能把底层 Network Link ID 直接给应用?
因为底层资源:
1 | |
应用更适合看到:
1 | |
现代很多系统都这样:
1 | |
三、API 和协议不应该是一回事
User Program 调:
1 | |
Network Program 内部却要做:
1 | |
这非常清晰地说明:
1 | |
而不是:
1 | |
四、一个 API Call 背后可能是异步状态机
OPENPRIM 看起来只是一个函数。
实际却经历:
1 | |
现代网络编程也是一样:
1 | |
表面是一句代码。
背后可能涉及:
1 | |
软件接口的价值之一就是隐藏这些过程。
五、资源管理需要多个索引视角
RFC 9 用:
1 | |
分别支持不同查询。
这是一种非常成熟的数据建模意识。
现代网络服务也经常同时需要:
1 | |
一个巨大 Map 往往不能高效支持所有访问模式。
六、Bitmap 很适合有限 ID 空间的 Resource Allocation
RFC 9 的:
1 | |
是一种极自然的数据结构。
今天仍会用在:
- Port Pool;
- CPU Affinity;
- Memory Page;
- ID Slot;
- File Descriptor Set;
- Resource Flags。
如果资源 ID 空间:
1 | |
Bitmap 通常非常高效。
七、Hash Table 适合稀疏 Active State
另一方面:
1 | |
虽然 ID 空间大,但真正 Active Link 只有少数。
所以 LINK Table 只为:
1 | |
创建 Section,并使用 Hash 查找。
这和:
1 | |
的现代实现思路完全一致。
八、Logical Capacity 与 Physical Capacity 必须分开建模
RFC 9:
1 | |
但同时:
1 | |
这是一个非常重要的资源设计模式:
1 | |
现代系统也会区分:
1 | |
九、Control Plane 与 Data Plane 分离又向前走了一步
RFC 9:
1 | |
再加上:
1 | |
可以看到控制路径越来越集中。
这种结构在系统复杂以后几乎是必然的。
十、但“所有通信先 Login”暴露了过度绑定人类交互模型的问题
这是 RFC 9 最值得现代软件架构师反思的一点。
最初需求是:
1 | |
于是协议自然设计成:
1 | |
但当网络用途扩展成:
1 | |
这个模型马上变得很笨重。
这就是一个典型的:
以第一批 Use Case 为中心设计的抽象,可能无法覆盖下一代 Use Case。
十一、应该把“身份认证”与“连接建立”解耦到合适程度
RFC 9 的问题之一,是:
1 | |
后来网络协议演化会逐渐把:
1 | |
分成更独立的层次。
这让机器通信不必先模拟:
1 | |
十二、Primary / Auxiliary 模型说明一个 Session 可以由多个 Stream 构成
虽然 RFC 9 这套设计最终被放弃,但它触及了一个今天仍然重要的问题:
1 | |
现代协议中:
1 | |
都会处理类似需求。
区别是现代协议把这种 Multiplexing 做得更加通用。
十三、状态机应该落到数据结构,不应该只存在文档里
RFC 9 中:
1 | |
这些字段表明:
1 | |
必须被编码成:
1 | |
否则程序无法:
1 | |
这对今天设计任何有状态协议都成立。
十四、网络功能一旦进入 OS,就必须有权限和生命周期边界
RFC 9 的 Service Call 通过:
1 | |
进入特权层。
这说明网络资源不是普通用户程序随便操纵的硬件。
操作系统需要管理:
1 | |
今天 Socket / Network Namespace / Capability / Firewall 都是在更成熟地回答类似问题。
十五、RFC 9 的失败本身比成功更值得学习
RFC 9 最大的价值之一,是它后来真的被推翻了。
因为它证明:
协议设计不能只问“能不能实现”,还要问“抽象能不能支撑未来用途”。
这套协议当然能实现远程登录和数据传输。
但它不能优雅支持:
1 | |
于是整个模型被重新设计。
这是非常典型的软件架构演化。
这篇 RFC 带来的影响
一、RFC 9 让 Host-Host Protocol 第一次和明确 API 绑定起来
前面的文档更多是:
1 | |
RFC 9 则真正写:
1 | |
从这里开始,协议能力已经不再只是:
1 | |
而是:
1 | |
二、RFC 11 直接继承并扩展这套思路
RFC 100 明确说:
1 | |
RFC 11:
Implementation of the Host-Host Software Procedures in GORDO
把这些设计进一步整合进:
1 | |
并详细讨论:
- Links;
- Connections;
- Message Structure;
- Transactions;
- Buffers;
- Interrupt Processing;
- Network Program;
- Handler;
- System Calls。
所以可以把:
1 | |
看作 RFC 11 的直接设计前身。
三、RFC 22 又修改 Control Message Formats
RFC 100 总结:
1 | |
说明:
1 | |
仍然在快速变化。
这也是早期 RFC 的典型模式:
1 | |
不断增量修正。
四、1969 年 12 月,这整套 Host-Host Protocol 被推翻
RFC 100 明确记录:
1 | |
这不是:
1 | |
而是:
1 | |
五、RFC 33 建立新的 Host-Host Protocol
1970 年 2 月:
1 | |
明确说:
1 | |
并正式:
1 | |
RFC 33 里开始出现更加成熟的:
1 | |
六、Link 也从 RFC 9 的“Full-Duplex Session Resource”重新定义
RFC 9:
1 | |
RFC 33 则明确:
1 | |
并通过:
1 | |
把两个 Process 间更高层通信抽象出来。
这是一个非常关键的成熟过程:
1 | |
而变成:
1 | |
上层另有:
1 | |
七、后来 Socket 进一步解决“进程端点”问题
RFC 33 把:
1 | |
逐渐拆开。
这正是 RFC 9 的 TTY / Login 模型没有处理好的问题:
1 | |
这个方向最终会成为现代网络编程最核心的抽象之一。
八、RFC 9 仍然留下了几个很耐用的软件工程思想
虽然协议被废弃,但下面这些东西没有过时:
1 | |
九、它展示了“失败的架构”同样值得长期保存
如果 RFC 系列只保留最终成功协议,我们就只能看到:
1 | |
却看不到:
1 | |
RFC 9 的价值恰恰在这里。
我们可以看到:
1 | |
为什么无法自然扩展成:
1 | |
这个失败过程本身就是协议史的重要知识。
总结
RFC 9 是早期 ARPANET Host Software 设计中一个非常重要的转折点。
它已经不满足于描述:
1 | |
而开始建立一个更完整的软件模型:
1 | |
在协议层,RFC 9 把:
1 | |
提供给每对 Host。
其中:
1 | |
其余可以作为:
1 | |
但 256 只是逻辑空间。
IMP 实际同时最多允许:
1 | |
而且如果本地 IMP 服务多个 Host:
1 | |
还需要共享。
Primary Link 是一段 Host-Host 会话的第一条 Link:
1 | |
典型流程:
1 | |
控制流程则通过:
1 | |
使用类似:
1 | |
的消息完成。
更重要的是,RFC 9 开始向 User Program 提供明确的网络 API:
1 | |
一个 User Program 不需要自己找空闲 Link、构造所有控制消息、维护 Host 状态或管理 ACK / NAK。
这些工作被统一收进:
1 | |
内部。
而 Network Program 又使用三类数据结构:
1 | |
其中甚至已经使用:
1 | |
这些今天仍然十分常见的软件实现技术。
一个 OPENPRIM 在内部会经历:
1 | |
也就是说:
1 | |
背后已经是一套真正的协议状态机。
但 RFC 9 这套设计最终没有成功。
1971 年的 RFC 100 回顾得非常直接:
1 | |
这个失败原因比任何字段都重要。
RFC 9 的设计本质上仍然把网络理解成:
1 | |
它非常适合:
1 | |
却不够适合:
1 | |
而互联网真正需要的是后者。
随后 RFC 33 会开始重构:
1 | |
把:
1 | |
和:
1 | |
逐渐拆开。
所以 RFC 9 最值得今天软件开发者带走的,不是:
1 | |
这些已经过时的机制。
而是三个更重要的工程经验。
第一:
协议最终必须变成应用可以调用的 API。
第二:
API 背后必须有清晰的状态、资源和数据结构模型。
第三,也是最重要的:
如果底层抽象过度绑定第一代使用场景,下一代需求出现时,整个协议可能不得不重做。
1969 年,第一代使用场景是:
1 | |
几个月以后,大家发现真正需要的是:
1 | |
于是:
1 | |
开始让位于:
1 | |
这一步,正是 ARPANET 从“远程终端网络”走向真正通用计算机网络的重要转折。
RFC 9,就是那个旧模型最完整、也最值得研究的一张设计图。