因爲須要客戶需求,須要把Ftp上的全部文件下載到本地,包括目錄和文件。看到文件數量的時候我就哭了。。java
幾萬個文件,暈死。這個地方我遇到的幾個困難我會一一說明。apache
下載commons-net包我就很少說了。。windows
.首先先寫客戶端下載的工具類,就是封裝了關於客戶端鏈接FTP,斷開,查詢文件,以及下載文件等方法。數組
這裏我借鑑了網上我忘了具體名字的裏面的一些代碼,因此有雷同請你們不要介意。。服務器
import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.SocketException; import org.apache.commons.net.ftp.FTP; import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPClientConfig; import org.apache.commons.net.ftp.FTPFile; import org.apache.commons.net.ftp.FTPReply; import org.apache.log4j.Logger;
public class FtpHelper { // 日誌 private static Logger logger = Logger.getLogger(FtpHelper.class); // 客戶端操做 public FTPClient ftpClient = new FTPClient();
(這是類的開頭,嘿嘿我的習慣。。不喜歡上傳文件)
這裏最主要的就是一個FTPClient 類,他就是客戶端進行Ftp鏈接的關鍵類。網絡
1.鏈接FTP服務器方法:這裏面有個ConfigInfo就是取配置文件的信息,一會我會放出來。多線程
/** * 鏈接FTP服務器 * * @param hostname 服務器名稱 * @param port 端口 * @param user 用戶名 * @param password 密碼 * @return 是否鏈接上 */ public boolean connect(String hostname, int port, String user, String password) { logger.info("進行FTP鏈接......"); logger.info("hostname:" + hostname + " port:" + port + " user" + user + " password:" + password); try { // 鏈接服務器 ftpClient.connect(hostname, port); // 設置傳輸編碼 ftpClient.setControlEncoding("UTF-8"); // 設置客戶端操做系統類型,爲windows 其實就是"WINDOWS" 雖然沒用到 FTPClientConfig conf = new FTPClientConfig(ConfigInfo.getSystem()); // 設置服務器端語言 中文 "zh" conf.setServerLanguageCode(ConfigInfo.getServerLanguageCode()); // 判斷服務器返回值,驗證是否已經鏈接上 if (FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) { // 驗證用戶名密碼 if (ftpClient.login(user, password)) { logger.info("已經鏈接到ftp......"); return true; } logger.error("鏈接ftp的用戶名或者密碼錯誤......"); // 取消鏈接 disconnect(); } } catch (SocketException e) { logger.error("鏈接不上ftp....", e); // e.printStackTrace(); } catch (IOException e) { logger.error("出現io異常....", e); // e.printStackTrace(); } return false; }
這裏須要注意的就是一個被動模式的設置和一個傳輸編碼的設置工具
2.關閉FTP鏈接測試
/** * 關閉FTP鏈接 */ public void disconnect() { logger.info("進入FTP鏈接關閉鏈接方法..."); // 判斷客戶端是否鏈接上FTP if (ftpClient.isConnected()) { // 若是鏈接上FTP,關閉FTP鏈接 try { logger.info("關閉ftp鏈接......"); ftpClient.disconnect(); } catch (IOException e) { logger.error("關閉ftp鏈接出現異常......", e); // e.printStackTrace(); } } }
這個就是判斷是否鏈接上,若是鏈接上斷開鏈接。this
3. 查詢當前工作空間下的全部ftp文件 包括了目錄
/** * 查詢當前工作空間下的全部ftp文件包括了目錄 * * @return 文件數組 */ public FTPFile[] getFilesList() { logger.info("進入查詢ftp全部文件方法....."); try { FTPFile[] ftpFiles = ftpClient.listFiles(); int num = 0; for (FTPFile ftpFile : ftpFiles) { if (!ftpFile.isFile()) { continue; } num++; } logger.info("進入查詢上文件個數.." + num); logger.info("進入查詢ftp全部文件方法結束....."); return ftpFiles; } catch (IOException e) { logger.error("查詢ftp上文件失敗...", e); return null; } }
4.變動工做目錄.變動工做目錄其實就是去下級目錄。由於ftp鏈接上默認是在根目錄上,因此若是你想訪問根目錄下其餘目錄
裏面的內容,須要變動到那個目錄。
/** * 變動工做目錄 * * @param remoteDir 變動到的工做目錄 */ public boolean changeDir(String remoteDir) { try { logger.info("變動工做目錄爲:" + remoteDir); ftpClient.changeWorkingDirectory(new String(remoteDir .getBytes("UTF-8"), "iso8859-1")); return true; } catch (IOException e) { logger.error("變動工做目錄爲" + remoteDir + "失敗", e); return false; } }
這裏須要注意一下remoteDir就是目錄的名稱,FTP一次只能變動到一個目錄下,而後這裏有個編碼問題,
這裏我不知道爲啥我設置了傳輸是UTF-8的還須要轉碼,原本這裏我沒有轉碼的,可是以後的測試,出現了各類錯誤,
才發現這裏出現了問題,須要一UTF-8解碼,而後iso8859-1編碼。。。(若是有達人解決這個問題,請聯繫我。。。)
6.變動工做目錄到其父目錄
/** * 變動工做目錄到其父目錄 * * @return 是否變動成功 */ public boolean changeToParentDir() { try { logger.info("變動工做目錄到父目錄"); return ftpClient.changeToParentDirectory(); } catch (IOException e) { logger.error("變動工做目錄到父目錄出錯", e); return false; } }
很少說,變動到上級目錄。。。
7.從服務器上下載特定文件,重頭戲。。。
/** * 從服務器上下載特定文件 * * @param remote * @param local * @return */ public Boolean downloadonefile(String remote, String local) { //System.out.println(ftpClient.isConnected()); logger.info("開始下載....."); logger.info("遠程文件:" + remote + " 本地文件存放路徑:" + local); // 設置被動模式 ftpClient.enterLocalPassiveMode(); // 設置以二進制方式傳輸 try { ftpClient.setFileType(FTP.BINARY_FILE_TYPE); } catch (IOException e) { logger.error("設置以二進制傳輸模式失敗...", e); } // 檢查FTP上是否存在文件 FTPFile[] files = null; try { files = ftpClient.listFiles(new String(remote .getBytes("UTF-8"), "iso8859-1")); logger.info(files==null?"不存在":"存在"+files.length); logger.info("搜索出來文件名爲:"); for(FTPFile file:files){ logger.info(file.getName()); } } catch (IOException e) { logger.error("檢查遠程文件是否存在失敗....", e); } if (files == null || files.length ==0) { logger.error("遠程文件不存在"); return false; } long ftp_file_size = files[0].getSize(); logger.info("遠程文件的大小:" + ftp_file_size); File local_file = new File(local); InputStream in = null; OutputStream out = null; //判斷本地文件是否存在,若是存在判斷是否須要斷點續傳 if (local_file.exists()) { logger.info("本地文件存在,判斷是否須要續傳....."); long local_file_size = local_file.length(); logger.info("本地文件大小:" + local_file_size); // 判斷本地文件大小是否大於遠程文件大小 if (local_file_size >= ftp_file_size) { logger.info("本地文件大於等於遠程文件,不須要續傳"); return true; } // 進行斷點續傳 logger.info("開始斷點續傳....."); ftpClient.setRestartOffset(local_file_size); try { //根據文件名字獲得輸入留 in = ftpClient.retrieveFileStream(new String(remote .getBytes("UTF-8"), "iso8859-1")); //創建輸出流,設置成續傳 out = new FileOutputStream(local_file, true); byte[] b = new byte[1024]; //已下載的大小 long dowland_size = local_file_size; int flag = 0; long count; if (((ftp_file_size - dowland_size) % b.length) == 0) { count = ((ftp_file_size - dowland_size) / b.length); } else { count = ((ftp_file_size - dowland_size) / b.length) + 1; } while (true) { int num = in.read(b); //System.out.println(num); if (num == -1) break; out.write(b, 0, num); dowland_size += num; flag++; //打印下載進度 if (flag % 1000 == 0) { logger.info("下載進度爲:" + (dowland_size * 100 / ftp_file_size) + "%"); } } if (count == flag) { logger.info("下載進度爲:100%"); } in.close(); out.close(); } catch (UnsupportedEncodingException e) { logger.error("字符轉換失敗", e); return false; } catch (FileNotFoundException e) { logger.error("未找到文件", e); return false; } catch (IOException e) { logger.error("出現io異常,請檢查網絡", e); return false; } } else { logger.info("本地文件不存在,此文件爲新文件,開始下載....."); byte[] b = new byte[1024]; try { //獲得輸入輸出流 in = ftpClient.retrieveFileStream(new String(remote .getBytes("UTF-8"), "iso8859-1")); out = new FileOutputStream(local); //已下載的大小 long dowland_size = 0; int flag = 0; long count; if ((ftp_file_size % b.length) == 0) { count = (ftp_file_size / b.length); } else { count = (ftp_file_size / b.length) + 1; } while (true) { int num = in.read(b); if (num == -1) break; out.write(b, 0, num); dowland_size += num; flag++; //打印下載進度 if (flag % 1000 == 0) { logger.info("下載進度爲:" + (dowland_size * 100 / ftp_file_size) + "%"); } // ftp_file_size } if (count == flag) { logger.info("下載進度爲:100%"); } //關閉輸入輸出流 in.close(); out.close(); } catch (UnsupportedEncodingException e) { logger.error("字符轉換失敗", e); return false; } catch (IOException e) { logger.error("出現io異常請檢查網絡", e); return false; } } return true; }
這裏借鑑了一下別人的代碼,若有雷同,請不要介意啦。。
這裏遇到的問題
a.編碼解碼,在ftpClient.listFiles(new String(remote.getBytes("UTF-8"), "iso8859-1"));
驗證文件是否在服務器上存在的時候,須要轉碼。
同理獲得輸入流的時候:
ftpClient.retrieveFileStream(new String(remote.getBytes("UTF-8"), "iso8859-1"));
也須要轉碼。
b.斷點續傳,其實就是看某個文件若是服務器存在以後,若是本地存在就判斷,本地文件和服務器文件的大小。
若是本地大於等於服務器,就不須要。。。其實大於這種狀況咋產生滴,是服務器那邊的事情。。
(有可能這裏會有人說不合理,大於的狀況就說明變化了,應該從新傳,可是咱們這裏客戶的需求是服務器端文件,只會作增量操做,不會修改刪除。因此。。。。固然,你們能夠根據本身的狀況進行變動)
若是小於,就從本地文件大小的位置開始續傳,ftpClient.setRestartOffset(local_file_size);這個方法能夠設置
輸入流開始的位置。以後就是傳輸問題了。
c.因爲原本用戶是要求有個比例的,可是後來取消了,由於文件數量太大了。。。因此這裏就是裝飾了。。。
到此工具類寫完了。哦對了,還有個東西
}
這樣就齊了。。
而後就是配置文件和ConfigInfo類,就是本身寫的一個讀取配置文件的類。
import java.io.IOException; import java.io.InputStream; import java.util.Properties; import org.apache.log4j.Logger; /** * * 配置文件讀取類 * @author houly * */ //這個配製成 文件更改時間 public class ConfigInfo { /**FTP服務器地址或名稱*/ private String ftpHostName; /**FTP服務器ftp服務端口*/ private int port; /**FTP服務器登錄用戶名*/ private String username; /**FTP服務器登錄密碼*/ private String password; /**FTP下載到本地路徑*/ private String ftpDownLoadDir; /**FTPserver操做系統*/ private String system; /**FTP語言*/ private String serverlanguagecode; /**FTP多線程下載線程數量*/ private int threadNUM; private final String _URL = "/config.properties"; //日誌 Logger logger = Logger.getLogger(ConfigInfo.class); private static ConfigInfo config = new ConfigInfo(); public static String getFtpHostName() { return config.ftpHostName; } public static int getPort() { return config.port; } public static String getUsername(){ return config.username; } public static String getPassword(){ return config.password; } public static String getFtpDownLoadDir(){ return config.ftpDownLoadDir; } public static String getSystem(){ return config.system; } public static String getServerLanguageCode(){ return config.serverlanguagecode; } public static int getThreadNUM(){ return config.threadNUM; } private ConfigInfo() { loadConfig(); } private void loadConfig() { InputStream is = this.getClass().getResourceAsStream(_URL); Properties pro = new Properties(); try { pro.load(is); } catch (IOException e) { logger.error("config.properties配置文件加載錯誤", e); } ftpHostName = pro.getProperty("ftphostname"); port = Integer.valueOf(pro.getProperty("port")); username=pro.getProperty("username"); password=pro.getProperty("password"); ftpDownLoadDir=pro.getProperty("ftpdownloaddir"); system = pro.getProperty("system"); serverlanguagecode = pro.getProperty("serverlanguagecode"); threadNUM = Integer.valueOf(pro.getProperty("threadNUM")); logger.info("配置文件信息....."); logger.info("ftpHostName:"+ftpHostName); logger.info("port:"+port); logger.info("username:"+username); logger.info("password:"+password); logger.info("ftpDownLoadDir:"+ftpDownLoadDir); logger.info("system:"+system); logger.info("serverlanguagecode:"+serverlanguagecode); logger.info("threadNUM:"+threadNUM); } }
配置文件 config.properties文件
ftphostname=localhost port=21 username=admin password=admin ftpdownloaddir=d\:/360 system=WINDOWS #system=UNIX serverlanguagecode=zh threadNUM=30
其餘的就沒了,而後這裏有個線程數,是ftp客戶端進行多線程下載的時候配置的。