分享

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

 WindySky 2016-04-18

本文介绍了在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

HTTP://www./files/simpleftp.jar

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. public int login(String username, String password)  
  2.     {  
  3.         this.username = username;  
  4.         this.password = password;  
  5.   
  6.         int status = LOGIN_OK;  
  7.   
  8.         jcon = new JConnection(host, port);  
  9.   
  10.         if(jcon.isThere())  
  11.         {  
  12.             in = jcon.getReader();  
  13.   
  14.             if(getLine(POSITIVE) == null)//FTP220_SERVICE_READY) == null)  
  15.             {  
  16.                 ok = false;                  
  17.                 status = OFFLINE;  
  18.             }     
  19.   
  20.             if(!getLine(loginAck).startsWith(POSITIVE))//FTP230_LOGGED_IN))  
  21.             {                 
  22.                 if(success(POSITIVE))//FTP230_LOGGED_IN))  
  23.                 {                   
  24.                 }  
  25.                 else  
  26.                 {  
  27.                     ok = false;  
  28.                     status = WRONG_LOGIN_DATA;  
  29.                 }  
  30.             }  
  31.         }  
  32.         else  
  33.         {  
  34.             if(msg)  
  35.             {  
  36.                 Log.debug("FTP not available!");  
  37.                 ok = false;  
  38.                 status = GENERIC_FAILED;  
  39.             }  
  40.         }  
  41.   
  42.         if(ok)  
  43.         {  
  44.             connected = true;  
  45.             system();  
  46.             binary();  
  47.               
  48.             String[] advSettings = new String[6];  
  49.   
  50.             if(getOsType().indexOf("OS/2") >= 0)  
  51.             {  
  52.                 LIST_DEFAULT = "LIST";  
  53.             }  
  54.   
  55.             if(LIST.equals("default"))  
  56.             {  
  57.                 //just get the first item (somehow it knows first is the  
  58.                 //FTP list command)  
  59.                 advSettings = LoadSet.loadSet(Settings.adv_settings);  
  60.   
  61.                 //*** IF FILE NOT FOUND, CREATE IT AND SET IT TO LIST_DEFAULT  
  62.                 if(advSettings == null)  
  63.                 {  
  64.                     LIST = LIST_DEFAULT;  
  65.   
  66.                     SaveSet s = new SaveSet(Settings.adv_settings, LIST);  
  67.                 }  
  68.                 else  
  69.                 {  
  70.                     LIST = advSettings[0];  
  71.   
  72.                     if(LIST == null)  
  73.                     {  
  74.                         LIST = LIST_DEFAULT;  
  75.                     }  
  76.                 }  
  77.             }  
  78.   
  79.             if(getOsType().indexOf("MVS") >= 0)  
  80.             {  
  81.                 LIST = "LIST";  
  82.             }  
  83.   
  84.             //***  
  85.             fireDirectoryUpdate(this);  
  86.             fireConnectionInitialized(this);  
  87.         }  
  88.         else  
  89.         {  
  90.             fireConnectionFailed(this, new Integer(status).toString());  
  91.         }  
  92.   
  93.         return status;  
  94.     }  
  95. 此登陆方法中,有一个JConnection类,此类负责建立socket套接字    ,同时,此类是一种单独的线程,这样的好处是为了配合界面的变化,而将网络的套接字连接等工作做为单独的线程来处理,有利于界面的友好性。下面是net.sf.jftp.net.JConnection类的run方法,当然,此线程的启动是在JConnection类的构造方法中启动的。  
  96.     public void run()  
  97.     {  
  98.         try  
  99.         {  
  100.             s = new Socket(host, port);  
  101.   
  102.             localPort = s.getLocalPort();  
  103.   
  104.             //if(time > 0) s.setSoTimeout(time);  
  105.             out = new PrintStream(new BufferedOutputStream(s.getOutputStream(),  
  106.                                                            Settings.bufferSize));  
  107.             in = new BufferedReader(new InputStreamReader(s.getInputStream()),  
  108.                                     Settings.bufferSize);  
  109.             isOk = true;  
  110.   
  111.             // }  
  112.         }  
  113.         catch(Exception ex)  
  114.         {  
  115.             ex.printStackTrace();  
  116.             Log.out("WARNING: connection closed due to exception (" + host +  
  117.                     ":" + port + ")");  
  118.             isOk = false;  
  119.   
  120.             try  
  121.             {  
  122.                 if((s != null) && !s.isClosed())  
  123.                 {  
  124.                     s.close();  
  125.                 }  
  126.   
  127.                 if(out != null)  
  128.                 {  
  129.                     out.close();  
  130.                 }  
  131.   
  132.                 if(in != null)  
  133.                 {  
  134.                     in.close();  
  135.                 }  
  136.             }  
  137.             catch(Exception ex2)  
  138.             {  
  139.                 ex2.printStackTrace();  
  140.                 Log.out("WARNING: got more errors trying to close socket and streams");  
  141.             }  
  142.         }  
  143.   
  144.         established = true;  
  145.     }  

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



    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约