本文介绍了在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类库,请进行进一步的补充。
在本文中,笔者采用是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
13
14
|
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则变化不是特别明显。
DEMO下载地址:https://dwz.cn/fgXtRtnu
Java实现FTP批量大文件上传下载篇1的更多相关文章
-
Java实现FTP与SFTP文件上传下载
添加依赖Jsch-0.1.54.jar <!-- https://mvnrepository.com/artifact/com.jcraft/jsch --> <dependency ...
-
java操作FTP,实现文件上传下载删除操作
上传文件到FTP服务器: /** * Description: 向FTP服务器上传文件 * @param url FTP服务器hostname * @param port FTP服务器端口,如果默认端 ...
-
Java 客户端操作 FastDFS 实现文件上传下载替换删除
FastDFS 的作者余庆先生已经为我们开发好了 Java 对应的 SDK.这里需要解释一下:作者余庆并没有及时更新最新的 Java SDK 至 Maven *仓库,目前*仓库最新版仍旧是 1.2 ...
-
java+大文件上传下载
文件上传下载,与传统的方式不同,这里能够上传和下载10G以上的文件.而且支持断点续传. 通常情况下,我们在网站上面下载的时候都是单个文件下载,但是在实际的业务场景中,我们经常会遇到客户需要批量下载的场 ...
-
java+web+大文件上传下载
文件上传是最古老的互联网操作之一,20多年来几乎没有怎么变化,还是操作麻烦.缺乏交互.用户体验差. 一.前端代码 英国程序员Remy Sharp总结了这些新的接口 ,本文在他的基础之上,讨论在前端采用 ...
-
java 如何实现大文件上传下载(传输)各种格式
我们平时经常做的是上传文件,上传文件夹与上传文件类似,但也有一些不同之处,这次做了上传文件夹就记录下以备后用. 首先我们需要了解的是上传文件三要素: 1.表单提交方式:post (get方式提交有大小 ...
-
JAVA WEB怎么实现大文件上传
javaweb上传文件 上传文件的jsp中的部分 上传文件同样可以使用form表单向后端发请求,也可以使用 ajax向后端发请求 1.通过form表单向后端发送请求 改进后的代码不需要form标签,直 ...
-
我的代码库-Java8实现FTP与SFTP文件上传下载
有网上的代码,也有自己的理解,代码备份 一般连接windows服务器使用FTP,连接linux服务器使用SFTP.linux都是通过SFTP上传文件,不需要额外安装,非要使用FTP的话,还得安装FTP ...
-
JAVA中使用FTPClient实现文件上传下载
在JAVA程序中,经常需要和FTP打交道,比如向FTP服务器上传文件.下载文件,本文简单介绍如何利用jakarta commons中的FTPClient(在commons-net包中)实现上传下载文件 ...
随机推荐
-
HotSpot JVM常用参数设置
转自:https://www.zybuluo.com/jewes/note/57352 选项的分类 Hotspot JVM提供以下三大类选项: 1. 标准选项:这类选项的功能是很稳定的,在后续版本中也 ...
-
9月19日上午JavaScript数组
数组 一.定义 1.数组的文字定义 广义上说,数组是相同类型数据的集合.但是对于强类型语言和弱类型语言来说其特点是不一样的.强类型语言数组和集合有以下特点. 数组强类型语言:1.数组里面只能存放相同数 ...
-
Apache CXF实现Web Service(3)——Tomcat容器和不借助Spring的普通Servlet实现JAX-RS(RESTful) web service
起步 参照这一系列的另外一篇文章: Apache CXF实现Web Service(2)——不借助重量级Web容器和Spring实现一个纯的JAX-RS(RESTful) web service 首先 ...
-
ASP.NET Core环境配置
一.环境准备 vs2015 update3 下载NET Core Tooling Preview 2 for Visual Studio 2015 (下载地址:https://go.microsoft ...
-
Android 如何修改默认的searchable items。
前言 欢迎大家我分享和推荐好用的代码段~~ 声明 欢迎转载,但请保留文章原始出处: CSDN:http://www.csdn.net ...
-
javascript第六课类型转换
1.parseint(参数): 转换为整数,即使参数中的字符串包含字母数字混合,此方法也会自动一个一个判断和转换 parseInt(参数,进制);将参数通过几进制的方式转为数字 2.parsefl ...
-
Ext.MessageBox.Show使用Progress
在此之前,先添加引用:以下引用方式仅供参考:由于我的extjs文件夹放在script文件夹下 <link href="~/Scripts/extjs/resources/ext-the ...
-
Java 异常分类
1.java异常层次结构: 粉红色的是已检查异常(checked exceptions),其必须被 try{}catch语句块所捕获,或者在方法签名里通过throws子句声明.已检查异常必须在编译时被 ...
-
Date中before和after方法的使用
Date1.after(Date2),当Date1大于Date2时,返回TRUE,当小于等于时,返回false: Date1.before(Date2),当Date1小于Date2时,返回TRUE,当 ...
-
Eleaticsearch源码分析(一)编译启动
转自:https://lunatictwo.github.io/2017/12/21/Eleaticsearch%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90(%E4%B8% ...