问题背景:
最近有一个系统对接需求,采用了古老的ftp交换文件方式来对接。于是我用了commons-net包的3.6版本来进行ftp的连接和文件的传输。连接ftp成功,登录也没问题,但是在传输文件的时候会卡住,程序没有往下走,一段时间后抛异常。传输文件的代码如下(顺便提一下如果你连都连不上,那先理清架构,问下你们运维是不是用了代理,如果用了代理,java代码里面需要设置使用代理连接)
//初始化ftpclient
initFtpClient();
//切换路径
ftpClient.changeWorkingDirectory(pathname);
InputStream inputStream = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] dataBytes;
try{
//卡在下面这一行!!
FTPFile[] ftpFiles = ftpClient.listFiles();
for(FTPFile file : ftpFiles){
if(file.getName().startsWith(filenamePrefix)){
inputStream = ftpClient.retrieveFileStream(pathname+file.getName());
break;
}
}
byte[] buffer = new byte[1024];
int len;
while ((len = inputStream.read(buffer)) != -1 ) {
baos.write(buffer, 0, len);
}
dataBytes = baos.toByteArray();
return dataBytes;
}catch (Exception e){
//....
}finally {
//....
}排查过程:
1.登录应用服务器,执行ftp各种操作排除架构,代理服务器配置等环境问题
由于公司的网络架构有非常严格的安全限制,所以实际上,应用服务器和目标ftp服务器之间往往还要经过各种代理服务器,所以首先要做的就是登录到应用服务器使用ftp命令连接ftp服务器(具体的ip和端口号不一定就是对方直接暴露给你的ip和端口号,需要咨询运维确认)执行目录切换,文件下载,文件上传的操作,排除架构上和代理服务器配置上可能存在的问题。附这几个操作的ftp命令:
#切换到xxx目录
cd xxx
#下载abcd.xls文件
get abcd.xls
#上传当前目录下的yyy.txt文件,当前目录可以用lcd查看
put yyy.txt一顿操作下来,目录切换和文件上传下载都正常,那么架构环境代理配置有问题的可能性就比较小了。命令可以正常下载文件而程序又不行,那就可能是程序的问题。可是程序只是卡住,抛出了一个空指针异常,没有任何指导意义,这种时候只能祭出最终兵器:抓包。
2.抓包分析程序的行为和命令行行为上的区别,找到问题所在
由于异常信息非常有限,我们只能使用抓包的命令(命令:tcpdump),分析我写的java程序连ftp传输文件和我直接使用ftp命令连接服务器传输文件所发出的命令的区别。把抓到的包用wireshark打开,只看协议为ftp的数据(过滤条件:ftp or ftp-data)。按时间顺序看下来,可以看到ftp客户端发送了port命令,使用的是主动模式,客户端在发出LIST请求后,服务端返回了连接失败。

问题原因:
到这里,问题的原因就很明显了:因为我的ftp客户端使用了主动模式(如果你想了解ftp的主动被动模式的区别,可以拉到最后),ftp服务器会主动尝试连接ftp客户端port命令所指定的随机端口建立数据传输连接,但是由于我的ftp客户端与ftp服务器之间还隔着防火墙,ftp客户端的防火墙没有开通(这么多随机端口想必要开防火墙也够喝一壶了),因此就出现了425 Failed to establish connection的返回。
解决办法:
使用主动模式还是被动模式,都是由客户端来决定的。除非你愿意开放一堆端口,不然的话最佳的解决办法就是设置客户端使用被动模式。我用的commons-net包的3.6版本,在初始化ftp客户端的时候就可以直接在代码中设定客户端使用被动模式,代码如下,设置被动模式后,问题解决。
//设置被动传输模式
ftpClient.setFileTransferMode(ftpClient.BINARY_FILE_TYPE);
ftpClient.enterLocalPassiveMode();扩展阅读:
ftp主动模式和被动模式:https://www.cnblogs.com/mawanglin2008/articles/3607767.html