Apache FTP客户端被动模式调用listFiles出现27错误的排查与解决
问题
我尝试使用Apache FTP客户端在被动模式下获取FTP文件列表,相关代码如下:
连接方法
public void connect() throws IOException { ftp.connect(host, port); if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) { ftp.disconnect(); throw new IOException("Exception in connecting to FTP Server"); } ftp.enterLocalPassiveMode(); if (!ftp.login(login, password)) { ftp.disconnect(); throw new IOException("Failed to login ftp server"); } }
获取文件列表方法
public List<String> getPaths(String path, String date) { try { val listFiles = ftp.listFiles(path);//1 val listNames = ftp.listNames(path);//2 return null; } catch (IOException e) { e.printStackTrace(); } }
我循环调用getPaths方法遍历1000+文件夹,使用ftp.listFiles(path)时,每千余次调用会出现1-2次异常:
org.apache.commons.net.MalformedServerReplyException: Could not parse response code. Server Reply: 27 Entering Passive Mode (10,141,166,68,126,88).
使用ftp.listNames(path)则无此异常;移除ftp.enterLocalPassiveMode()改用默认主动模式也无异常,但我无法使用主动模式,仍需调用ftp.listFiles(path)。
相关日志:
PASV 227 Entering Passive Mode (95,167,245,94,129,168) LIST /out/published/Jamalo-Nenetckii_AO/purchaseNoticeEP/daily 150 Here comes the directory listing. 226 Directory send OK. PASV 227 Entering Passive Mode (95,167,245,94,129,0) LIST /out/published/Jamalo-Nenetckii_AO/purchaseNoticeIS/daily 150 Here comes the directory listing. 226 Directory send OK. PASV org.apache.commons.net.MalformedServerReplyException: Could not parse response code. Server Reply: 27 Entering Passive Mode (10,141,166,68,118,198).
Wireshark抓包显示服务器返回了错误的IP地址。请问该异常的原因是什么,如何解决?
原因分析
- 服务器响应格式异常:正常被动模式响应以
227开头,但异常时服务器返回27 Entering Passive Mode...,缺失了开头的2,导致Apache FTP客户端无法解析标准FTP响应码,直接抛出MalformedServerReplyException。 - 高并发下服务器不稳定:遍历1000+文件夹属于高频请求,服务器负载过高或网络波动时,FTP服务生成被动模式响应出现BUG,既返回了错误IP,又出现了响应码截断的情况。
- API处理逻辑差异:
listFiles需要建立数据连接获取详细文件元信息,对被动模式的依赖更强;而listNames处理逻辑更简洁,可能复用连接或跳过了部分易触发异常的流程,主动模式则是客户端发起数据连接,服务器响应逻辑不同,因此都未触发该问题。
解决方案
- 增加异常重试逻辑
针对偶发的MalformedServerReplyException,在调用listFiles时添加重试机制,重试2-3次基本能覆盖大部分异常场景:
public List<String> getPaths(String path, String date) { int retryTimes = 3; while (retryTimes > 0) { try { FTPFile[] files = ftp.listFiles(path); List<String> result = new ArrayList<>(); if (files != null) { for (FTPFile file : files) { result.add(file.getName()); } } return result; } catch (MalformedServerReplyException e) { retryTimes--; if (retryTimes == 0) { e.printStackTrace(); return null; } // 重试前短暂休眠,降低服务器压力 try { Thread.sleep(100); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } } catch (IOException e) { e.printStackTrace(); return null; } } return null; }
- 手动指定被动模式IP
如果服务器返回的被动模式IP错误,可通过setPassiveHost强制指定正确的FTP服务器IP,绕过服务器返回的错误地址:
public void connect() throws IOException { ftp.connect(host, port); if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) { ftp.disconnect(); throw new IOException("Exception in connecting to FTP Server"); } ftp.enterLocalPassiveMode(); // 手动设置被动模式使用的服务器IP,替换为实际FTP主机地址 ftp.setPassiveHost(host); if (!ftp.login(login, password)) { ftp.disconnect(); throw new IOException("Failed to login ftp server"); } }
升级Apache Commons Net版本
检查当前使用的版本,旧版本可能存在对异常响应处理不健壮的问题,升级到最新稳定版(如3.10.0+),能优化对非标准响应的兼容逻辑。降低请求频率
在遍历文件夹的循环中增加短暂休眠(50-100ms),减少服务器瞬时负载,降低响应异常的概率。
内容的提问来源于stack exchange,提问作者user19282140
相关产品推荐
相关产品推荐

