因为须要客户需求,须要把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客户端进行多线程下载的时候配置的。