当前位置:文档之家› Java实现FTP批量大文件上传下载

Java实现FTP批量大文件上传下载

Java实现FTP批量大文件上传下载
Java实现FTP批量大文件上传下载

用Java实现FTP批量大文件上传下载(一)

本文介绍了在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进行文件传输,其实本质上还是采用https://www.doczj.com/doc/7b8685456.html,.socket进行通信。以下代码只是类https://www.doczj.com/doc/7b8685456.html,.FtpConnection其中一个login方法。当然在下面的代码,为了节省版面,以及将一些原理阐述清楚,笔者将一些没必要的代码去掉了,如日志等代码。完整的代码请参考J-ftp的源代码或是笔者所以的示例源代码,后面的代码示例也同理:

public int login(String username, String password)

{

https://www.doczj.com/doc/7b8685456.html,ername = 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(m sg)

{

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

{

}

return status;

}

此登陆方法中,有一个JConnection类,此类负责建立socket套接字,同时,此类是一种单独的线程,这样的好处是为了配合界面的变化,而将网络的套接字连接等工作做为单独的线程来处理,有利于界面的友好性。下面是https://www.doczj.com/doc/7b8685456.html,.JConnection类的run方法,当然,此线程的启动是在JConnection类的构造方法中启动的。

public void run()

{

try

{

s =new Socket(host, port);

localPort = s.getLocalPort();

//if(tim e > 0) s.setSoTim eout(tim e);

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();

":" + 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 m ore errors trying to close socket and strea ms");

}

}

established =true;

}

此run方法中的socket这里说明一下,此类实现客户端套接字(也可以就叫“套接字”),套接字是两台机器之间的通信端点。套接字的实际工作由 SocketImpl 类的实例执行。应用程序通过更改创建套接字实现的套接字工厂可以配置它自身,以创建适合本地防火墙的套接字。具体的说明请参考JDK5 的API说明,最好是中文的。呵呵。

用Java实现FTP批量大文件上传下载(二)

上传下载

文件的上传可以分成多线程及单线程,在单线程情况下比较简单,而在多线程的情况下,要处理的事情要多点,同时也要小心很多。下面是net.sf.jft https://www.doczj.com/doc/7b8685456.html,.FtpConnection的上传handleUpload方法。已经考虑了单线程及多线程两种不同的类型。

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 com pletely disabled.");

}

return (realName == null) ? upload(file) : upload(file, realName);

}

}

在多线程的情况下,有一个单独的类https://www.doczj.com/doc/7b8685456.html, .FtpTransfer,当然,多线程情况下,此类肯定是一个单独的线程了。与JConnection相似,其线程的启动也是在构造方法中启动。而在它的run方法中,进行文件的读取及传输。

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(fil e,

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;

}

(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(fil e,

QUEUED,

-1);

}

}

else

{

break;

}

}

catch(Exception ex)

{

ex.printStackTrace();

}

}

if(!work)

if(listeners != null)

{

for(int i = 0; i < listeners.size(); i++)

{

((ConnectionListener) listeners.elem entAt(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, rem otePath, crlf);

con.setConnectionHandler(handler);

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.newNam e);

}

}

if(!pause)

{

handler.removeConnection(file);

}

至于下载的过程,因为它是上传的逆过程,与上传的方法及写法大同小异,在些出于篇幅的考虑,并没有将代码列出,但其思想及思路完全一样。请读者参考源代码。

四、进度条

可以想象,如果在上传或是下载的过程中,没有任何的提示,用户根本没法判断任务是否完成或是任务是否死了,常常由于上传时间或下载时间过长而误导用户。因此,进度条就显得非常的重要与实用。

进度条的实现,其实说起来很简单。就是在程序中开启两个线程,第一个线程用于动态的改变界面上进度条的value值,而第二个线程则在上传或是下载的过程中,做成一个循环,在此循环中,每次读取一定数量如8192字节数的数据。然后传完此数据后,调用第一个线程中的updateProgress方法,来更新界面进度条的value值。

而上传或下载的过程中(见上一节的FtpTransfer类的run方法),可以查看,con.upload(file, newName)方法,代码如下所示,

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.PUT DIR;

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()方法。其实代码如下:

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则变化不是特别明显。

用Java实现FTP批量大文件上传下载(三)

五、断点续传

对于熟用QQ的程序员,QQ的断点续传功能应该是印象很深刻的。因为它很实用也很方面。因此,在我们的上传下载过程中,很实现了断点续传的功能。

其实断点续传的原理很简单,就在上传的过程中,先去服务上进行查找,是否存在此文件,如果存在些文件,则比较服务器上文件的大小与本地文件的大小,如果服务器上的文件比本地的要小,则认为此文件上传过程中应该可以进行断点续传。

在实现的过程中,RandomAccessFile类变得很有用。此类的实例支持对随机存取文件的读取和写入。随机存取文件的行为类似存储在文件系统中的一个大型

字节数组。存在指向该隐含数组的光标或索引,称为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机存取文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。写入隐含数组的当前末尾之后的输出操作导致该数组扩展。该文件指针可以通过getFilePointer 方法读取,并通过seek 方法进行设置。

RandomAccessFile类的skipBytes方法尝试跳过输入的n 个字节以丢弃跳过的字节。如果从服务器上查得待上传文件的大小n,则采用skipBytes方法可以跳过这n个字节,从而开始从新的地方开始进行断点续传。具体的方法说明可以参见JDK5的API说明。

可以在https://www.doczj.com/doc/7b8685456.html,. DataConnection类的run方法中,可以看出上传下载中断点续传的实现,代码如下:

public void run()

{

try

{

newLine = con.getCRLF();

if(Settings.getFtpPasvMode())

{

try

{

sock = new Socket(host, port);

sock.setSoTimeout(Settings.getSocketTimeout());

}

catch(Exception ex)

{

相关主题
文本预览
相关文档 最新文档