II. 问题与倾向意见
在本节中, 我们试图逐一列出最近的 NWG/RFC 和私下交流中提出的若干问题, 并针对每个问题提出一个答案或一项策略。在许多情况下, 好的想法之所以被否决, 是因为我们估计它们应当放在另一个层次上实现。
A. 双重填充
正如 BBN 报告 #1822 所解释的, Host-to-Imp 接口的 Imp 一侧会拼接一个 1 并随后跟上零个或多个 0, 以把消息填充到 Imp 字边界, 同时又保留消息长度。此外, Imp-to-Host 接口的主机一侧会用 0 扩展消息, 把消息填充到主机字边界。
如果发送方主机想要发送整数个字, 或者发送方主机的硬件能够发送不完整的字, 那么 BBN 的机制工作良好。然而, 如果发送方主机想要发送一条长度不规则的消息, 而它的硬件又只能发送长度为字的整数倍的消息, 那么就需要某种额外的约定。
最简单的解决办法之一, 是修改 Host-to-Imp 接口的 Imp 一侧, 使其只追加 0。这意味着主机软件必须自己提供末尾的那个 1。BBN 否决了这项修改, 因为他们对硬件改动怀有一种可以理解的强烈抵触。也有人提出, 对 Imp 程序打一个五条指令的补丁即可去掉接口提供的那个 1, 但这同样被否决了, 新的理由是: 只依靠主机硬件来标示消息结束、而完全不依靠主机软件, 看起来更为可靠。
另外还有两种解决办法。一种是采用 "双重填充", 即发送方主机提供 10*, 网络也提供 10*。接收方主机在输入时再剥去末尾的 10* 10*。另一种办法是利用标记。标记是插在消息首部与消息正文之间的一个形如 0*1 的串。标记的最初用意是扩展首部, 使发送方主机能够让其正文在字边界上开始。也可以利用标记来扩展消息, 使它在字边界上结束。
请注意, 如果让正文开头紧贴首部, 双重填充就可以完全取代标记。对于 32 bit 的机器, 这样做很方便, 而标记则不方便; 而对于其他字长, 特别是 36 bit 的机器, 标记要比双重填充方便得多。
我们没有强烈的倾向, 部分原因是我们能够发送字的片段。Shoshani 等人在 NWG/RFC #44 中声称, 调整标记不会给他们造成任何问题, 而他们用的是 32 bit 的机器。既然标记的想法已被接受了一段时间, 我们建议不采用双重填充, 而用标记来调整消息的长度。我们注意到, 如果 BBN 将来确实从硬件填充中去掉那个 1, 那么发送一侧的主机软件只需做极小的改动。
W. Sutherland 提出了一种漂亮得多 (也昂贵得多) 的方案。他建议让 Host/Imp 接口足够智能, 能够剥去填充或标记, 甚至可以在输入时解析消息。
B. 重连
为数众多的网络研究者因为我们在协议中加入了动态重连而对我们大加抨击。我们觉得, 讲一讲它是怎样被加进来的, 或许会引起大家的兴趣。
在对连接及其用途思考了一段时间之后, 我们想知道, 连接这一机制与现有的各种主机内进程间通信形式相比如何。有两个方面值得关注: 文献中提出过哪些形式体系, 以及实际使用着哪些机制。形式体系之所以值得关注, 是因为它们能带来统一的实现和简约的设计。现有机制之所以值得关注, 是因为它们指出了哪些问题需要解决, 有时还能提示合适的形式体系可能是什么样子。特别是, 我们注意到, 拨入时把控制台连接到登录程序的机制、创建作业的机制, 以及在作业内部把控制台在各个进程之间传递的机制, 往往都极其特殊, 与操作系统内的其他所有结构和机制都截然不同。
就文献而言, 似乎只有一种思想及其若干变体, 即进程应当共享各自地址空间的一部分, 并相互协作地唤醒对方。信号量和事件通道是唤醒信号的便利扩展, 但其用意基本相同。(事件通道或许可以充当连接, 但这似乎不在其预定用途之内。在小型系统中, 事件通道的效率与容量成反比。)
就现有实现而言, 我们注意到, 有若干系统允许一个进程在另一个进程看来就是一个文件。有些系统, 例如 SRI 的 SDS-940, 在这样连接起来的两个进程之间强加一种主从关系, 而另一些系统则提供对等关系, 例如 MAC 的 AI 小组的 PDP-6 系统。PDP-6 系统还有一项功能, 上级进程可以用一个从设备名和文件名到其他设备名和文件名的映射把下级进程 "包围" 起来。控制台的语义与文件几乎相同, 因此下级进程完全有理由以为自己在与控制台通信, 而实际上却是在与另一个进程通信。
网络连接与现有的顺序式进程间连接之间的相似性, 支持了我们的信念: 网络连接很可能是使用网络的正确结构。而且, 这种结构足够简洁, 又与足够多的机器相容, 可以称得上一种形式体系或理论, 至少在文献中提出的其他进程间通信形式所达到的程度上是如此。
我们认为, 任何新的形式体系至少必须经受住以下两项检验:
-
它解决了哪些悬而未决的问题?
-
它在所有操作下是否封闭?
就网络连接而言, 第一项检验的候选答案就是上面给出的那些, 即所有涉及把控制台连接到作业或进程的操作。同样值得关注的, 还有对磁带机、打印机和读卡机等顺序设备的建模, 以及对其缓冲 (假脱机, symbiont) 系统的建模。
第二个问题提到了封闭性。在把连接这一形式体系应用于拨入和登录过程时, 我们感到需要加入某种切换或重连, 而一篇 SJCC 论文 (也就是 NWG/RFC #33) 中给出了一种极其温和的形式。这种温和形式只允许替换 AEN, 而且即便如此, 也只能在建立连接之时进行。然而, 常见的经验是, 如果一种操作在一个扩展的定义域上有自然的定义, 那么终究会有必要, 或者至少会希望, 扩展它的定义。因此, 我们考虑了以下扩展:
-
切换到任何其他 socket, 可能位于另一台主机中。
-
即使在数据流已经开始之后也能切换。
认为这些扩展可能有用, 甚至还有一些先例可循。按照对操作系统的一种看法, 我们把所有可用的电话线都看作属于一个活动进程, 即登录程序。登录程序接听呼叫、筛查用户, 并创建作业和进程。大多数电话应答设备的特点之一是, 通过使用一组连续号码和一套轮转应答系统, 许多条电话线可以服务于同一个电话号码。在追求为实际系统建立准确模型的过程中, 我们希望能为网络用户提供同等的服务, 即他们应当能够呼叫一个公布的号码并被连接到登录程序。这样, 切换的必要性就有了初步的依据。
接下来我们看到, 登录程序在盘问一个潜在用户之后, 必须把该用户连接到一个新创建的作业上。用户与登录程序之间的数据流此时已经开始, 因此, 如果不希望丢失或弄乱传输中的数据, 流量控制就必须与切换配合起来。
至于主机间切换, 我们很容易设想一种分布在整个网络中的实用服务, 它在用户不知情的情况下把连接从一个 socket 转交给另一个 socket。此外, 它也类似于较为复杂的电话系统、电话公司接线员的标准服务, 以及分布式的专用系统。
这些考虑促使我们去研究能否找到一种可以为所有已知模型提供基础的重连方式。这个算法得来并不容易, 大概是因为我们缺乏有限状态自动机理论方面的经验, 但我们最终还是得出了 NWG/RFC #36 中给出的算法。不久之后, Bill Crowther 给出了一个等价的算法, 它对竞争条件采取了另一种处理方式。
网络研究者的反应似乎有两种。要么认为它很漂亮, 并且 (或许正因如此) 很有用; 要么认为它很复杂, 并且 (同样或许正因如此) 没有必要。在我们看来, 后一类人要多得多, 于是我们被迫处于守势, 承认动态重连只不过是
-
很漂亮
-
对登录和控制台传递有用
为回应持续不断的批评, 我们对协议做了以下修改。不再通过呼叫 socket <O,H,O> 来登录, 而是规定形如 <U,H,O> 和 <U,H,1> 的 socket 分别是登录程序的某个副本的输入 socket 和输出 socket; 或者, 如果已经以用户 id U 启动了一个作业, 那么这两个 socket 就是控制台 socket。因此, 登录协议就是向 <U,H,O> 和 <U,H,1> 发起连接。如果用户 U 未被使用, 登录程序的一个副本将作出响应并盘问呼叫方。如果用户 id U 正被使用, 呼叫将被拒绝。这项修改是 Barry Wessler 最近提出的。(其他人很早以前也提出过这项修改, 但当时被我们否决了。)
登录程序可以要求呼叫方来自同一个虚拟网, 即呼叫方可以在另一台主机上拥有用户 id U; 也可以要求用户提供与用户 id U 相匹配的口令; 或者两者都要求。有些系统甚至可能选择允许任何人以任何用户 id 登录。
登录之后, AEN 0 和 1 仍然是控制台 AEN。每个系统想必都有传递控制台的机制, 这些机制可以加以扩展, 使之能够为网络用户识别 AEN 0 和 1。因此, 传递控制台就是把 socket 重新连接到端口的问题, 它发生在主机内部, 不涉及网络。
在收到 NWG/RFC #46 之后, 我们与 Meyer 和 Skinner 进行了交谈, 他们提出了一种既不同于 Meyer 的方案、也不同于我们在上文一节中所述方案的登录方案。他们的新方案看起来要好一些, 我们期待着他们的下一份笔记。
大家普遍同意, 登录应当是 "第三层" 的, 也就是位于 NCP 层之上。对于具体采用哪种登录方案, 我们开始变得无所谓了; 各种方案似乎都可以, 但没有一种让我们印象特别深刻。我们建议试用几种方案。当然, 修改本地登录过程会带来一些负担, 但我们认为应对多种不同的登录过程并不会造成额外的困难。这是因为文本序列和中断约定本来就五花八门, 以至于比如说在我们的系统上遵循我们的方案、在 Multics 上遵循 Meyer 的方案, 所带来的额外负担微乎其微。
我们同意, 初始协议中不应要求重连, 我们以后会把它作为一种可选的实验性工具提供出来。此外, 我们愿意郑重作出如下预测: 通用的重连功能将会变得有用, 并将为当前那些临时拼凑的操作系统结构提供一个统一的框架。
C. 连接与链路的解耦
Bill Crowther (BBN) 和 Steve Wolfe (UCLA) 分别独立地提出, 不要把链路分配给特定的连接。他们建议, 把目标 socket 作为消息正文的一部分包含进去, 然后通过任何一条未被阻塞的链路发送消息。
我们在 NWG/RFC #37 中稍微讨论过这个问题, 并认为两种做法目前都还各有道理。鉴于当前强调简单、快速和占用内存少, 让链路与连接保持耦合似乎更有效率。因此, 我们推荐这种做法。
D. 错误报告
正如 RAND 的 J. Heafner 和 E. Harslem 所指出的, 处理可能出现的错误是很重要的。一个好的原则是防范任何会破坏 NCP 数据库一致性的输入。
Heafner 和 Harslem 在 NWG/RFC #40 中、Meyer 在 NWG/RFC #46 中给出的错误命令的具体形式看来是合理的, 我们建议采用。不过, 有几点意见需要说明。
应当区分资源错误和其他类型的错误。资源错误只是对过载状况的检测。过载状况是定义明确且合法的, 尽管可能并不理想。其他类型的错误则反映了软件或硬件出了差错。我们认为, 资源错误不应当用错误机制来处理, 而应当用针对该问题的专门机制来处理。因此, 当再也没有空间保存等待中的 <RFC> 时, 可以发出 <CLS> 命令。流量控制协议的设计目的就只是处理缓冲过载。
至于真正的错误, 我们不能确定 <ERR> 命令对接收方有什么价值。接收方的 NCP 想必已经坏了, 再用错误命令对它狂轰滥炸, 可能只会使问题更加恶化。因此, 我们建议: 错误的生成是可选的; 所有错误都在本地记录到一个按时间顺序排列的文件中; 收到的 <ERR> 命令同样记录到一个按时间顺序排列的文件中。目前不规定任何纠正措施。
在网络于 UCLA 投入运行的这段短暂时间里, 我们已经确信, 网络本身产生的错误将会非常少。我们看着 BBN 的工作人员调试和测试 IMP 程序, 看来大多数错误影响的是时序和吞吐量, 而不是正确性。因此, 大多数错误很可能来自出了故障的主机和/或有缺陷的 NCP。
E. 状态测试与报告
一种很有价值的调试辅助手段, 是能够获取关于外部 NCP 认为正在发生什么的信息。实现这一点的一个便利方法是, 允许 NCP 在任何它们愿意的时候发送状态, 但要求它们在收到请求时一律这样做。
由于我们把这项功能主要看作一种调试工具, 我们建议使用一条专门的链路, 比如 255。其用意是, 状态请求的处理和状态消息的生成应当尽可能少地使用常规机制。因此, 我们建议用链路 255 发送 "请求状态" 和 "状态为" 命令。其形式遵循 NWG/RFC #40 第 2 页上的建议。
Meyer 的 <ECO> 命令易于实现, 并承担了测试外部 NCP 是否存活这一更基本的功能。我们建议 <ECO> 命令的长度可变, 因为在这一场合下 48 bits 似乎没有什么意义。此外, 一个 (想必是) 8 位二进制开关的价值并不清楚, 因此我们推荐一对命令:
<ECO> <length> <text>
以及
<ERP> <length> <text>
其中
<length> 为 8 位。
收到 <ECO> 命令后, NCP 将以 <ERP> 命令作为回显。
F. 扩展与实验
正如 Meyer 在 NWG/RFC #46 中正确指出的, 网络协议是分层的。到目前为止, 已经可以看出三个层次。
-
IMP 网络协议
-
网络控制程序协议
-
特殊用户层或子系统层协议
最后这一层应当保持为每台主机 (甚至每个用户) 所特有。第一层已由 BBN 作出了很好的规定, 我们在这里关注的是第 2 层。我们希望让第 2 层尽可能中立和简单, 特别是, 我们同意登录协议应当尽可能放在第 3 层。
尽管力求简单并有所预见, 但总会有第 2 层协议需要改变或需要进行实验的时候。为了给实验和改变留出余地, 我们建议只把链路号 2 到 31 分配给常规连接, 其余的链路号 32 到 255 用于实验。我们已经建议用链路 255 进行状态请求和应答, 这与我们把该功能视为实验性功能的看法是一致的。
我们还建议, 从 255 往下的控制命令前缀用于实验。
我们认为, 这两项约定足以让任意一部分站点之间方便地对新协议进行实验。因此, 我们不赞成采纳 Ancona 在 NWG/RFC #42 中提出的建议, 即把消息正文的前八位用作消息数据类型码。
G. 端口到 socket 的多路复用
Wolfe 在 NWG/RFC #38 中、Shoshani 等人在 NWG/RFC #44 中提出, 应当可以把多个端口连接到一个 socket 上。虽然我们所有的图示和原型系统调用都显示 socket 与端口之间是一一对应的, 但这纯粹是本地实现的问题。我们注意到, socket 构成一个全网范围的名字空间, 其唯一目的是在各个操作系统所特有的特殊结构之间充当接口。我们提到端口只是为了便于说明, 如果没有与之对应的内部结构, 就应当忽略它们。不过, 大多数系统确实有这样的结构, 所以我们将继续用它们来举例说明。
H. 回显、中断与代码转换
1. 中断
我们原以为所有操作系统都会扫描来自键盘的某个保留字符, 并把它解释为中断信号。MIT 的 Tom Skinner 和 Ed Meyer 告诉我们, 37 型 TTY 和 IBM 2741 会产生一个 200-500 毫秒的 "长空号", 它由 I/O 通道硬件检测到, 并作为中断传递给操作系统。"长空号" 不是字符 -- 它没有 ASCII 码, 也不能由程序生成。
早在一年多以前, 我们就考虑过模拟控制台中断的问题, 并否决了 <INT> 类型的命令, 因为它不能正确地模拟我们所知道的任何系统。现在我们改变立场, 推荐按照 Meyer 在 NWG/RFC #46 中的建议, 实现一个 INTERRUPT 系统调用和一个 <INT> 控制命令。
中断功能应遵守两项限制。第一, 在与扫描中断字符的系统通信时, 不应使用这项功能。第二, 非控制台类的连接大概不应当有中断。我们建议各系统遵循各自的约定, 如果某个 <INT> 到达了一个本不该收到它的连接, 那么该 <INT> 应当被丢弃, 并可选择作为错误返回。
2. 回显与代码转换
我们认为, 每个站点都应继续沿用其现行的回显策略, 代码转换应由使用方进程完成。这一领域的标准化应留待进一步的发展。
Ancona 提出的表驱动前端转换器的建议看来是正确的做法, 但我们认为, 这类技术属于一个更大的讨论, 涉及网络的高级语言。
I. 广播功能
Heafner 和 Harslem 在 NWG/RFC #39 中提出了一种广播功能, 即 <TER> 和 <BDC>。我们并不完全理解这项功能的价值, 因此倾向于反对它。我们猜想, 如果我们对 OS/360 有更多经验, 就会更好地理解它的价值。一般来说, 运行 OS/360 或类似系统的站点, 大概会比运行分时系统的站点觉得我们关于网络协议的建议与自己的关系更小。对于 OS/360 与网络协议背后的概念和假设之间的关系, 我们欢迎任何有说服力的阐述。
J. 实例号
Meyer 在 NWG/RFC #46 中建议扩展 socket, 使其包含一个标识连接到该 socket 的进程的实例码。我们曾精心安排, 使各个进程无法相互区分。我们这样做是基于这样的信念: 无论从形式上还是从实践上看, 一项计算是由一个进程还是由多个进程完成, 只是主机内部关心的事情。因此, 我们认为一个作业内的所有进程都应当协作分配 AEN。如果某个操作系统具有在作业内把控制台从一个进程传递给另一个进程的功能, 那么这些功能与当前的网络协议可以很好地配合, 即使在重连协议之下也是如此; 但实例号会妨碍这样的过程。
我们建议对这个问题进行充分讨论, 因为它关系到 socket 和连接的基本理念。目前我们推荐使用不带实例码的 40 位 socket 号。
K. AEN
包括我们自己在内, 没有人对我们给 socket 低 8 位起的名字 AEN 特别满意。我们否决了 socket number (socket 号), 对 Meyer 的 socket code (socket 码) 也同样不满意。字段名中不应使用 socket 这个词, 我们征求大家的建议。