目录
主动模式与被动模式
和 HTTP 不同的是,FTP 采用两条链路通信,一条链路只做控制,一条链路只做数据传输。控制链路传输控制命令,数据链路传输文件内容或命令的输出结果,控制链路 server 监听 well-known 端口(默认 21),数据链路端口使用视传输模式而定。
- 被动模式(默认)
所谓被动模式,是指 server 端的数据连接是被动打开,即 server 监听某端口,client 连接该端口。对于这种模式,数据端口采用随机端口,server 通过控制链路告知 client 当前数据端口是多少。例如:
ftp> put /tmp/test.txt /0/test.txt
local: /tmp/test.txt remote: /0/test.txt
---> TYPE I
200 Switching to Binary mode.
---> PASV
227 Entering Passive Mode (10,105,51,240,211,175)
server 会在 PASV 命令的 reply 中指明本次传输采用 54191(211*256+175) 端口进行数据传输。
- 主动模式
主动模式则刚好相反,即 server 连接 client 主动打开数据链路,且 server 使用的端口固定,默认为 20.
弱网络传输
笔者所在的一个项目中,要将游戏安装包从大陆传输到日本的 CDN 源站,出海网络质量无保障,时好时坏,经常遇到传输到一半就超时失败,超时之后用户又得重传。于是就有了“断点续传”的想法,FTP 协议里有两个命令能实现断点续传,一个是 REST(restart), 另一个是 APPE(append), 这里不详细展开。
断点续传
断点续传也就是超时之后获取已经上传的大小(SIZE 命令),重试未上传的部分。
但是,在 FTP 的设计里面,数据链路只能传数据,没有确认包,因为 send 不会超时(假设发送缓冲区没满),也就无法实现“超时重传”。因此,要实现“超时重传”,必须要有 receive. 在 FTP 的设计里面,客户端关闭数据连接,如果 server 收到了 FIN 包,server 会在控制连接上回复 226 Transfer complete.
响应,客户端收到该响应则表示 server 已收到 FIN 包,进一步说明,文件极有可能已经收到(之所以说是“极有可能”是因为有些包可能滞留在网络中,比 FIN 包晚到)。因此,就有了如下步骤
- send file content on data connection
- close data connection
- receive reply on command connection
- receive timed out
- get remote file size
- send file content again
分块确认
在网络质量比较差的情况下,发送一个 300M 的文件,可能 server 只收到了 100M, 但是客户端却要发送完整个文件才关闭数据连接,多余的 200M 只会让网络更拥堵。因此,为了提升传输效率,我们引入了“分块确认”的机制。整个过程如下:
- send 1 block
- close data connection
- receive reply on command connection
- receive timed out
- get remote file size
- send 1 block from offset “remote size”
- …
防火墙与代理
笔者所在的项目中,有这样一种场景:文件从印尼、越南、泰国等地传到 Akamai CDN 源站。东南亚的网络基础设施大多比较差,Akamai 提供的 CDN 源站域名会解析到日本或美国,在有些时段,网络质量非常差。由于新加坡到日本、印尼等到新加坡的网络质量较好,考虑在新加坡搭建一台 proxy. 文件从印尼等传到新加坡,再从新加坡传到 Akamai CDN 源站。
在之前的实践中,通过端口转发的方式实现 HTTP 的代理效果较好,因此复用了端口转发的方式。
被动模式
由于被动模式 server 的数据端口不固定,因此,端口转发无法实现。主动模式
主动模式是服务端主动连接客户端,因此要考虑防火墙的设置,由于 server 是一个域名,接入了很多 IP, 因此防火墙的设置是个问题。
此外,client 处于 NAT 网络之中,开启的数据端口通过控制链路的控制消息传递,即通过应用层消息传递地址,而 NAT 不会处理应用层数据,因此,外网的 FTP server 接收到的 client 监听的数据传输地址是一个内网地址,FTP server 无法主动连接该地址(不过如果 client 所在的网络环境使用了应用层网关(ALG), 该内网地址会被替换成外网地址,这个问题会随之解决).
因此,端口转发行不通。于是尝试了使用 HAProxy 的 TCP 代理,但在传大文件时会出现控制链路断开的问题,抓包分析之后,发现 TCP 代理只代理了控制链路,数据传输依然是直连 real server. 原因很简单,因为数据端口是在控制链路的消息中给出,TCP 代理无法修改应用层的内容。因此,要想实现 FTP 代理,必须替换应用层消息。也即是说,FTP 代理无法用三层代理来实现,只能通过应用层代理来实现。