摘要

  • Telnet 的网络虚拟终端没有统一所有主机,只统一了必要动作:CR LF 是“新起一行”,CR NUL 是“只把位置退回本行左端”,默认 NVT ASCII 不允许裸 CR。
  • CR 后面的字节是一项可本地验证的证据;发送端选择共同动作,接收端再把它映射为自己的终端或操作系统行为。
  • RFC 1123 后来补清 Enter 键的输入歧义;Binary 模式取消行尾转换,Net-Unicode 则保留 CRLF、逐步收窄 CR NUL 的用途。

一枚尚未完成的字符

把早期终端看成一台打印机,CR 与 LF 就不是同义词。CR 让打印头回到本行左边,却不下移;LF 让它向下走一行,却保留横向位置。二者连在一起,才得到现代读者熟悉的“下一行开头”。

不同机器并不都能分别执行这两个动作。有的设备把回车与进纸绑定,有的主机只用 LF 结束一行,有的用 CR,还有的根本用记录长度或特殊分隔符。接收端若见到 CR 就立即动作,随后又独立处理 LF,结果可能重复;若一味等待,又不知道何时可以确定发送者只要回车。

RFC 318 在 1972 年把它写成一个有限等待问题:先保留 CR,再看下一字符。遇到 LF,就执行统一的换行功能;发送者若只想回车,则发送 CR NUL。NUL 对虚拟打印机不产生动作,却明确表示“这里没有配套的 LF”。

因此,空字节并非普遍无意义。它在特定位置完成语法,让沉默本身成为可解析的选择。

共同层只保留不可少的差别

RFC 854 把 Network Virtual Terminal 设为 Telnet 连接的初始共同表示。两端不必先交换完整的终端型号与操作系统习惯。每一端负责把本地行为变成 NVT 动作,并把收到的 NVT 动作再翻译回本地。

这不是一份全球终端政策。NVT 规定 NUL 无动作、LF 竖直下移、CR 回到左边;终端宽度、页长、字符转换、缓冲与本地驱动仍由端点决定。

RFC 854 同时把早期建议收紧为默认 NVT ASCII 规则:组合动作必须写作 CR LF,并作为一个“新行”处理;单独回车必须写作 CR NUL;其他位置不得出现 CR。该规则双向适用,即使双方都知道实际连接的是程序而非打印机。

接收 CR NUL 后,Telnet 要先去掉 NUL,再进行 NVT 到本地字符集的映射。这个 NUL 属于网络表示层,不天然属于应用正文。相反,在别的模式里擅自丢弃同一字节也可能毁坏数据。

Enter 键把本地意图重新带回问题

线上的二字节语法很清楚,人手按下 Enter 时却出现第二层歧义。用户是在要求“完成一行”,还是要求把 ASCII 的 CR 键原样交给远端字符型程序?

RFC 1123 记录了现实分裂:一些客户端发送 CR LF,另一些发送 CR NUL。对正确实现的 ASCII 服务器,两者作为终端输入都应产生本地行结束键的效果;非 ASCII 主机却可能在“保留真正的裸回车”与“兼容既有客户端”之间为难。

修正规则没有假装所有场景相同。User Telnet 必须能发送 CR LF、CR NUL 和 LF。ASCII 客户端宜让用户选择 CR LF 或 CR NUL,并以 CR LF 作为行结束键的默认值。对于服务器输出,或通过 Telnet 承载的其他应用协议文本,行尾必须使用 CR LF。

服务器仍掌握最后一段本地映射。原始终端模式可以向应用交付 CR;格式化模式则应用本机行尾规则。标准要求的是终端边界上的行为等价,并非所有内部缓冲区都保存相同字节。

Binary 不是“八位版 NVT”

RFC 856 的 Binary 选项需要逐方向协商。只有一端提出、另一端确认,该方向才进入二进制解释;反方向不会自动跟随。

进入 Binary 后,普通字节作为八位数据。Telnet 命令仍有边界:IAC 继续引出命令,数据中的 255 必须写成两个 IAC。但行尾解释停止。RFC 1123 明确禁止把 CR 改写为 CR NUL 或 CR LF。

这里发生的不是字符集变宽,而是解释权转移。在 NVT ASCII 中,CR 需要后一字节决定动作;在 Binary 中,CR、LF 与 NUL 都是应用数据,除非上层协议另有定义。只凭字节形状做“清理”,既可能误删 NUL,也可能凭空插入 NUL。

因此,故障记录不能只保存可见文字。方向、协商状态与原始 octet 必须在同一时间线上,才能判断转换是否越权。

Linemode 把编辑移近用户

RFC 1184 处理的是时延与逐字符交互成本。启用 Linemode 编辑后,客户端可以先在本地完成一行,再把正常行结束符写成 CR LF 发送。编辑关闭时,回车写成 CR NUL,LF 仍是 LF,无法映射成普通 ASCII、但含义是“完成此行”的按键写成 CR LF。

服务器输出的责任没有随编辑位置移动:新行仍用 CR LF,只回车用 CR NUL,只下移用 LF。优化把缓冲与编辑放到了更近的端点,却没有让客户端接管服务器输出的语义。

这条边界说明,性能布局与共同含义是两项独立决定。改变执行位置,不等于允许重写协议事实。

虚拟打印机退场,CRLF 留下

RFC 5198 在 Net-Unicode 中回顾了 NVT ASCII 的影响。它选用 UTF-8,并继续以 CRLF 表示行尾;同时保留 CR 必须紧接 LF 或 NUL 的结构限制。

不过,该文档建议尽量避免 CR NUL。现代文本能以组合字符和标记表达早期的叠印、版面效果,而 NUL 又可能被某些语言和库误作字符串终点。曾经必要的“只回车”动作,已不再值得在一般网络文本里广泛携带。

这不是说 1972 年的设计错误。它说明公共表示会分层老化:深度安装的 CRLF 继续承担行边界,依赖老式打印动作的例外逐渐缩小。

IANA Telnet Options registry 仍列出 Binary、Linemode 与回车处理等选项。登记只能证明编号与名称稳定,不能证明当下部署数量或产品实现质量。

过早归一化会抹掉谁的决定

这套机制的权力分配很薄。发送者声明共同动作;接收者读取相邻字节即可本地验证;端点各自决定本机映射;双方同意后,Binary 等模式才改变解释器。

风险来自越过顺序。网关尚未读到协商状态便统一行尾,客户端过早把 Enter 固定成换行,字符串库在 NUL 处截断,日志只保留渲染结果。共同规则本来给出了答案,工具却先销毁了答案。

来源与证据边界

这些官方文档能够证明协议模型、修正过程、协商例外与登记词汇,不能测量当前 Telnet 使用率,不能认证某款产品的默认行为,也不能证明历史实现从未分歧。本文只讨论 CR NUL 在明确模式与方向中的表示责任。