由于需要客户需求,需要把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(newString(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("远程文件不存在");
returnfalse;
}
longftp_file_size = files[0].getSize();
logger.info("远程文件的大小:"+ ftp_file_size);
File local_file =newFile(local);
InputStream in =null;
OutputStream out =null;
//判断本地文件是否存在,如果存在判断是否需要断点续传
if(local_file.exists()) {
logger.info("本地文件存在,判断是否需要续传.....");
longlocal_file_size = local_file.length();
logger.info("本地文件大小:"+ local_file_size);
// 判断本地文件大小是否大于远程文件大小
if(local_file_size >= ftp_file_size) {
logger.info("本地文件大于等于远程文件,不需要续传");
returntrue;
}
// 进行断点续传
logger.info("开始断点续传.....");
ftpClient.setRestartOffset(local_file_size);
try{
//根据文件名字得到输入留
in = ftpClient.retrieveFileStream(newString(remote.getBytes("UTF-8"),"iso8859-1"));
//建立输出流,设置成续传
out =newFileOutputStream(local_file,true);
byte[] b =newbyte[1024];
//已下载的大小
longdowland_size = local_file_size;
intflag =0;
longcount;
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) {
intnum = 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);
returnfalse;
}catch(FileNotFoundException e) {
logger.error("未找到文件", e);
return false;
}catch(IOException e) {
logger.error("出现io异常,请检查网络", e);
return false; }
}else{
logger.info("本地文件不存在,此文件为新文件,开始下载.....");
byte[] b =newbyte[1024];
try{
//得到输入输出流
in = ftpClient.retrieveFileStream(newString(remote.getBytes("UTF-8"),"iso8859-1"));
out =newFileOutputStream(local);
//已下载的大小
longdowland_size =0;
intflag =0;
longcount;
if((ftp_file_size % b.length) ==0) {
count = (ftp_file_size / b.length);
}else{
count = (ftp_file_size / b.length) +1;
}
while(true) {
intnum = 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客户端进行多线程下载的时候配置的。