用Java实现FTP批量大文件上传下载
原文:http://tech.it168.com/j/2007-10-18/200710182058687.shtml
本文介绍了在Java中,如何使用Java现有的可用的库来编写FTP客户端代码,并开发成Applet控件,做成基于Web的批量、大文件的上传下载控件。文章在比较了一系列FTP客户库的基础上,就其中一个比较通用且功能较强的j-ftp类库,对一些比较常见的功能如进度条、断点续传、内外网的映射、在Applet中回调JavaScript函数等问题进行详细的阐述及代码实现,希望通过此文起到一个抛砖引玉的作用。
一、 概述
笔者在实施一个项目过程中出现了一种基于Web的文件上传下载需求。在全省(或全国)各地的用户,需要将一些文件上传至某中心的文件服务器上。这些文件是用于一些大型的工程建设,可能涉及到上千万甚至上亿的建设工程。文件具有三个鲜明的特征:一是文件大,可能达到50M;二是文件数量多,有可能15个左右;三是数据安全性方面要求数字签名及数据加密。
首先考虑到是基于HTTP的传输方式。但笔者通过比较很快发现满足上面的需求:
1:用HTTP协议上传,似乎更适合web编程的方便性;上传小于1M文件速度要比用FTP协议上传文件略快。但对于批量及大文件的传输可能无能为力。当然,它也有它的优势,如不像FTP那样,必须在服务器端启动一个FTP服务。
2:用FTP协议上传文件大于1M的文件速度比HTTP快。文件越大,上传的速度就比HTTP上传的速度快数倍。而且用java编写程序;FTP比HTTP方便。
笔者曾经使用VB也写过ActiveX控件来进行批量文件的上传下载,其功能也很强大。只是由于没有对CAB文件或OCX进行专门的数字签名,因此需要进行客户端烦琐的设置,如设置安全站点、降低客户端的安全级别等等,因而放弃了些方案。
同时考虑到在需在客户端对文件进行数字签名及数据加密,决定采用Applet的方式实现。。文件上传之前,在客户端可以获取本地USBKEY密钥信息,完成对上传文件的加密和签名处理。虽然采用Applet要求在客户端安装JRE运行时环境,给客户端的管理及使用带来一度的不方便性,但是相对起如此大量的文件及文件的安全性,这也许已经算是比较小的代价了。
总结一下运行的环境为:
FTP服务器端:Serv-U,专业的FTP服务器端程序,网上有现成的软件下载,当然读者也可能自己写一个服务器端的FTP文件接收程序来进行解释。如果没有特殊要求或功能的话,Serv-U应该可以满足我们一般上传下载的需求了;
客户端:Java applet,当年让Java大火了一把的号称与微软的ActiveX相提并论的技术当然,现在Java出了JavaFX,是不是Applet的替代品呢?
应用环境:Internet网,最终目的。
二、 Java FTP客户端库的选择
让我们设想这样一个情形--我们想写一个纯Java的从一个远程计算机上运行的FTP服务器上传下载文件的应用程序;我们还希望能够得到那些供下载的远程文件的基本文件信息,如文件名、数据或者文件大小等。
尽管从头开始写一个FTP协议处理程序是可能的,并且也许很有趣,但这项工作也是困难、漫长并且存在着潜在的危险。因为我们不愿意亲自花时间、精力、或者金钱去写这样的一个处理程序,所以我们转而采用那些已经存在的可重用的组件。并且很多的库存在于网上。
找一个优秀的适合我们需要的Java FTP 客户端库并不像看起来那么简单。相反这是一项非常痛苦复杂的工作。首先找到一个FTP客户端库需要一些时间,其次,在我们找到所有的存在的库后,我们该选哪一个呢?每个库都适合不同的需求。这些库在性能上是不等价的,并且它们的设计上有着根本上的差别。每个类库都各具特点并使用不同的术语来描述它们。因而,评价和比较FTP客户端库是一件困难的事情。
使用可重用组件是一种值得提倡的方法,但是在这种情况下,刚开始往往是令人气馁的。后来或许有点惭愧:在选择了一个好的FTP库后,其后的工作就非常简单了,按简单的规则来就行了。目前,已经有很多公开免费的ftp客户端类库,如simpleftp、J-ftp等,还有很多其他的ftpclient。如下表所示,表中未能全部列出,如读者有更好的客户端FTP类库,请进行进一步的补充。
FTP客户端类库名 | 备注 |
J-ftp | J-ftp |
simpleftp | |
ftpclient | com.enterprisedt.net.ftp.FTPClient |
FTPProtocol | com.ibm.network.ftp.protocol.FTPProtocol |
FtpConnection | net.sf.jftp.net.FtpConnection |
FTPClient | org.apache.commons.net.ftp.FTPClient |
FTPClient | jshop.jnet.FTPClient |
FtpClient | sun.net.ftp.FtpClient |
FTP | com.cqs.ftp.FTP |
Ftp | cz.dhl.ftp.Ftp |
FTPClient | org.globus.io.ftp.FTPClient |
在本文中,笔者采用是J-ftp。这个是个开源的且功能十分强大的客户端FTP类库。笔者很喜欢,同时也向各位读者推荐一下。算了免费为它做一个广告。
三、 基本功能
1、 登陆
采用FTP进行文件传输,其实本质上还是采用Java.net.socket进行通信。以下代码只是类net.sf.jftp.net.FtpConnection其中一个login方法。当然在下面的代码,为了节省版面,以及将一些原理阐述清楚,笔者将一些没必要的代码去掉了,如日志等代码。完整的代码请参考J-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
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
|
public
int
login(String username, String password)
{
this
.username = username;
this
.password = password;
int
status = LOGIN_OK;
jcon =
new
JConnection(host, port);
if
(jcon.isThere())
{
in = jcon.getReader();
if
(getLine(POSITIVE) ==
null
)
//FTP220_SERVICE_READY) == null)
{
ok =
false
;
status = OFFLINE;
}
if
(!getLine(loginAck).startsWith(POSITIVE))
//FTP230_LOGGED_IN))
{
if
(success(POSITIVE))
//FTP230_LOGGED_IN))
{
}
else
{
ok =
false
;
status = WRONG_LOGIN_DATA;
}
}
}
else
{
if
(msg)
{
Log.debug(
"FTP not available!"
);
ok =
false
;
status = GENERIC_FAILED;
}
}
if
(ok)
{
connected =
true
;
system();
binary();
String[] advSettings =
new
String[
6
];
if
(getOsType().indexOf(
"OS/2"
) >=
0
)
{
LIST_DEFAULT =
"LIST"
;
}
if
(LIST.equals(
"default"
))
{
//just get the first item (somehow it knows first is the
//FTP list command)
advSettings = LoadSet.loadSet(Settings.adv_settings);
//*** IF FILE NOT FOUND, CREATE IT AND SET IT TO LIST_DEFAULT
if
(advSettings ==
null
)
{
LIST = LIST_DEFAULT;
SaveSet s =
new
SaveSet(Settings.adv_settings, LIST);
}
else
{
LIST = advSettings[
0
];
if
(LIST ==
null
)
{
LIST = LIST_DEFAULT;
}
}
}
if
(getOsType().indexOf(
"MVS"
) >=
0
)
{
LIST =
"LIST"
;
}
//***
fireDirectoryUpdate(
this
);
fireConnectionInitialized(
this
);
}
else
{
fireConnectionFailed(
this
,
new
Integer(status).toString());
}
return
status;
}
|
此登陆方法中,有一个JConnection类,此类负责建立socket套接字 ,同时,此类是一种单独的线程,这样的好处是为了配合界面的变化,而将网络的套接字连接等工作做为单独的线程来处理,有利于界面的友好性。下面是net.sf.jftp.net.JConnection类的run方法,当然,此线程的启动是在JConnection类的构造方法中启动的。
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
|
public
void
run()
{
try
{
s =
new
Socket(host, port);
localPort = s.getLocalPort();
//if(time > 0) s.setSoTimeout(time);
out =
new
PrintStream(
new
BufferedOutputStream(s.getOutputStream(),
Settings.bufferSize));
in =
new
BufferedReader(
new
InputStreamReader(s.getInputStream()),
Settings.bufferSize);
isOk =
true
;
// }
}
catch
(Exception ex)
{
ex.printStackTrace();
Log.out(
"WARNING: connection closed due to exception ("
+ host +
":"
+ port +
")"
);
isOk =
false
;
try
{
if
((s !=
null
) && !s.isClosed())
{
s.close();
}
if
(out !=
null
)
{
out.close();
}
if
(in !=
null
)
{
in.close();
}
}
catch
(Exception ex2)
{
ex2.printStackTrace();
Log.out(
"WARNING: got more errors trying to close socket and streams"
);
}
}
established =
true
;
}
|
此run方法中的socket这里说明一下,此类实现客户端套接字(也可以就叫“套接字”),套接字是两台机器之间的通信端点。套接字的实际工作由 SocketImpl 类的实例执行。应用程序通过更改创建套接字实现的套接字工厂可以配置它自身,以创建适合本地防火墙的套接字。具体的说明请参考JDK5 的API说明,最好是中文的。呵呵。
2.上传下载
文件的上传可以分成多线程及单线程,在单线程情况下比较简单,而在多线程的情况下,要处理的事情要多点,同时也要小心很多。下面是net.sf.jftp.net.FtpConnection的上传handleUpload方法。已经考虑了单线程及多线程两种不同的类型。
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
|
public
int
handleUpload(String file, String realName)
{
if
(Settings.getEnableMultiThreading() &&
(!Settings.getNoUploadMultiThreading()))
{
Log.out(
"spawning new thread for this upload."
);
FtpTransfer t;
if
(realName !=
null
)
{
t =
new
FtpTransfer(host, port, getLocalPath(), getCachedPWD(),
file, username, password, Transfer.UPLOAD,
handler, listeners, realName, crlf);
}
else
{
t =
new
FtpTransfer(host, port, getLocalPath(), getCachedPWD(),
file, username, password, Transfer.UPLOAD,
handler, listeners, crlf);
}
lastTransfer = t;
return
NEW_TRANSFER_SPAWNED;
}
else
{
if
(Settings.getNoUploadMultiThreading())
{
Log.out(
"upload multithreading is disabled."
);
}
else
{
Log.out(
"multithreading is completely disabled."
);
}
return
(realName ==
null
) ? upload(file) : upload(file, realName);
}
}
|
在多线程的情况下,有一个单独的类net.sf.jftp.net .FtpTransfer,当然,多线程情况下,此类肯定是一个单独的线程了。与JConnection相似,其线程的启动也是在构造方法中启动。而在它的run方法中,进行文件的读取及传输。
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
|
public
void
run()
{
if
(handler.getConnections().get(file) ==
null
)
{
handler.addConnection(file,
this
);
}
else
if
(!pause)
{
Log.debug(
"Transfer already in progress: "
+ file);
work =
false
;
stat =
2
;
return
;
}
boolean
hasPaused =
false
;
while
(pause)
{
try
{
runner.sleep(
100
);
if
(listeners !=
null
)
{
for
(
int
i =
0
; i < listeners.size(); i++)
{
((ConnectionListener) listeners.elementAt(i)).updateProgress(file,
PAUSED,
-
1
);
}
}
if
(!work)
{
if
(listeners !=
null
)
{
for
(
int
i =
0
; i < listeners.size(); i++)
{
((ConnectionListener) listeners.elementAt(i)).updateProgress(file,
REMOVED,
-
1
);
}
}
}
}
catch
(Exception ex)
{
}
hasPaused =
true
;
}
while
((handler.getConnectionSize() >= Settings.getMaxConnections()) &&
(handler.getConnectionSize() >
0
) && work)
{
try
{
stat =
4
;
runner.sleep(
400
);
if
(!hasPaused && (listeners !=
null
))
{
for
(
int
i =
0
; i < listeners.size(); i++)
{
((ConnectionListener) listeners.elementAt(i)).updateProgress(file,
QUEUED,
-
1
);
}
}
else
{
break
;
}
}
catch
(Exception ex)
{
ex.printStackTrace();
}
}
if
(!work)
{
if
(listeners !=
null
)
{
for
(
int
i =
0
; i < listeners.size(); i++)
{
((ConnectionListener) listeners.elementAt(i)).updateProgress(file,
REMOVED,
-
1
);
}
}
handler.removeConnection(file);
stat =
3
;
return
;
}
started =
true
;
try
{
runner.sleep(Settings.ftpTransferThreadPause);
}
catch
(Exception ex)
{
}
con =
new
FtpConnection(host, port, remotePath, crlf);
con.setConnectionHandler(handler);
con.setConnectionListeners(listeners);
int
status = con.login(user, pass);
if
(status == FtpConnection.LOGIN_OK)
{
File f =
new
File(localPath);
con.setLocalPath(f.getAbsolutePath());
if
(type.equals(UPLOAD))
{
if
(newName !=
null
)
{
transferStatus = con.upload(file, newName);
}
else
{
transferStatus = con.upload(file);
}
}
else
{
transferStatus = con.download(file,
this
.newName);
}
}
if
(!pause)
{
handler.removeConnection(file);
}
}
|
至于下载的过程,因为它是上传的逆过程,与上传的方法及写法大同小异,在些出于篇幅的考虑,并没有将代码列出,但其思想及思路完全一样。请读者参考源代码。
四、进度条
可以想象,如果在上传或是下载的过程中,没有任何的提示,用户根本没法判断任务是否完成或是任务是否死了,常常由于上传时间或下载时间过长而误导用户。因此,进度条就显得非常的重要与实用。
进度条的实现,其实说起来很简单。就是在程序中开启两个线程,第一个线程用于动态的改变界面上进度条的value值,而第二个线程则在上传或是下载的过程中,做成一个循环,在此循环中,每次读取一定数量如8192字节数的数据。然后传完此数据后,调用第一个线程中的updateProgress方法,来更新界面进度条的value值。
而上传或下载的过程中(见上一节的FtpTransfer类的run方法),可以查看,con.upload(file, newName)方法,代码如下所示,
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
|
public
int
upload(String file, String realName, InputStream in)
{
hasUploaded =
true
;
Log.out(
"ftp upload started: "
+
this
);
int
stat;
if
((in ==
null
) &&
new
File(file).isDirectory())
{
shortProgress =
true
;
fileCount =
0
;
baseFile = file;
dataType = DataConnection.PUTDIR;
isDirUpload =
true
;
stat = uploadDir(file);
shortProgress =
false
;
//System.out.println(fileCount + ":" + baseFile);
fireProgressUpdate(baseFile,
DataConnection.DFINISHED +
":"
+ fileCount, -
1
);
fireActionFinished(
this
);
fireDirectoryUpdate(
this
);
}
else
{
dataType = DataConnection.PUT;
stat = rawUpload(file, realName, in);
try
{
Thread.sleep(
100
);
}
catch
(Exception ex)
{
}
fireActionFinished(
this
);
fireDirectoryUpdate(
this
);
}
try
{
Thread.sleep(
500
);
}
catch
(Exception ex)
{
}
return
stat;
}
|
此方法进行负责上传一定字节数量的内容,其实就是调用rawUpload方法,这里没列出,请参考源代码,而当传完此字节数据后,通过调用fireActionFinished()方法来调用主线程中的updateProgressBar()方法。其实代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
protected
void
updateProgressBar() {
int
percent = (
int
) (((
float
) lFileCompleteSize / (
float
) lFileSize) * 10000F);
pbFile.setValue(percent);
// System.out.println("================================================="+percent);
pbFile.setString(lFileCompleteSize / 1024L +
"/"
+ lFileSize / 1024L
+
" kB"
);
percent = (
int
) (((
float
) lTotalCompleteSize / (
float
) lTotalSize) * 10000F);
pbTotal.setString(lTotalCompleteSize / 1024L +
"/"
+ lTotalSize / 1024L
+
" kB"
);
pbTotal.setValue(percent);
repaint();
}
|
上面用了两个进度条,第一个进度条表示当前文件的上传或下载进度,第二个进度条表示所有文件下载或上传的进度。同时,为了产生进度条的移动或变化进度幅度比较明显,通过pbFile.setMaximum(10000)及pbTotal.setMaximum(10000)将进度条的最大值设置成10000,而不是平时我们所设置的100。笔者认为这样比较好看,因为有的时候上传或下载的时候由于网络原因,可能变化比较小。若设置成100则变化不是特别明显。
五、断点续传
对于熟用QQ的程序员,QQ的断点续传功能应该是印象很深刻的。因为它很实用也很方面。因此,在我们的上传下载过程中,很实现了断点续传的功能。
其实断点续传的原理很简单,就在上传的过程中,先去服务上进行查找,是否存在此文件,如果存在些文件,则比较服务器上文件的大小与本地文件的大小,如果服务器上的文件比本地的要小,则认为此文件上传过程中应该可以进行断点续传。
在实现的过程中,RandomAccessFile类变得很有用。此类的实例支持对随机存取文件的读取和写入。随机存取文件的行为类似存储在文件系统中的一个大型字节数组。存在指向该隐含数组的光标或索引,称为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机存取文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。写入隐含数组的当前末尾之后的输出操作导致该数组扩展。该文件指针可以通过 getFilePointer 方法读取,并通过seek 方法进行设置。
RandomAccessFile类的skipBytes方法尝试跳过输入的 n 个字节以丢弃跳过的字节。如果从服务器上查得待上传文件的大小n,则采用skipBytes方法可以跳过这n个字节,从而开始从新的地方开始进行断点续传。具体的方法说明可以参见JDK5的API说明。
可以在net.sf.jftp.net. DataConnection类的run方法中,可以看出上传下载中断点续传的实现,代码如下:
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
|
public
void
run()
{
try
{
newLine = con.getCRLF();
if
(Settings.getFtpPasvMode())
{
try
{
sock =
new
Socket(host, port);
sock.setSoTimeout(Settings.getSocketTimeout());
}
catch
(Exception ex)
{
ok =
false
;
debug(
"Can't open Socket on port "
+ port);
}
}
else
{
//Log.debug("trying new server socket: "+port);
try
{
ssock =
new
ServerSocket(port);
}
catch
(Exception ex)
{
ok =
false
;
Log.debug(
"Can't open ServerSocket on port "
+ port);
}
}
}
catch
(Exception ex)
{
debug(ex.toString());
}
isThere =
true
;
boolean
ok =
true
;
RandomAccessFile fOut =
null
;
BufferedOutputStream bOut =
null
;
RandomAccessFile fIn =
null
;
try
{
if
(!Settings.getFtpPasvMode())
{
int
retry =
0
;
while
((retry++ <
5
) && (sock ==
null
))
{
try
{
ssock.setSoTimeout(Settings.connectionTimeout);
sock = ssock.accept();
}
catch
(IOException e)
{
sock =
null
;
debug(
"Got IOException while trying to open a socket!"
);
if
(retry ==
5
)
{
debug(
"Connection failed, tried 5 times - maybe try a higher timeout in Settings.java"
);
}
finished =
true
;
throw
e;
}
finally
{
ssock.close();
}
debug(
"Attempt timed out, retrying"
);
}
}
if
(ok)
{
byte
[] buf =
new
byte
[Settings.bufferSize];
start = System.currentTimeMillis();
int
buflen =
0
;
//---------------download,下载----------------------
if
(type.equals(GET) || type.equals(GETDIR))
{
if
(!justStream)
{
try
{
if
(resume)
{
File f =
new
File(file);
fOut =
new
RandomAccessFile(file,
"rw"
);
fOut.skipBytes((
int
) f.length());
buflen = (
int
) f.length();
}
else
{
if
(localfile ==
null
)
{
localfile = file;
}
File f2 =
new
File(Settings.appHomeDir);
f2.mkdirs();
File f =
new
File(localfile);
if
(f.exists())
{
f.delete();
}
bOut =
new
BufferedOutputStream(
new
FileOutputStream(localfile),
Settings.bufferSize);
}
}
catch
(Exception ex)
{
debug(
"Can't create outputfile: "
+ file);
ok =
false
;
ex.printStackTrace();
}
}
//---------------upload,上传----------------------
if
(type.equals(PUT) || type.equals(PUTDIR))
{
if
(in ==
null
)
{
try
{
fIn =
new
RandomAccessFile(file,
"r"
);
if
(resume)
{
fIn.skipBytes(skiplen);
}
//fIn = new BufferedInputStream(new FileInputStream(file));
}
catch
(Exception ex)
{
debug(
"Can't open inputfile: "
+
" ("
+ ex +
")"
);
ok =
false
;
}
}
if
(ok)
{
try
{
out =
new
BufferedOutputStream(sock.getOutputStream());
}
catch
(Exception ex)
{
ok =
false
;
debug(
"Can't get OutputStream"
);
}
if
(ok)
{
try
{
int
len = skiplen;
char
b;
while
(
true
)
{
int
read;
if
(in !=
null
)
{
read = in.read(buf);
}
else
{
read = fIn.read(buf);
}
len += read;
//System.out.println(file + " " + type+ " " + len + " " + read);
if
(read == -
1
)
{
break
;
}
if
(newLine !=
null
)
{
byte
[] buf2 = modifyPut(buf, read);
out.write(buf2,
0
, buf2.length);
}
else
{
out.write(buf,
0
, read);
}
con.fireProgressUpdate(file, type, len);
if
(time())
{
// Log.debugSize(len, false, false, file);
}
if
(read == StreamTokenizer.TT_EOF)
{
break
;
}
}
out.flush();
//Log.debugSize(len, false, true, file);
}
catch
(IOException ex)
{
ok =
false
;
debug(
"Error: Data connection closed."
);
con.fireProgressUpdate(file, FAILED, -
1
);
ex.printStackTrace();
}
}
}
}
}
}
catch
(IOException ex)
{
Log.debug(
"Can't connect socket to ServerSocket"
);
ex.printStackTrace();
}
finally
{
try
{
if
(out !=
null
)
{
out.flush();
out.close();
}
}
catch
(Exception ex)
{
ex.printStackTrace();
}
try
{
if
(bOut !=
null
)
{
bOut.flush();
bOut.close();
}
}
catch
(Exception ex)
{
ex.printStackTrace();
}
try
{
if
(fOut !=
null
)
{
fOut.close();
}
}
catch
(Exception ex)
{
ex.printStackTrace();
}
try
{
if
(in !=
null
&& !justStream)
{
in.close();
}
if
(fIn !=
null
)
{
fIn.close();
}
}
catch
(Exception ex)
{
ex.printStackTrace();
}
}
try
{
sock.close();
}
catch
(Exception ex)
{
debug(ex.toString());
}
if
(!Settings.getFtpPasvMode())
{
try
{
ssock.close();
}
catch
(Exception ex)
{
debug(ex.toString());
}
}
finished =
true
;
if
(ok)
{
con.fireProgressUpdate(file, FINISHED, -
1
);
}
else
{
con.fireProgressUpdate(file, FAILED, -
1
);
}
}
|
六、FTP端口映射
FTP的数据连接有PASV和PORT两种,如果你的FTP服务器位于内网中,需要做端口映射。笔者刚开始时对FTP的网外网映射也是不怎么了解,因此开始走了不少的弯路,开始一直以为是自己的程序有问题,浪费了不少时间,希望通过这段,能让大家在开发的时候少花或不花这些无谓的时间与精力。
PCD上曾经有一篇文章介绍过一种直接访问内网的方法,其实我们只要用端口映射工具,就可轻松实现穿透内网的目的。“端口映射器”就是一款这样的工具,更值得一提的是,它摆脱了命令行模式,提供了图形界面的操作环境。
为了让各位能更加明白,先说一下原理。假设现在有一个局域网,主机为A,局域网内除了主机外,还有一台机器为B,B机器当然是通过主机A上网的。另外还有一台可上网的机器为C,与A和B并不在一个局域网内。通常情况下,C机器只能访问到A主机,而无法穿透局域网,访问到B。而通过端口映射后,当C机器访问主机A的指定端口时,主机A上的“端口映射器”就起作用了,它会把指定端口上的数据转到局域网内另一台机器的指定端口上,从而实现访问内网机器的目的。这样说,大家明白了吧。至于具体的如何进行配置,笔者认为应该不是件很难的事情,再说,网上这样的图形解释很多,请大家参考网络上的文章进行设置。
当然,实现直接访问内网的优点是显而易见的,别的不说,起码FTP资源是被充分利用了。不过必须提醒读者的是,直接访问内网可能使内网的安全性受到威胁。笔者相信大部分朋友对主机安全的重要性还是重视的,但往往会忽略内网机器的安全设置。一旦你实现了直接访问内网,那就必须像对待主机一样对待内网机器,否则你的整个网络将可能处于危险状态。
六、 访问客户端资源
Java应用程序环境的安全策略,对于不同的代码所拥有的不同资源的许可,它由一个Policy对象来表达。为了让Applet(或者运行在 SecurityManager下的一个应用程序)能够执行受保护的行为,例如读写文件,Applet(或 Java应用程序)必须获得那项操作的许可,安全策略文件就是用来实现这些许可。
Policy对象可能有多个实体,虽然任何时候只能有一个起作用。当前安装的Policy对象,在程序中可以通过调用getPolicy方法得到,也可以通过调用setPolicy方法改变。Policy对象评估整个策略,返回一个适当的Permissions对象,详细说明哪些代码可以访问哪些资源。策略文件可以储存在无格式的ASCII文件或Policy类的二进制文件或数据库中。本文仅讨论无格式的ASCII文件的形式。
在实际使用中,我们可以不需要自己手动去编写那么复杂的java.policy文件,特别是在不使用数字签名时。这时,我们完全可以借鉴JRE提供给我们的现成的 C:\Program Files\Java\jre1.5.0_12\lib\security\java.policy文件,根据我们的需要做相应的修改,本文就针对不使用数字签名情况编写安全策略文件。下面,是一个完整的在Windows NT/XP下使用的java.policy文件。在文件中,分别使用注释的形式说明了每个“permission”记录的用途。当然,不同的程序对资源访问权限的要求可能不一样,可以根据项目需要进行调整与选择。
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
|
grant {
//对系统和用户目录“读”的权限
permission java.util.PropertyPermission "user.dir", "read";
permission java.util.PropertyPermission "user.home", "read";
permission java.util.PropertyPermission "java.home", "read";
permission java.util.PropertyPermission "java.class.pat", "read";
permission java.util.PropertyPermission "user.name", "read";
//对线程和线程组的操作权限
permission java.lang.RuntimePermission "accessClassInPackage.sun.misc";
permission java.lang.RuntimePermission "accessClassInPackage.sun.audio";
permission java.lang.RuntimePermission "modifyThread";
permission java.lang.RuntimePermission "modifyThreadGroup";
permission java.lang.RuntimePermission "loadLibrary.*";
//读写文件的权限
permission java.io.FilePermission "<<ALL FILES>>", "read";
permission java.io.FilePermission "${user.dir}${/}jmf.log", "write";
permission java.io.FilePermission "${user.home}${/}.JMStudioCfg", "write";
permission java.net.SocketPermissio "*", "connect,accept";
permission java.io.FilePermission "C:\WINNT\TEMP\*", "write";
permission java.io.FilePermission "C:\WINNT\TEMP\*", "delete";
permission java.awt.AWTPermission "showWindowWithoutWarningBanner";
permission javax.sound.sampled.AudioPermission "record";
// //操作Socket端口的各种权限
permission java.net.SocketPermission "-", "listen";
permission java.net.SocketPermission "-", "accept";
permission java.net.SocketPermission "-", "connect";
permission java.net.SocketPermission "-", "resolve";
permission java.security.AllPermission;
};
grant signedBy "saili" {
permission java.net.SocketPermission "*:1024-65535", "connect,accept,resolve";
permission java.net.SocketPermission "*:80", "connect";
permission java.net.SocketPermission "-", "listen, accept, connect, listen, resolve", signedBy "ganja";
permission java.net.SocketPermission "-", "accept";
permission java.net.SocketPermission "-", "connect";
permission java.net.SocketPermission "-", "resolve";
permission java.security.AllPermission;
};
|
笔者在本项目中,为了使用客户端的用户设置更加的方便与简单,将上面的文件采用VB或C#来做成一个小程序来写。然后将JRE及些exe共同打成一个EXE包,当JRE安装完成后,此小程序负责找到JRE在操作系统中的安装路径,并在程序中写出此java.policy文件,覆盖原有的文件。如此一来,用户就只需安装一个EXE文件,从而简化了安装的操作次数。
七、Applet回调服务器
JavaScript与Applet之间能够相互通讯给我们带来了很多方便,Java与JavaScript互相补充,以开发功能更完美的Web应用程序。B/S下能够充分利用java的优势,给我们带来更多的网络体验,方便用户。笔者用的比较多的是利用Swing组件开发的应用程序利用Applet实现B/s下架构,这样能够充分显示Swing组件的优势,便于系统升级,便于维护;还有就是在WEB下,有时客户端要使用本地的硬件资源,笔者所知道的是通过Applet来实现,通过Applet去调用javaAPI来实现。 我们具体来看看 JavaScript与Applet之间到底是怎样通讯的呢?
1.JavaScript访问Applet
<applet name="appletName" ……/>//JavaScript访问Applet属性。
window.document.appletName.appletField (属性必须是public的,"window.document."也可以不写) //JavaScript访问Applet方法。
window.document.appletName.appletMethod (方法必须是public的,"window.document."也可以不写)。
2.Applet访问JavaScript
Live Connect提供了Java与JavaScript的接口,可以允许在Java Applet小程序中使用JavaScript。
需要用到一个jar包,在C:\Program Files\Java\目录下找,大概有5M多,其实就是打开看哪个有netscape.javascript.JSObject。如果没有装个NetScape或从网上下都可以。 可以把它重命名为netscape.jar(不是必须的),一定要加入到classpath,目的是使开发的时候能够编译。特别注意的是:部署时不需要包括netscape.jar,因为整个包会下载到客户端,影响速度。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
//引入netscape类
import
netscape.javascript.JSObject;
import
netscape.javascript.JSException;
//可允许在小程序中处理异常事件
public
void
callJavaScript(String callBackJavascript) {
JSObject window = JSObject.getWindow(
this
);
// 获取JavaScript窗口句柄,引用当前文档窗口
JSObject docment = (JSObject) window.getMember(
"document"
);
form=(JSObject)doc.getMember(
"textForm"
);
//访问JavaScript form对象
textField=(JSObject)form.getMember(
"textField"
);访问JavaScript text对象
text=(String) textField.getMember(
"value"
);
//获取文本区的值
// 调用JavaScript的alert()方法
// window.eval("alert(\"This alert comes from Java!\")");
window.call(callBackJavascript,
null
);
// 参数用数组的形式表示。
}
|
八、运行效果
1.上传
(1).启动上传上面
(2).上传中
(3).上传中
(4).上传成功
2.下载
(1)下载文件的保存路径
(2)下载中
(3)下载中
(4)下载成功
九、小结
在本文中,笔者将在实际项目中的上传下载问题的解决方案进行了阐述,通过采用FTP协议,来达到批量的,基本Web的大文件的上传下载。同时通过Applet技术实现在客户端对本地资源进行访问。就一些大家经常遇到的实际功能如进度条、断点续传、FTP内外网映射等问题进行了初步的探讨。这是笔者基于一些FTP的Java客户端库的基础应用。希望对读者有一定的借鉴作用。对其中一些未尽事宜进行补充。还有一些比较容易而且网上都有说明或实例的内容在此没有一一列举。如FTP在服务器端Serv-U软件如何建立FTP服务、Applet在JSP页面中的嵌入方式及参数传递方法、在Eclipse或是NetBeans下开始Applet等内容,由于篇幅的限制,并没有详尽描述,请读者参考网上的例子或其他参考资料。