java實現FTP上傳下載:FTPClient類進行FTP上傳下載大檔案(包含導致假死現象)
介紹:
FTPClient是一個強大的FTP上傳下載工具,可以實現各種方式的ftp檔案傳輸,可以支援上傳下載各種大檔案(已在實踐中使用),而且存在官網使使用者方便的使用這個工具等等。
1.首先,程式中設定ftp請求方式為被動模式,即程式去請求ftp伺服器,要求伺服器來開啟一個埠,讓客戶端傳輸檔案。這是基本的,但是無法避免上傳阻塞問題。
2.其次,設定連線超時,資料傳輸超時等等,也無法避免阻塞。
3.接著,呼叫上傳或下載後,呼叫stream.close()方法,同樣無法避免阻塞,這是基本的操作,說明不了什麼。
4.最後,我在程式中加入了上傳下載listener(ftpclient自己實現的一個監聽器),只是為了顯示上傳下載進度。
結論:1.這些都算基本的功能,但是程式如果長時間上傳或下載一個大檔案,或者長時間多工上傳多個任務,還是會出現阻塞現象。
解決方式:我使用的解決方案是,去掉監聽器,然後重新執行,程式竟然不再阻塞!具體原因我也不太清楚,可能與流有關,在此做個筆記,
希望能幫助到與我遇到同樣問題的人兒。
2.如果出現org.apache.commons.net.io.CopyStreamException: IOException caught while copying
或者Caused by: java.net.SocketException: Connection reset by peer: socket write error 的異常,
你需要檢測:(1)連線是否及時關閉,先logout,再disconnection。(2)磁碟是否已滿,這個也是經常發生的。
注意:附件中為自己實現的ftp上傳下載工具,包含自己寫的超時監聽功能(自帶的導致阻塞問題)
</pre><pre name="code" class="java">import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.SocketException; import java.util.HashMap; import java.util.Map; import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPFile; import org.apache.commons.net.ftp.FTPReply; import org.apache.log4j.Logger; import com.gg.service.vo.FtpTransferFileVo; /** * * FTP相關操作工具類 * * @author zzj * @date May 30, 2016 10:54:20 AM */ public class FtpUtil { /** * 日誌記錄器 */ private static Logger logger = Logger.getLogger(FtpUtil.class); /** * ftp伺服器ip地址 */ private String ftpHost = "192.168.128.1"; /** * ftp伺服器登入名 */ private String ftpUserName = "pds"; /** * ftp伺服器登入密碼 */ private String ftpPassword = "<span style="font-family: Arial, Helvetica, sans-serif;">pds</span>"; /** * ftp伺服器所在的根目錄 */ private String ftpRootDir=""; /** * ftp伺服器埠號 */ private int ftpPort = 21; /** * 檔案最大存放天數,0表示永久儲存,其他表示到達存放指定存放天數後進行刪除檔案 */ private int maxStoreDays=0; /** * 流緩衝區存放的大小 */ private final static int UPLOAD_BUFFER_SIZE=1024*1024; /** * 掃描總個數(總監控時間=SCAN_NUMBER*ONE_SECOND*SCAN_SECOND) * 目前:5*60*(10S)=3000S=50Min */ public static final int SCAN_NUMBER = 5*60; /** * 一秒 */ public static final int ONE_SECOND = 1000; /** * 每次休眠時間 */ public static final int SCAN_SECOND = 10; public static void main(String[] args) { FtpUtil ftpUtil = new FtpUtil(); String absPath ="/home/test/aa2/aad/"; String fileString="D:/software/MyEclipse_10.7.zip"; //String url ="http://pc2-dx1.newasp.net/soft/good/eclipse-standard-kepler-SR1-win32.zip"; String urlString="http://192.168.92.7/cloud/v1/versionDFS/DCC/AL817_ROW_T_SECBOOT/hq6753_65u_b1o_l1/S1La40_USR_S275_1604251950_MP3V2_16G_ROW/S1La40_USR_S275_1604251950_MP3V2_16G_ROW_DCC/S1La40_USR_S275_1604251950_MP3V2_16G_ROW.rar"; File tempFile = new File(urlString); String fileName = tempFile.getName(); InputStream io =HttpClientUtil.getUrlInputStream(urlString); try { //io = new FileInputStream(new File(fileString)); ftpUtil.uploadHttpFile(io,absPath,fileName); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * 將指定遠端url的網路檔案上傳到ftp目錄 * @param remoteStorDir ftp的絕對路徑(目錄+檔名),這個路徑必須以/結尾 * @param url 檔案所在的http的絕對路徑 * @author zzj * @throws Exception */ public boolean uploadHttpFile(InputStream in,String remoteStorDir,String fileName) throws Exception { logger.info("start uploading file to ftp..."); boolean result = false; FTPClient ftpClient = null; String absFilePath=null; try { // 建立並登陸ftp伺服器 ftpClient = this.getFTPClient(ftpHost, ftpPassword, ftpUserName, ftpPort); /*ftpClient.setDataTimeout(1000*1000); ftpClient.setConnectTimeout(connectTimeout)*/ // 設定ftp上傳進度監聽器 //ftpClient.setCopyStreamListener(createListener()); // 設定PassiveMode被動模式-向服務傳送傳輸請求 ftpClient.enterLocalPassiveMode(); // 設定以二進位制流的方式傳輸 ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE); //新增超時監控執行緒 new DemaonThread(ftpClient,fileName).start(); // 處理目錄的操作 createDirs(ftpClient, remoteStorDir); logger.info("being upload,please waiting..."); absFilePath = remoteStorDir+fileName; // 最後,將io流上傳到指定的目錄 result = ftpClient.storeFile(absFilePath, in); in.close(); logger.info("the upload result is:"+result+" and file path:" +absFilePath); } catch (Exception e) { logger.error("upload file failed,", e); //刪除有可能產生的臨時檔案 if (absFilePath!=null) { ftpClient.deleteFile(absFilePath); } throw new Exception(e); } finally { try { /*if (!result && absFilePath!=null) { ftpClient.deleteFile(absFilePath); System.out.println("刪除成功!"); }*/ ftpClient.logout(); if (ftpClient.isConnected()) { ftpClient.disconnect(); ftpClient=null; } } catch (IOException e) { logger.error("最後操作ftp期間發生異常,",e); } } return result; } /** * 獲取FTPClient物件 * * @param ftpHost FTP主機伺服器 * @param ftpPassword FTP 登入密碼 * @param ftpUserName FTP登入使用者名稱 * @param ftpPort FTP埠 預設為21 * @return */ public FTPClient getFTPClient(String ftpHost, String ftpPassword, String ftpUserName, int ftpPort) { FTPClient ftpClient = null; try { // 連線FTP伺服器 ftpClient = new FTPClient(); logger.info("start connect ftp server."); ftpClient.connect(ftpHost); //登入到ftp伺服器 ftpClient.login(ftpUserName, ftpPassword); ftpClient.setBufferSize(UPLOAD_BUFFER_SIZE); //超時時間 int defaultTimeoutSecond=30*60 * 1000; ftpClient.setDefaultTimeout(defaultTimeoutSecond); ftpClient.setConnectTimeout(defaultTimeoutSecond ); ftpClient.setDataTimeout(defaultTimeoutSecond); logger.info("connect and login ftp server success."); // 設定每次上傳的大小 /*ftpClient.setBufferSize(UPLOAD_BUFFER_SIZE);*/ if (!FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) { logger.info("未連線到FTP,使用者名稱或密碼錯誤。"); ftpClient.logout(); ftpClient.disconnect(); ftpClient=null; } else { System.out.println("FTP connect success!"); } } catch (SocketException e) { logger.error("FTP的IP地址可能錯誤,請正確配置。", e); } catch (IOException e) { logger.error("FTP的埠錯誤,請正確配置。", e); } return ftpClient; } /** * 監控ftpclient超時守護執行緒 * @author zzj * @date Jun 3, 2016 6:19:18 PM */ private class DemaonThread extends Thread { private FTPClient ftpClient; private String fileName; int num = 0; Long start = System.currentTimeMillis()/1000; /** * @param ftpClient2 * @param fileName */ public DemaonThread(FTPClient ftpClient2, String fileName) { this.ftpClient = ftpClient2; this.fileName=fileName; } @Override public void run() { while (num < SCAN_NUMBER && ftpClient.isConnected()) { try { System.out.println(fileName+",monitor ftpclient start..."+(System.currentTimeMillis()/1000-start)+" S." ); Thread.sleep(ONE_SECOND*SCAN_SECOND); num++; System.out.println(fileName+",monitor ftpclient timeout..." ); } catch (Exception e) { e.printStackTrace(); } } try { System.out.println(fileName+",monitor ftpclient timeout finish..."); ftpClient.logout(); if (ftpClient.isConnected()) { ftpClient.disconnect(); ftpClient=null; } } catch (Exception e) { System.out.println(fileName+",**********monitor happend error,"+e.getMessage()); } } } /** * 上傳進度監聽器(可能導致阻塞) * @return 監聽器物件 * @author zzj */ /*public CopyStreamListener createListener() { return new CopyStreamListener() { private long start = System.currentTimeMillis()/1000; @Override public void bytesTransferred(CopyStreamEvent event) { System.out.println("transfeerred"); bytesTransferred(event.getTotalBytesTransferred(), event.getBytesTransferred(), event.getStreamSize()); } @Override public void bytesTransferred(long totalBytesTransferred, int bytesTransferred, long streamSize) { System.out.println("Spended time: "+(System.currentTimeMillis()/1000-start)+" seconds."); System.out.println("transfered total bytes:" + FileUtil.FormetFileSize(totalBytesTransferred) + ",per transfeerred types:" + bytesTransferred+",stream size="+streamSize); } }; }*/ /** * 建立指定的目錄 * @param ftpClient ftp客戶端 * @param remoteUpLoadPath ftp伺服器目錄 * @throws IOException * @author zzj */ public static void createDirs(FTPClient ftpClient, String remoteUpLoadPath) throws IOException { //根據路徑逐層判斷目錄是否存在,如果不存在則建立 //1.首先進入ftp的根目錄 ftpClient.changeWorkingDirectory("/"); String[] dirs = remoteUpLoadPath.split("/"); for (String dir : dirs) { //2.建立並進入不存在的目錄 if (!ftpClient.changeWorkingDirectory(dir)) { int num = ftpClient.mkd(dir); System.out.println(num); ftpClient.changeWorkingDirectory(dir); System.out.println("進入目錄成功:"+dir); } } } /** * 從ftp下載檔案 * @param sourceFtpFileDir 檔案所在ftp目錄 * @param version 所要取得檔案的版本號 * @return 下載後的目錄名字 * @throws Exception 異常 * @author zzj */ public FTPFile[] listFtpFiles(FTPClient ftpClient ,String sourceFtpFileDir) throws Exception{ logger.info("start downloading file from ftp..."); FTPFile[] ftpFiles = null; try { // 設定ftp上傳進度監聽器 //ftpClient.setCopyStreamListener(createListener("downloading... ")); // 設定PassiveMode被動模式-向服務傳送傳輸請求 ftpClient.enterLocalPassiveMode(); // 設定以二進位制流的方式傳輸 ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE); //新增超時監控執行緒 new DemaonThread(ftpClient,"downloading... ").start(); //得到指定目錄下所有的檔案,指定需要獲取的檔案的字尾名 ftpFiles =ftpClient.listFiles(sourceFtpFileDir, new FTPFileSuffixFilter(".rar,.zip")); logger.info("list file size is:"+ftpFiles==null?0:ftpFiles.length); } catch (Exception e) { logger.error("download file failed,", e); throw new Exception(e); } finally { } return ftpFiles; } /** * 需要下載的檔案的檔案流 * @param ftpClient ftp客戶端物件 * @param ftpFiles 獲取到的所有檔案 * @param version 版本還 * @return 檔案流 * @author zzj * @throws IOException */ public Map<String, Object> downloadFtpFile(String fileDir,String version,FtpTransferFileVo transferFileVo) throws Exception{ Map<String, Object> map = new HashMap<String, Object>(); InputStream inputStream = null; FTPClient ftpClient =null; try { ftpClient = this.getFTPClient(this.getFtpHost(), this.getFtpPassword(), this.getFtpUserName(),this.getFtpPort());; FTPFile[] ftpFiles=this.listFtpFiles(ftpClient, this.getFtpRootDir()); int num=0; String fileName = null; for(FTPFile file : ftpFiles){ String tn = file.getName(); tn = tn.substring(0,tn.lastIndexOf(".")); if (file.isFile()&& tn.equals(version)) { fileName = file.getName(); num++; } } if (num==0) { throw new Exception("沒有找到對應版本【"+version+"】的檔案號."); }else if(num>1){ throw new Exception("對應版本"+version+"的檔案大於1個,個數為:"+num+",請人工處理"); } //設定檔案路徑 transferFileVo.setFileName(fileName); transferFileVo.setHttpFileUrl(fileDir+fileName); boolean flag =ftpClient.changeWorkingDirectory(fileDir); if (!flag) { throw new Exception("指定的目錄不存在或ftp無法開啟,路徑為:"+fileDir); } System.out.println("進入目錄:"+fileDir+"結果:"+flag ); //執行下載檔案 inputStream = ftpClient.retrieveFileStream(fileDir+fileName); map.put("error","false"); } catch (Exception e) { logger.error("發生異常,",e); map.put("error", e.getMessage()); }finally{ map.put("stream", inputStream); map.put("ftpClient", ftpClient); } return map; } /** * 所有工廠對應的ftp目錄列表 */ public static Map<String,String> factoryMap = new HashMap<String, String>(); static{ factoryMap.put("ss", "ss"); } /** * 根據工廠名得到對應的目錄 * @param factory 工廠名 * @return ftp子目錄 * @author zzj */ public static String getFactoryDir(String factory){ String dirName=null; for (Map.Entry<String,String> entry : factoryMap.entrySet()) { String cname=entry.getKey(); if (factory.contains(cname)) { dirName=entry.getValue(); break; } } return dirName; } public String getFtpHost() { return ftpHost; } public void setFtpHost(String ftpHost) { this.ftpHost = ftpHost; } public String getFtpUserName() { return ftpUserName; } public void setFtpUserName(String ftpUserName) { this.ftpUserName = ftpUserName; } public String getFtpPassword() { return ftpPassword; } public void setFtpPassword(String ftpPassword) { this.ftpPassword = ftpPassword; } public int getFtpPort() { return ftpPort; } public void setFtpPort(int ftpPort) { this.ftpPort = ftpPort; } public String getFtpRootDir() { return ftpRootDir; } public void setFtpRootDir(String ftpRootDir) { this.ftpRootDir = ftpRootDir; } public int getMaxStoreDays() { return maxStoreDays; } public void setMaxStoreDays(int maxStoreDays) { this.maxStoreDays = maxStoreDays; } }
package com.gg.service.util;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPFileFilter;
/**
* 獲取檔案時過濾檔案字尾的過濾器
* @author zzj
* @date Jun 7, 2016 3:37:36 PM
*/
public class FTPFileSuffixFilter implements FTPFileFilter{
/**
* 傳過來的字尾資訊(多個時用英文逗號隔開)
*/
private String fileSuffix;
/**
* 初始化引數
* @param subffix 字尾
*/
public FTPFileSuffixFilter(String subffix){
this.fileSuffix=subffix;
}
/* (non-Javadoc)
* @see org.apache.commons.net.ftp.FTPFileFilter#accept(org.apache.commons.net.ftp.FTPFile)
*/
@Override
public boolean accept(FTPFile file) {
String filename = file.getName();
String[] strings = fileSuffix.split(",");
boolean flag = false;
for(String suf:strings){
if (filename.lastIndexOf(suf) != -1) {
flag = true;
}
}
return flag;
}
}
commons-net只能實現ftp、tftp、ftps等各類ftp的上傳下載,如要實現sftp的上傳下載,請參考JSCH這個開源jar包,下面為其中的一個示例:
http://www.linux178.com/Java/ftp-sftp-progress.html?utm_source=tuicool&utm_medium=referral 進度示例
http://www.cnblogs.com/dongliyang/p/4173583.html 工具類