使用ftpClient下载超大文件心得

最近接到一个任务:使用java程序下载ftp上的文件;

说明:下载ftp文件的教程网上一大堆,但我要下载的文件大多超过两个G的压缩包,用网上那一套代码时行不通;

遇到的问题:文件下载到五百多兆时,在执行

c = in.read(bytes)

时返回值为-1,意味着其实没有下载完成,而程序却通知我下载已完成,实际解压的时候得知该文件并不是正确的压缩包。

解决思路:赶在通知我下载完成之前,自行了断,然后重新登录断点续传。(虽然看起来有点不太靠谱,但是它行得通啊吐舌头

贴出我的工具类,以便大家使用,有什么可以升级改进的地方还请大神们多多指点。

其中ftp:

                <dependency>
		    <groupId>commons-net</groupId>
		    <artifactId>commons-net</artifactId>
    		    <version>3.3</version>
		</dependency>


import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.SocketTimeoutException;

import javax.annotation.PreDestroy;

import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 类名称:FtpUtils 类描述:操作ftp工具类2
 * 
 * @author wangsen 创建时间:2018年4月11日 下午6:14:32
 * @email <@to wangsen_job@163.com>
 */
public class FtpUtils {

	/** 日志 */
	private static final Logger logger = LoggerFactory
			.getLogger(FtpUtils.class);

	// ftp服务器地址
	public String hostname = "10.0.0.25"; //TODO 换上你的地址
	
	// ftp服务器端口号默认为21
	public Integer port = 21;
	// ftp登录账号
	public String username = "username"; //TODO 换上你的用户名
	
	// ftp登录密码
	public String password = "pwd"; //TODO 换上你的密码

	public FTPClient ftpClient = null;
	
	/** 单例 */
	private static FtpUtils instance = null;
	
	/** 五分钟的毫秒数 */
	private static final long TEN_MINUTE = 5 * 60 * 1000L;
	
	/** 
	 * 功能描述:  获取连接实例
	 * 创建人: wangsen  
	 * 创建时间:2018年5月16日 上午9:51:59  
	 * 修改人                        修改时间                          修改内容	 
	 * @return   
	 * FtpUtils 
	 */ 	
	public synchronized static FtpUtils getInstance() {
		if (instance == null) {
			instance = new FtpUtils();
		} 
		return instance;
	}
	
	/** 私有化构造器 */
	private FtpUtils() {
		super();
	}
	
	/** 
	 * 功能描述:  注销登录
	 * 创建人: wangsen  
	 * 创建时间:2018年5月16日 上午10:59:02  
	 * 修改人                        修改时间                          修改内容	    
	 * void 
	 */ 	
	public static void logout() {
		if (instance != null && instance.ftpClient != null) {
			try {
				instance.ftpClient.logout();
			} catch (Exception e) {
				logger.debug("登出失败:", e); // 可以忽略的错误
			}
			try {
				instance.ftpClient.disconnect();
			} catch (Exception e) {
				logger.debug("关闭链接失败:", e); // 可以忽略的错误
			}
			instance.ftpClient = null;
			logger.info("成功退出登录");
		} else {
			logger.info("并没有登录,无需退出");
		}
	}

	/**
	 * 初始化ftp服务器
	 */
	public void initFtpClient() {
		if (ftpClient != null && ftpClient.isConnected() && ftpClient.isAvailable()) {
			ftpClient.enterLocalPassiveMode(); // 防止假卡死
			return;
		}
		ftpClient = new FTPClient();
		ftpClient.setControlEncoding("utf-8");
		try {
			ftpClient.connect(hostname, port); // 连接ftp服务器
			ftpClient.login(username, password); // 登录ftp服务器
			int replyCode = ftpClient.getReplyCode(); // 是否成功登录服务器
			if (!FTPReply.isPositiveCompletion(replyCode)) {
				logger.info("ftp服务器登录失败:" + this.hostname + ":"
						+ this.port);
			}
			logger.info("ftp服务器登录成功:" + this.hostname + ":"
					+ this.port);
			ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
			
			//设置Linux环境:如果ftp服务器部署在linux系统中,此处注释应该打开,若为Windows服务器则不需要
//            FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_UNIX);
//            ftpClient.configure(conf);
			
			ftpClient.enterLocalPassiveMode(); // 防止假卡死
			ftpClient.setRemoteVerificationEnabled(false);
			ftpClient.setConnectTimeout(10 * 1000); // 登录十秒超时
			ftpClient.setDataTimeout(1 * 60 * 1000); // 获取数据超时 一分钟
			ftpClient.setReceiveBufferSize(1024 * 1024);
			ftpClient.setBufferSize(1024 * 1024);
		} catch (Exception e) {
			logger.error("登录出错:", e);
		}
	}

	
	@PreDestroy
	public void closeConn() {
		if (ftpClient != null) {
			try {
				ftpClient.logout();
			} catch (IOException e1) {
				logger.debug("关闭连接错误", e1); // 可以忽略的错误
			}
			try {
				ftpClient.disconnect();
			} catch (IOException e) {
				logger.debug("关闭连接错误", e); // 可以忽略的错误
			}
		}
	}
	
	/**
	 * 功能描述: 改变目录路径 创建人: wangsen 创建时间:2018年4月12日 上午10:28:28 修改人 修改时间 修改内容
	 * 
	 * @param directory
	 *            要改变的路径
	 * @return boolean
	 */
	public boolean changeWorkingDirectory(String directory) {
		boolean flag = true;
		try {
			flag = ftpClient.changeWorkingDirectory(directory);
			if (flag) {
				logger.debug("进入文件夹" + directory + " 成功!");
			} else {
				logger.info("进入文件夹" + directory + " 失败!开始创建文件夹");
			}
		} catch (IOException ioe) {
			ioe.printStackTrace();
		}
		return flag;
	}

	/**
	 * * 下载文件 *
	 * @WARNNING 该方法可能不太好用,我用的是下面下载文件夹的方法,若想下载单独的文件请参考FtpUtils.downloadDir(String, String, String)
	 * @param pathname
	 *            FTP服务器文件目录 *
	 * @param filename
	 *            文件名称 *
	 * @param localpath
	 *            下载后的文件路径 *
	 * @return
	 */
	public boolean downloadFile(String pathname, String filename,
			String localpath) {
		boolean flag = false;
		OutputStream os = null;
		try {
			initFtpClient();
			// 切换FTP目录
			ftpClient.changeWorkingDirectory(pathname);
			FTPFile[] ftpFiles = ftpClient.listFiles();
			for (FTPFile file : ftpFiles) {
				if (filename.equalsIgnoreCase(file.getName())) {
					File localFile = new File(localpath + "/" + file.getName());
					if (localFile.exists()) { // 已经存在则删除
						localFile.delete();
					}
					// 然后再创建
					localFile.createNewFile();
					os = new FileOutputStream(localFile);
					logger.info("下载中:" + filename);
					ftpClient.retrieveFile(file.getName(), os);
					os.close();
				}
			}
			flag = true;
			logger.info("下载文件成功:{}", filename);
		} catch (Exception e) {
			logger.error("下载文件失败:{}", filename, e);
		} finally {
			if (null != os) {
				try {
					os.close();
				} catch (Exception e) {
					logger.debug("关闭连接异常"); // 可以忽略的错误类型
				}
			}
		}
		return flag;
	}

	/**
	 * 功能描述: 下载目录下所有文件 创建人: wangsen 创建时间:2018年4月11日 下午6:47:10 修改人 修改时间 修改内容
	 * 
	 * @param pathname
	 *            FTP服务器文件目录 *
	 * @param localpath
	 *            下载后的文件路径 * void
	 * @param dirName 根目录名称
	 */
	public boolean downloadDir(String pathname, String localpath, String dirName) {
		boolean flag = false;
		try {
			initFtpClient();
			// 切换FTP目录
			ftpClient.changeWorkingDirectory(pathname);
			FTPFile[] ftpFiles = ftpClient.listFiles();
			for (FTPFile file : ftpFiles) {
				if (".".equals(file.getName()) || "..".equals(file.getName())) {
					continue;
				}
				FileOutputStream os = null;
				long size = file.getSize();
				File localFile = new File(localpath + "/" + file.getName());
				if (localFile.length() == size) {
					logger.info("文件{}已下载完成", localFile.getName());
					continue;
				}
				
				if (localFile != null && localFile.exists() && localFile.isFile() && localFile.length() > 0 && localFile.length() < size) {
					// 需要断点续传
					os = new FileOutputStream(localFile, true);
				} else {
					if (!checkFile(localFile)) {
						return false;
					}
					os = new FileOutputStream(localFile);
				}
				logger.info("正在下载文件:{},总大小:{}", file.getName(), size);
				long start = System.currentTimeMillis();
				try {
					InputStream in = null;
					try {
						byte[] bytes = new byte[1024 * 32];
			            long step = size /100;
			            long process = 0L;
			            long localSize = localFile.length();
			            if (localSize > size) {
							logger.error("本地文件大于服务器文件,终止下载");
							checkFile(localFile);
							return false;
						}
			            if (localSize > 0) {
			            	ftpClient.setRestartOffset(localSize);
						}
			            int c;
						in = ftpClient.retrieveFileStream(file.getName());
						if (in == null) {
							logger.info("连接异常,将退出登录");
							try {
								ftpClient.logout();
							} catch (Exception e) {
								logger.debug("关闭连接错误", e); // 可以忽略的错误
							}
							try {
								ftpClient.disconnect();
							} catch (Exception e) {
								logger.debug("关闭连接错误", e); // 可以忽略的错误
							}
							ftpClient = null;
							logger.info("退出登录成功");
							return false;
						}
						
						while((c = in.read(bytes))!= -1){
			                os.write(bytes, 0, c);
			                localSize += c;
			                long nowProcess = localSize /step;
			                if(size > 50000000 && nowProcess > process){ // 大于50兆才显示进度
			                    process = nowProcess;
			                    logger.info("{}%", process);
			                    if (System.currentTimeMillis() - start > TEN_MINUTE) { // 大于指定时间
									logger.info("时间已到,未下载部分将在下次任务中下载");
									try {
										ftpClient.logout();
									} catch (Exception e) {
										logger.debug("关闭连接错误", e); // 可以忽略的错误
									}
									try {
										ftpClient.disconnect();
									} catch (Exception e) {
										logger.debug("关闭连接错误", e); // 可以忽略的错误
									}
									return false;
								}
			                }  
			            }
						logger.info("文件下载完成到:{}", localpath + "/" + file.getName());
					} catch (SocketTimeoutException e) {
						logger.info("下载出错:", e);
						return false;
					} catch (Exception e) {
						logger.error("下载出错:", e);
						return false;
					} 
					finally {
						try {
							in.close();
						} catch (Exception e) {
							logger.debug("关闭流错误", e); // 可以忽略的错误
						}
					}
				} catch (Exception e) {
					logger.error("下载文件错误:{}", file.getName(), e);
					return false;
				} finally {
					try {
						os.close();
					} catch (Exception e) {
						logger.debug("关闭连接异常"); // 可以忽略的错误类型
					}
				}
			}
			flag = true;
		} catch (Exception e) {
			logger.error("下载文件错误:{}", pathname, e);
			return false;
		}
		return flag;
	}

	/** 
	 * 功能描述:  初始化文件
	 * 创建人: wangsen  
	 * 创建时间:2018年4月12日 下午2:26:58  
	 * 修改人                        修改时间                          修改内容	 
	 * @param localFile   
	 * void 
	 */ 	
	private boolean checkFile(File localFile) {
		if (localFile.exists()) { // 已经存在则删除
			localFile.delete();
		}
		// 然后再创建
		localFile.mkdirs();
		if (localFile.exists()) {
			localFile.delete();
		}
		try {
			localFile.createNewFile();
			return true;
		} catch (IOException e) {
			logger.error("创建文件失败", e);
			return false;
		}
	}

	/** 
	 * 功能描述:  获取列表
	 * 创建人: wangsen  
	 * 创建时间:2018年4月12日 上午11:02:57  
	 * 修改人                        修改时间                          修改内容	 
	 * @param path		路径
	 * @return   
	 * FTPFile[] 
	 */ 	
	public FTPFile[] getDirList(String path) {
		try {
			initFtpClient();
			// 切换FTP目录
			ftpClient.changeWorkingDirectory(path);
			return ftpClient.listFiles();
		} catch (Exception e) {
			logger.error("获取目录【{}】列表错误:", path, e);
		}
		return null;
	}

	/** 
	 * 功能描述:  获取目录下的第一个文件夹
	 * 创建人: wangsen  
	 * 创建时间:2018年4月12日 上午11:07:51  
	 * 修改人                        修改时间                          修改内容	 
	 * @param pathName		目录
	 * @return   
	 * FTPFile 
	 */ 	
	public FTPFile getFirst(String pathName) {
		try {
			initFtpClient();
			// 切换FTP目录
			ftpClient.changeWorkingDirectory(pathName);
			FTPFile[] listFiles = ftpClient.listFiles();
			if (listFiles == null || listFiles.length == 0) {
				return null;
			}
			for (FTPFile ftpFile : listFiles) {
				if (".".equals(ftpFile.getName()) || "..".equals(ftpFile.getName())) {
					continue;
				} else {
					return ftpFile;
				}
			}
			return null;
		} catch (Exception e) {
			logger.error("获取目录【{}】列表错误:", pathName, e);
		}
		return null;
	}
}

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值