1. 程式人生 > >java實現FTP上傳下載:FTPClient類進行FTP上傳下載大檔案(包含導致假死現象)

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 工具類