由于需要客户需求,需要把Ftp上的所有文件下载到本地,包括目录和文件。看到文件数量的时候我就哭了。。
几万个文件,晕死。这个地方我遇到的几个困难我会一一说明。
下载commons-net包我就不多说了。。
.首先先写客户端下载的工具类,就是封装了关于客户端连接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(); } } }
这个就是判断是否连接上,如果连接上断开连接。
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客户端进行多线程下载的时候配置的。