公司要接入一个三方的应用,需要使用FTP下载对方的数据文件,因为对方测试环境没有提供FTP的下载渠道,所有开发过程为了模拟FTP下载,我在自己的阿里云linux上搭建了一个FTP服务器(采用vsftpd),起初在本机windows上测试一切正常,发布到公司测试环境后,FTPClient.listFiles一直是空,并且ftpClient.retrieveFile下载不到文件
FTPClient ftpClient = new FTPClient();
ftpClient.connect(ftp.getUrl(), ftp.getPort());
ftpClient.setControlEncoding(ftp.getCharset());
ftpClient.login(ftp.getUser(), ftp.getPassword());
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
FTPFile[] ftpFiles = ftpClient.listFiles("/");
log.info("文件个数:{}", ftpFiles.length);
for (FTPFile ftpFile : ftpFiles) {
log.info("FTP文件名:{},文件大小:{}", ftpFile.getName(), ftpFile.getSize());
}
File file = new File(ftp.getLocalFileName().substring(0, ftp.getLocalFileName().lastIndexOf(File.separator)));
if (!file.exists()) {
file.mkdirs();
}
log.info("FTP下载开始");
FileOutputStream outputStream = new FileOutputStream(new File(ftp.getLocalFileName()));
ftpClient.retrieveFile(ftp.getTargetFileName(), outputStream);
log.info("FTP下载结束");
这段代码在我本机windows运行一切正常,但发布到公司的测试环境后,读不到FTP服务器的文件,公司的测试环境是linux,使用docker部署
上网搜了一下,有些文章讲述了类似经历,解决方案是调整FTP为被动模式,即在进行FTP操作前,添加下面一行代码:
ftpClient.enterLocalPassiveMode();
调整后,本地测了几下,发现程序会随机地卡在这行代码,既不报错也不继续执行,而通过这行代码后,下载到的文件也有可能是0KB,小概率下会正常下载完毕,于是找文章仔细看了下FTP主动模式与被动模式的区别,大致如下:
FTP连接会建立两条TCP连接,一条用来发布命令,一条用来传输数据,对于FTP服务端而言,命令通道默认是21端口,数据通道默认是20端口,具体分为主动模式和被动模式:
客户端通过端口N连接到服务器的21端口,并告诉服务端,我开启了一个N+1端口(实际可能不是N+1,但会和N比较接近)用来传输数据,然后服务端通过20端口,主动连接客户端的N+1端口,进行数据传输
该模式为FTP的默认模式
客户端通过端口N连接到服务器的21端口,并告诉服务端,我采用的是被动模式,请提供一个端口给我用来传输数据,然后服务器告诉客户端:“OK,我给你分配的端口是M,可以开始传输数据”。然后客户端通过N+1(同上,可能不是但比较接近)端口主动连接服务器的M端口,进行数据传输
通过比较,可以发现两种方式的区别,主要在于传输数据的连接由谁来建立,如果由服务器端来建立(主动模式),那么对于客户端来说,需要N+1端口对外开放,由于这个端口是随机的,所以客户端需要开放一定范围的端口,这在网络限制比较严格的生产环境比较困难,也有风险。当然这种模式对于服务器端比较方便,因为服务器端只需要开通21端口的入方向和20端口的出方向;如果由客户端来建立(被动模式),那么客户端通过N+1端口访问服务端,一般不会有网络问题,因为正常情况下不会限制出方向的访问,而服务器端需要开放更多端口,这个可以通过FTP服务器配置,指定端口的范围
由于调整为被动模式后,本地连接FTP成功与否具有一定随机性,所以猜测会不会是服务器端提供的端口M不通,导致客户端建立数据通道的时候连接失败,先到阿里云上看下安全策略:
之前为了测试方便,把入方向的端口范围调整为15-30000,正常来讲这个范围的端口已经满足各种组件的应用了,但是FTP的端口是随机的,有可能超出了30000,导致客户端无法正常连接,这里的解决方案是设定FTP服务端被动模式的端口范围
打开vsftp的配置文件vsftpd.conf,添加以下配置
pasv_enable=YES
pasv_min_port=3000
pasv_max_port=5000
开启被动模式,配置被动模式的端口范围为3000到5000,即当客户端以被动模式连接服务端时,服务端会在3000-5000的范围内随机分配一个端口,供客户端连接并进行数据传输
重启FTP服务器:
systemctl restart vsftpd.service
再次调用下载接口,程序可以稳定地完成下载