文件传输协议演进:从 Kermit 到 rsync
C-Kermit 11.0.506 时隔近15年发布首个非测试版。了解 Kermit 如何应对噪声线路,以及 FTP、NAT、SFTP 和 rsync 为何改变文件传输。
文件传输协议为何不断变化
每种文件传输协议,都是针对其所处年代的特定故障模式设计的。Kermit 假定线路会损坏数据。XMODEM 和 ZMODEM 假定连接速度很慢,而且每分钟都需要付费。FTP(文件传输协议)假定中间的网络会正常转发数据。SSH 则假定网络环境不可信。最后一种假设最终占了上风。这就是为什么如今的 VPS 通常提供基于 SSH 的 SFTP 和 rsync,而很少提供其他选项。
现在重新审视这一点正合适。C-Kermit 11.0.506 于 3 August 2026 发布。这是自 20 August 2011 发布 C-Kermit 9.0.302 以来的第一个非 beta 版本,而它实现的协议设计于 May 1981。四十五年足以见证一个完整类别的技术被发明、标准化、被其运行所依赖的网络破坏,最终并入 SSH。
Kermit,1981:为吞噬字节的线路而设计
Kermit 于 1981 年 5 月由 Frank da Cruz 和 Bill Catchings 在 Columbia University Computer Center 创建。名称来自 Kermit the Frog。Da Cruz 说,当时团队正在讨论名称,墙上正好挂着一张 Muppets 日历,没有人想到这个名称最终会传播开来。
Kermit 解决的问题不是速度。终端与大型机之间的链路并不是可以传输任意字节的管道。它是一种带有自身限制的字符设备。它可能只支持 7 位。它可能是半双工的。它可能吞掉控制字符,或者将其中某个字符作为命令执行。未经修改地通过它发送二进制文件无法正常工作。
因此,Kermit 的设计直接接受了这些限制。Kermit Project 的历史资料列出了以下设计目标:
- 使用短数据包,因为大多数大型机无法从终端接收长时间的连续数据
- 使用半双工的停等传输,因为 IBM 大型机不支持全双工通信
- 将控制字符和 8 位字符编码为可打印字符,因为二者都无法通过大型机的终端驱动程序
- 每个数据包都附带校验和,并由接收方确认,这样数据包损坏时只需重传该数据包,而不必重新传输整个文件
第三点最值得注意。Kermit 发送的是文件的文本安全编码,而不是文件本身。控制字节会转换为一个前缀字符和一个可打印字符;对于 7 位链路,设置了最高位的字节也可以采用相同方式编码。链路中间的设备即使只理解可打印文本,看到的也仍然是可打印文本。代价是数据量增加:二进制文件在链路上传输时会变大。对于原本会彻底破坏传输内容的大型机前端来说,这是正确的取舍。
Kermit 的另一个独特之处是其适用范围。XMODEM 在两台已经对文件定义达成一致的机器之间传输文件。Kermit 则被设计为不同系统之间的最低共同标准;这些系统可能使用不同的字符集、不同的记录结构,也可能对文本行结束方式有不同理解。这就是从大型机迁移到云服务器的漫长过程所描述的世界。在网络层替你处理互操作性之前,Kermit 就是互操作性的实现方式。
Columbia 于 2011 年结束赞助,并依据修订后的 3-clause BSD licence 发布 C-Kermit。Frank da Cruz 参与该项目长达 44 年,从 1981 年的设计工作一直持续到 2025 年。2026 release 由 OpenKermit project 维护,John Goerzen 负责将这个 C codebase 现代化;它比如今阅读本文的大多数人都更早出现。
XMODEM 和 ZMODEM:电话费如何影响协议设计
Ward Christensen 于 1977 年编写了 MODEM.ASM,该程序引入的协议就是 XMODEM。1978 年,他与 Randy Suess 将 CBBS 上线,这是第一个公开的公告板系统。Christensen 于 2024 年 10 月 11 日去世。
XMODEM 几乎已经是协议所能达到的最小规模。数据以 128 字节为一个块传输。每个块包含一个字节的校验和,即 128 个数据字节之和对 256 取模。接收方确认每个块,或要求重新发送该块。这种设计源于经济因素。在拨号线路上,费用按使用时间计算,因此线路错误只需重新传输一个块,而不是整个传输。
它的弱点也在同一个设计中。XMODEM 每传输 128 字节就要等待一次确认。Chuck Forsberg 在 ZMODEM 规范中直截了当地写道:“短块长度在分时系统、分组交换网络和卫星线路中使用时,会导致吞吐量下降。”导致停等协议性能下降的是延迟,而不是带宽。每次往返等待,都会让计费中的线路处于空闲状态。
接下来出现了 YMODEM,Ward Christensen 于 1985 年为其命名。它增加了批量传输功能。发送方在数据之前声明文件名和大小,因此一次会话可以传输多个文件,接收方也能知道每个文件的结束位置。
ZMODEM 是 Chuck Forsberg 在 Omen Technology 编写的解决方案。其规范版本日期为 1988 年 10 月 14 日,并声明:“ZMODEM 是根据 Telenet 合同为公有领域开发的。”Telenet 运营着一个公共分组交换数据网络,这份合同影响了协议设计。ZMODEM 会转义网络控制字符,因此中间的分组网络不会消耗这些字符。它使用唯一的字符序列标记每个帧的开始,而不是通过静默状态推断帧边界,因此遇到噪声时无需等待超时即可恢复。它还提供显式的断点续传功能,因此中断的传输可以从停止的位置继续。
更重要的是,它不再等待。规范对其特性的描述是:“ZMODEM 实际上将整个文件作为一个窗口使用。”发送方持续发送,只有在接收方报告问题时才停止。这与 TCP 在窗口机制中实现的是同一个理念,只是出发方向相反:有人观察到调制解调器处于空闲状态,并据此得出了结论。
FTP 的两条连接为何如此难以适应现代网络
FTP 比这些技术都更早。RFC 114《文件传输协议》发布于 1971 年 4 月 16 日,作者是 A. Bhushan。
其中一个值得了解的细节是,RFC 114 曾讨论并否定两条连接的设计。Bhushan 权衡了“使用两条全双工链路,一条用于控制信息,另一条用于数据”,随后得出结论:“我们建议使用一条全双工连接,同时交换数据和控制信息。”这种拆分设计后来才出现。日期为 1972 年 7 月 8 日的 RFC 354 规定,“数据和文件只能通过数据连接传输”,命令则通过单独的 Telnet 连接传输。由 Postel 和 Reynolds 编写、发布于 1985 年 10 月的 RFC 959,成为至今仍被广泛实现的版本。
RFC 959 还固定了端口。服务器默认的数据端口是“与控制连接端口相邻的端口(即 L-1)”。因此,控制连接使用 21 端口时,数据端口就是 20。
但原始设计中有一部分无法适应现代网络。在 FTP 的原始模式下,服务器 会向客户端反向建立数据连接。位于 NAT(网络地址转换)之后的客户端没有服务器可以访问的地址;位于防火墙之后的客户端也不会接受入站连接。因此,数据连接无法建立。客户端请求目录列表或文件后,传输就会挂起。解决方案是 PASV。RFC 959 将其定义为:请求服务器“在某个数据端口(不是默认数据端口)上‘监听’,并等待连接,而不是在收到传输命令后主动发起连接”。服务器会返回待连接的地址和端口:
PASV
227 Entering Passive Mode (203,0,113,10,195,80)该响应表示主机为 203.0.113.10,端口为 195 乘以 256 再加 80,即 50000。再读一遍,就能看出其中的结构性问题。第二条连接的端点信息,包含在第一条连接的有效载荷中。NAT 设备或防火墙必须解析控制通道,并开放其中指定的端口,才能放行这条连接。Linux 自带的连接跟踪辅助模块正是这样工作的。该辅助模块只有在控制连接使用明文时才有效。因此,通过 TLS(传输层安全)封装 FTP 后,中间设备就无法看到原本用于使 FTP 正常工作的控制信息。
这就是 FTP 带来的教训,可以概括为一句话:它让网络参与了协议处理。一个需要网络理解自身内容的协议,无法在网络不再信任它之后继续生存。
结局已有记录。Firefox 在 2021 年 7 月发布的 90 版本中移除了 FTP 支持。Chrome 在 2021 年 10 月发布的 Chrome 95 中移除了 FTP 代码。
rcp 和 r-commands:按主机名建立信任
4.2BSD 由 Berkeley 在 DARPA 资助下于 1983 年发布,带来了 rcp、rsh 和 rlogin。它们面向同一网络中的 Unix 机器组成的校园环境设计,其身份验证模型也体现了这一点。主机会声明发起调用的用户身份。如果 /etc/hosts.equiv 或用户的 ~/.rhosts 表示信任该主机,系统就接受这一声明,不要求输入密码。
必须明确说明其工作机制,因为这正是这些命令被淘汰的原因。信任建立在地址和声明之上。两者都会以明文通过网络传输,因此链路上的任何人都可以读取,也可以伪造。从 Unix 到 Linux 的演进路径所描述的环境中,网络仅限于一栋建筑,这种模型尚且合理。网络变成互联网后,它立即失去了合理性。
rcp 做对的是接口设计。源、目标,然后完成。不需要建立会话,不需要协商传输模式,也不需要安排第二个连接。它的行为类似于路径中带冒号的 cp。这个接口比其协议多延续了四十年。
SSH 涵盖了整个领域
1995 年,时任赫尔辛基工业大学研究人员的 Tatu Ylonen 为应对大学网络中的密码嗅探攻击编写了 SSH。1995 年 7 月,他将 SSH 作为附带源代码的自由软件发布。同年年底,估计已有来自 50 个国家的约 20,000 名用户。1995 年 12 月,他成立了 SSH Communications Security,继续开发该软件。
后续版本收紧了许可证条款,因此 OpenBSD 开发者从最后一个采用自由许可证的版本 ssh 1.2.12 分支出新项目。首次导入发生在 26 September 1999,OpenSSH 1.2.2 随 OpenBSD 2.6 于 1 December 1999 发布。这个分支很好地说明了为什么开源许可证条款在实践中很重要,因为如今几乎所有人运行的 SSH 实现,都源自那个许可证仍允许如此操作的版本。
SSH 出现后,文件传输不再是一个独立问题。经过身份验证且加密的流可以承载多个通道,已经具备旧协议必须自行构建的功能:完整性、顺序控制,以及无需建立第二个 TCP 连接的第二条数据路径。如果您还不熟悉这些机制,请先了解 SSH 的实际工作方式,再继续阅读。
SSH 因此衍生出了两个工具。scp 是在 SSH 会话中运行的 rcp 有线协议,因此它完全继承了 rcp 的命令行界面。SFTP 则采用不同设计:它是真正的文件协议,支持目录列表、文件属性和随机访问,并通过 SSH 通道传输。SFTP 从未成为 RFC。IETF 草案 draft-ietf-secsh-filexfer 于 18 July 2006 达到第 13 版,随后过期。OpenSSH 实现了该草案的第 3 版。全世界使用最广泛的安全文件传输协议,是一份已废弃草案的编号修订版,但它确实能正常工作。
传统 scp 协议现在也已被弃用。OpenSSH 8.8 于 26 September 2021 发布,并警告:“OpenSSH 的后续版本很快会将 scp(1) 从使用传统 scp/rcp 协议切换为默认使用 SFTP。”OpenSSH 9.0 于 8 April 2022 发布,并完成了这一变更:“此版本将 scp(1) 从使用传统 scp/rcp 协议切换为默认使用 SFTP 协议。”
其中的原因解释了一项常见认知。旧版 scp 协议会将远程文件名通配符交给远程 shell 展开,因此用户学会了在远程路径中为每个元字符加上双引号。8.8 的说明指出,基于 SFTP 的 scp “不再需要这种繁琐且脆弱的引号处理”。因此,在当前服务器上,scp 是一个使用 rcp 命令行界面的 SFTP 客户端。1983 年的接口保留了下来。1983 年的有线协议没有。
rsync,1996:发送差异,而不是整个文件
Andrew Tridgell 和 Paul Mackerras 于 1996 年 6 月 19 日在澳大利亚国立大学宣布推出 rsync,同时发布了技术报告 TR-CS-96-05《rsync 算法》。
在 rsync 之前,每种协议都在解决如何移动文件而不损坏文件。rsync 则提出了另一个问题:对方已经拥有这个文件的多少内容。报告将目标描述为“低带宽、高延迟的双向通信链路”,目标是识别“源文件中与目标文件某些部分相同的部分”,从而只发送不匹配的部分。
理解其工作机制很有价值,因为这能解释 rsync 的行为。接收端将已有副本切分为固定大小的块,并为每个块计算两个校验和:一个较弱且计算成本低,另一个较强且计算成本高。然后,接收端将这份列表发送给发送端。发送端以每次移动一个字节的方式,在自己的文件上滑动窗口,并增量更新弱校验和。这使得逐字节扫描的成本可控。发现弱匹配后,再使用强校验和进行确认。确认的匹配项会转换为块引用。其他内容则以字节文本的形式发送。接收端利用已有块的引用和刚收到的文本字节重建文件。
如果在大型文件开头插入一个字节,简单的差异工具必须发送整个文件,因为每个偏移量都发生了变化。滚动窗口可以在新的偏移量处找到相同的块,因此 rsync 只需发送一个字节和相关的元数据。这也是 rsync 仍然适合用于多次复制同一目录的原因。
有两种行为经常让人感到意外,而手册中都对此有说明。首先,rsync 不会先对文件进行校验和计算,再决定是否检查文件。它“默认使用‘快速检查’算法查找需要传输的文件,该算法检查文件大小或最后修改时间是否发生变化”。如果文件内容发生变化,但大小和时间戳保持不变,rsync 就会跳过该文件。--checksum 会改变这一行为,使两端完整读取每个候选文件。其次,当两个路径都是本地路径时,差异算法默认关闭,因为在同一台机器上读取并计算两个副本的校验和,其成本高于直接复制字节。只有链路速度较慢时,差异传输才能节省开销。
您在 VPS 上实际会使用什么,以及原因
简而言之:传输少量文件时使用 SFTP;需要反复复制某个目录时,使用基于 SSH 的 rsync。
二者都通过 SSH 传输,因此无需额外配置即可继承主机密钥验证和加密功能。这相当于将五十年的工作压缩成一个默认设置。Kermit 的设计者必须假设线路会损坏数据,因此在协议中加入了校验和重传机制。现在由 TCP 完成这些工作。Christensen 和 Forsberg 必须假设每个字节都需要付费,因此加入了断点续传和流式传输功能。现在由 rsync 的差分算法完成这些工作,而且做得更好。FTP 的作者假设网络中的主机彼此协作;在这些假设中,只有这一项后来被证明是错误的,而且无论如何改进协议都无法修复。
校验和的作用仍然是什么
“校验和”一词在这段历史中承担过三种不同的作用,不能混为一谈。
Kermit 和 XMODEM 的逐数据包校验和用于检测线路上的数据损坏。如今,TCP 校验和以及链路层的纠错机制负责这项工作,因此现代传输工具不会要求您关注它。
rsync 的分块校验和并不是回答“这些数据是否正确”,而是回答“您是否已经拥有这个数据块”。在这里,强校验和是查找键,而不是证明文件来源的依据。
第三种作用仍然需要您处理。发布文件附带的校验和可以回答 TLS 无法回答的问题。TLS 只能证明您连接到了正确的服务器,不能证明该服务器上的文件本身正确;如果文件来自镜像站,TLS 对此也无能为力。因此,发布文件的校验和和签名仍然值得花三十秒检查,而且很容易养成习惯:检查每个要安装的下载文件的校验和。
这个问题之外的其他问题,都由下层解决了。校验文件来源的问题没有解决,因为它从来都不是网络问题。
FAQ
FTP 在 VPS 上是否仍然安全?
不安全。普通 FTP 以明文传输凭据和文件内容,路径上的任何人都可以读取这两类数据。它还依赖能够解析控制信道的防火墙;一旦使用 TLS 加密控制信道,这种解析就无法继续进行。浏览器也已停止支持 FTP:Firefox 在 2021 年 7 月的版本 90 中移除了 FTP 支持,Chrome 在 2021 年 10 月的版本 95 中移除了相关代码。请使用基于 SSH 的 SFTP。它只需要一个端口,也不需要理解协议的中间设备。
FTP 为什么需要被动模式?
因为在 FTP 的原始模式中,服务器会反向向客户端发起数据连接。RFC 959 将服务器的默认数据端口定义为“控制连接端口相邻的端口(即 L-1)”,因此控制端口为 21 时,数据端口为 20。位于 NAT(网络地址转换)之后的客户端没有服务器可访问的地址,因此该连接无法到达,传输会一直挂起。PASV 会反转连接方向:服务器改为监听,并在 227 Entering Passive Mode 响应中返回地址和端口,供客户端连接。
scp 是否仍使用自己的协议?
自 OpenSSH 9.0 起不再使用。该版本于 2022 年 4 月 8 日发布,并“默认将 scp(1) 从使用旧版 scp/rcp 协议切换为使用 SFTP 协议”。OpenSSH 8.8 在 2021 年 9 月宣布了这一变更。表面上的差异在于引号处理。旧协议会将远程通配符传递给远程 shell,由远程 shell 展开;基于 SFTP 的协议不会这样做。因此,依赖 shell 展开的路径现在的行为会有所不同。
对 VPS 而言,rsync 何时优于 scp?
当您需要复制同一目录树多次时。rsync 只传输目标端尚未拥有的文件内容,因此第二次复制的成本远低于第一次。对于目标端从未见过的单个文件,scp 和 rsync 传输的字节数大致相同,而 scp 更简单。请注意,rsync 默认根据大小和修改时间决定需要检查的内容。因此,如果文件内容已更改,但大小和时间戳没有变化,则需要 --checksum,rsync 才能发现变化。
Kermit 为什么将文件编码为可打印文本,而不是直接发送原始字节?
因为它面向的连接是通往大型机的终端线路,而不是字节管道。这些链路可能只有 7 位,且大型机的终端驱动程序会处理控制字符,而不是将其原样传递。Kermit 会将控制字节和高位字节编码为可打印字符,避免链路中的设备对其做出响应。这种编码会增大二进制文件在线路上的大小,但与文件传输后损坏相比,这是正确的取舍。