由于需要客户需求,需要把Ftp上的所有文件下载到本地,包括目录和文件。看到文件数量的时候我就哭了。。
几万个文件,晕死。这个地方我遇到的几个困难我会一一说明。
下载commons-net包我就不多说了。。
.首先先写客户端下载的工具类,就是封装了关于客户端连接FTP,断开,查询文件,以及下载文件等方法。
这里我借鉴了网上我忘了具体名字的里面的一些代码,所以有雷同请大家不要介意。。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
importjava.io.File;
importjava.io.FileNotFoundException;
importjava.io.FileOutputStream;
importjava.io.IOException;
importjava.io.InputStream;
importjava.io.OutputStream;
importjava.io.UnsupportedEncodingException;
importjava.net.SocketException;
importorg.apache.commons.net.ftp.FTP;
importorg.apache.commons.net.ftp.FTPClient;
importorg.apache.commons.net.ftp.FTPClientConfig;
importorg.apache.commons.net.ftp.FTPFile;
importorg.apache.commons.net.ftp.FTPReply;
importorg.apache.log4j.Logger;
|
1
2
3
4
5
|
publicclassFtpHelper {
// 日志
privatestaticLogger logger = Logger.getLogger(FtpHelper.class);
// 客户端操作
publicFTPClient ftpClient =newFTPClient();
|
这里最主要的就是一个FTPClient 类,他就是客户端进行Ftp连接的关键类。
1.连接FTP服务器方法:这里面有个ConfigInfo就是取配置文件的信息,一会我会放出来。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
/**
* 连接FTP服务器
*
* @param hostname 服务器名称
* @param port 端口
* @param user 用户名
* @param password 密码
* @return 是否连接上
*/
publicbooleanconnect(String hostname,intport, 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 =newFTPClientConfig(ConfigInfo.getSystem());
// 设置服务器端语言 中文 "zh"
conf.setServerLanguageCode(ConfigInfo.getServerLanguageCode());
// 判断服务器返回值,验证是否已经连接上
if(FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {
// 验证用户名密码
if(ftpClient.login(user, password)) {
logger.info("已经连接到ftp......");
returntrue;
}
logger.error("连接ftp的用户名或者密码错误......");
// 取消连接
disconnect();
}
}catch(SocketException e) {
logger.error("连接不上ftp....", e);
// e.printStackTrace();
}catch(IOException e) {
logger.error("出现io异常....", e);
// e.printStackTrace();
}
returnfalse;
}
|
这里需要注意的就是一个被动模式的设置和一个传输编码的设置
2.关闭FTP连接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
/**
* 关闭FTP连接
*/
publicvoiddisconnect() {
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文件 包括了目录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
/**
* 查询当前工做空间下的所有ftp文件包括了目录
*
* @return 文件数组
*/
publicFTPFile[] getFilesList() {
logger.info("进入查询ftp所有文件方法.....");
try{
FTPFile[] ftpFiles = ftpClient.listFiles();
intnum =0;
for(FTPFile ftpFile : ftpFiles) {
if(!ftpFile.isFile()) {
continue;
}
num++;
}
logger.info("进入查询上文件个数.."+ num);
logger.info("进入查询ftp所有文件方法结束.....");
returnftpFiles;
}catch(IOException e) {
logger.error("查询ftp上文件失败...", e);
returnnull;
}
}
|
4.变更工作目录.变更工作目录其实就是去下级目录。因为ftp连接上默认是在根目录上,所以如果你想访问根目录下其他目录
里面的内容,需要变更到那个目录。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
/**
* 变更工作目录
*
* @param remoteDir 变更到的工作目录
*/
publicbooleanchangeDir(String remoteDir) {
try{
logger.info("变更工作目录为:"+ remoteDir);
ftpClient.changeWorkingDirectory(newString(remoteDir
.getBytes("UTF-8"),"iso8859-1"));
returntrue;
}catch(IOException e) {
logger.error("变更工作目录为"+ remoteDir +"失败", e);
returnfalse;
}
}
|
这里需要注意一下remoteDir就是目录的名称,FTP一次只能变更到一个目录下,然后这里有个编码问题,
这里我不知道为啥我设置了传输是UTF-8的还需要转码,本来这里我没有转码的,但是之后的测试,出现了各种错误,
才发现这里出现了问题,需要一UTF-8解码,然后iso8859-1编码。。。(如果有达人解决这个问题,请联系我。。。)
6.变更工作目录到其父目录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
/**
* 变更工作目录到其父目录
*
* @return 是否变更成功
*/
publicbooleanchangeToParentDir() {
try{
logger.info("变更工作目录到父目录");
returnftpClient.changeToParentDirectory();
}catch(IOException e) {
logger.error("变更工作目录到父目录出错", e);
returnfalse;
}
}
|
7.从服务器上下载特定文件,重头戏。。。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
|
<span> /**
* 从服务器上下载特定文件
*
* @param remote
* @param local
* @return
*/
publicBoolean 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</span><span style="line-height:19px;">false</span>;<span> }catch(IOException e) {
logger.error("出现io异常,请检查网络", e);
return</span><span style="line-height:19px;">false</span>;<span> }
}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</span><span style="line-height:19px;">false</span>;<span> }catch(IOException e) {
logger.error("出现io异常请检查网络", e);
return</span><span style="line-height:19px;">false</span>;<span> }
}
returntrue;
}</span>
|
这里遇到的问题
a.编码解码,在ftpClient.listFiles(new String(remote.getBytes("UTF-8"), "iso8859-1"));
验证文件是否在服务器上存在的时候,需要转码。
同理得到输入流的时候:
1
|
<span>ftpClient.retrieveFileStream(newString(remote.getBytes("UTF-8"),"iso8859-1"));</span>
|
也需要转码。
b.断点续传,其实就是看某个文件如果服务器存在之后,如果本地存在就判断,本地文件和服务器文件的大小。
如果本地大于等于服务器,就不需要。。。其实大于这种情况咋产生滴,是服务器那边的事情。。
(有可能这里会有人说不合理,大于的情况就说明变化了,应该重新传,但是我们这里客户的需求是服务器端文件,只会做增量操作,不会修改删除。所以。。。。当然,大家可以根据自己的情况进行变更)
如果小于,就从本地文件大小的位置开始续传,ftpClient.setRestartOffset(local_file_size);这个方法可以设置
输入流开始的位置。之后就是传输问题了。
c.由于本来用户是要求有个比例的,但是后来取消了,因为文件数量太大了。。。所以这里就是装饰了。。。
到此工具类写完了。哦对了,还有个东西
1
|
}
|
这样就齐了。。
然后就是配置文件和ConfigInfo类,就是自己写的一个读取配置文件的类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
|
importjava.io.IOException;
importjava.io.InputStream;
importjava.util.Properties;
importorg.apache.log4j.Logger;
/**
*
* 配置文件读取类
* @author houly
*
*/
//这个配制成 文件更改时间
publicclassConfigInfo {
/**FTP服务器地址或名称*/
privateString ftpHostName;
/**FTP服务器ftp服务端口*/
privateintport;
/**FTP服务器登陆用户名*/
privateString username;
/**FTP服务器登陆密码*/
privateString password;
/**FTP下载到本地路径*/
privateString ftpDownLoadDir;
/**FTPserver操作系统*/
privateString system;
/**FTP语言*/
privateString serverlanguagecode;
/**FTP多线程下载线程数量*/
privateintthreadNUM;
privatefinalString _URL ="/config.properties";
//日志
Logger logger = Logger.getLogger(ConfigInfo.class);
privatestaticConfigInfo config =newConfigInfo();
publicstaticString getFtpHostName() {
returnconfig.ftpHostName;
}
publicstaticintgetPort() {
returnconfig.port;
}
publicstaticString getUsername(){
returnconfig.username;
}
publicstaticString getPassword(){
returnconfig.password;
}
publicstaticString getFtpDownLoadDir(){
returnconfig.ftpDownLoadDir;
}
publicstaticString getSystem(){
returnconfig.system;
}
publicstaticString getServerLanguageCode(){
returnconfig.serverlanguagecode;
}
publicstaticintgetThreadNUM(){
returnconfig.threadNUM;
}
privateConfigInfo() {
loadConfig();
}
privatevoidloadConfig() {
InputStream is =this.getClass().getResourceAsStream(_URL);
Properties pro =newProperties();
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);
}
}
|
1
2
3
4
5
6
7
8
9
|
ftphostname=localhost
port=21
username=admin
password=admin
ftpdownloaddir=d\:/360
system=WINDOWS
#system=UNIX
serverlanguagecode=zh
threadNUM=30
|
其他的就没了,然后这里有个线程数,是ftp客户端进行多线程下载的时候配置的。