今天項目中要下載快錢的對帳單,快錢對帳單文件的FTP服務器是Unix系統,connectServer方法中已鏈接成功,reply code:220。html
可是問題是download方法中的ftpClient.listFiles(remote)不能找到具體某一文件,若是使用ftpClient.listFiles()而不具體指定某一遠程文件時能夠列舉出全部的文件,包括remote這個須要下載的文件。且代碼中的ftpClient.retrieveFile(remote, out);會出現長時間的等待,upNewStatus爲false,最終會拋出:FTP response 421 received. Server closed connection.apache
public static boolean connectServer(String host, int port, String user, String password, String defaultPath) throws SocketException, IOException { ftpClient = new FTPClient(); // org.apache.commons.net.MalformedServerReplyException: Could not parse response code. // Server Reply: SSH-2.0-OpenSSH_7.2 // ftpClient.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out))); // 設置以二進制方式傳輸 ftpClient.setDataTimeout(5000); ftpClient.setConnectTimeout(connectTimeout); ftpClient.setControlEncoding("UTF-8"); ftpClient.connect(host, port); log.info("Connected to " + host + "."); log.info("FTP server reply code:" + ftpClient.getReplyCode()); if (FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) { if (ftpClient.login(user, password)) { // Path is the sub-path of the FTP path if (defaultPath != null && defaultPath.length() != 0) { ftpClient.changeWorkingDirectory(defaultPath); } return true; } } disconnect(); return false; } public static File download(String remote, String local) throws IOException { log.info("remote={},local={}", remote, local); File downloadFile = null; // 檢查遠程文件是否存在 FTPFile[] files = ftpClient.listFiles(remote); if (files.length == 0) { log.info("遠程文件不存在: {}, {}", remote, files); return downloadFile; } File f = new File(local); OutputStream out = null; if (f.exists()) { f.delete(); } try { //ftpClient.enterLocalPassiveMode(); out = new FileOutputStream(f); boolean upNewStatus = ftpClient.retrieveFile(remote, out); out.flush(); if (upNewStatus) { downloadFile = f; } } catch (Exception e) { log.error(e.getMessage(), e); } finally { if (out != null) { out.close(); } } return downloadFile; }
FTP須要在本身測試服務器開通21端口,這個已經叫運維開通了。且須要快錢把咱們這邊測試服務器ip加入快錢的白名單,不然是連接不到那邊的FTP服務器的。服務器
開始一直覺得是快錢那邊提供的用戶是否須要什麼文件鏈接權限,定位了好久的問題,最後搜索問題才發現對FTP鏈接模式和原理不清楚致使。運維
首先FTP分2中模式:主動模式(port)和被動模式(pasv).FTP標準命令TCP端口號爲21,Port方式數據端口爲20tcp
無論哪一種模式,都必須經過21這個端口創建起到FTP的管道鏈接,經過這個通道發送命令。測試
port模式:1.經過tcp的21端口創建起通道spa
2.客戶端在此通道發起PORT命令,併產生一個隨機非特殊的端口號N(1023<N<65536)給到FTP服務器。.net
3.此時客戶端監聽N+1端口(N+1>=1025,不必定是N端口+1),同時經過21的通道發送命令通知FTP服務器客戶點經過此端口接受數據傳輸。code
4.FTP服務器接收到上一步的響應後經過本身的數據源端口20,去連接遠程的客戶端的N+1端口(此時是FTP服務端主動發起的一個端口連接)orm
5.若是此時客戶端的防火牆策略是不能隨意外部連接內部服務器的端口,則會形成上一步出現數據端連接失敗!
6.若是沒有上一步的狀況,FTP客戶端則會接收到服務端響應並返回響應信息,則創建起了數據連接通道。
pasv模式:1.經過tcp的21端口創建起通道
2.但與主動方式的FTP不一樣,客戶端不會提交PORT命令並容許服務器來回連它的數據端口,而是提交 PASV命令.會產生兩個隨機非特殊的端口N(1023<N<65536) 和N+1給到FTP服務器。其中N端口跟主動模式同樣,會把N給到服務端的遠程的21端口。至關於FTP服務端被動接受數據端口號而不是以前port模式的主動發起鏈接
3.FTP服務端會則會打開N+1的端口號
4.客戶端發起N+1端口的連接,並創建數據連接。
port模式:
pasv模式:
上面的圖都省略了創建tcp的21端口這個步驟。
正是由於以前採用主動模式,可是測試服務器防火牆阻止了快錢發起的數據端口的鏈接。也就是port模式的第5步出現問題。
於是FTPClient.listFiles(remote)或者FTPClient.retrieveFile(remote)方法時獲取不了數據,就中止在那裏,什麼反應都沒有,出現假死狀態。
解決辦法:在調用這兩個方法以前,調用FTPClient.enterLocalPassiveMode();
這個方法的意思就是每次數據鏈接以前,ftp client告訴ftp server:數據鏈接的端口號已經告訴你了,你只需被動接受數據鏈接的請求就行
參考:http://www.cnblogs.com/xiaohh/p/4789813.html
http://blog.csdn.net/u010154760/article/details/45458219