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

本文介绍了在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的源代码或是笔者所以的示例源代码,后面的代码示例也同理:

 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类的构造方法中启动的。

 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方法。已经考虑了单线程及多线程两种不同的类型。

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方法中,进行文件的读取及传输。

 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)方法,代码如下所示,

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

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

以上就是FTP批量大文件上传下载的基础篇,希望对大家的学习有所帮助,也希望大家多多支持我们。

时间: 2016-08-26

JAVA中使用FTPClient实现文件上传下载实例代码

在java程序开发中,ftp用的比较多,经常打交道,比如说向FTP服务器上传文件.下载文件,本文给大家介绍如何利用jakarta commons中的FTPClient(在commons-net包中)实现上传下载文件. 一.上传文件 原理就不介绍了,大家直接看代码吧 /** * Description: 向FTP服务器上传文件 * @Version1.0 Jul 27, 2008 4:31:09 PM by 崔红保(cuihongbao@d-heaven.com)创建 * @param url F

JAVA SFTP文件上传、下载及批量下载实例

1.jsch官方API查看地址(附件为需要的jar) http://www.jcraft.com/jsch/ 2.jsch简介 JSch(Java Secure Channel)是一个SSH2的纯Java实现.它允许你连接到一个SSH服务器,并且可以使用端口转发,X11转发,文件传输等,当然你也可以集成它的功能到你自己的应用程序. SFTP(Secure File Transfer Protocol)安全文件传送协议.可以为传输文件提供一种安全的加密方法.SFTP 为 SSH的一部份,是一种传输

java基于Apache FTP实现文件上传、下载、修改文件名、删除

Apache FTP 是应用比较广泛的FTP上传客户端工具,它易于操作,代码简略,结构清晰,是做FTP文件客户端管理软件的优先之选.FTP的操作包括:FTP文件上传(断点续传).FTP文件下载.FTP文件重命名.FTP文件删除,这些操作已经将FTP应用管理的方式发挥的淋漓尽致了,So 我一直都用此种方式来实现FTP文件服务器的管理工作:下附FTP工具代码. 1.FTP文件操作状态枚举类 package com.scengine.wtms.utils.ftp; public enum FTPSta

Java实现ftp文件上传下载解决慢中文乱码多个文件下载等问题

废话不多说了,直接给大家贴代码了,具体代码如下所示: //文件上传 public static boolean uploadToFTP(String url,int port,String username,String password,String path,String filename,InputStream input) { boolean success=false; FTPClient ftp=new FTPClient();//org.apache.commons.net.ftp

java实现FTP文件上传与文件下载

本文实例为大家分享了两种java实现FTP文件上传下载的方式,供大家参考,具体内容如下 第一种方式: package com.cloudpower.util; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import sun.net.TelnetInputStream; import sun.net.TelnetO

详解ftp文件上传下载命令

介绍:从本地以用户wasqry登录的机器1*.1**.21.67上通过ftp远程登录到ftp服务器上,登录用户名是lte****,以下为使用该连接做的实验. 查看远程ftp服务器上用户lte****相应目录下的文件所使用的命令为:ls,登录到ftp后在ftp命令提示符下查看本地机器用户wasqry相应目录下文件的命令是:!ls.查询ftp命令可在提示符下输入:?,然后回车.   1.从远程ftp服务器下载文件的命令格式: get  远程ftp服务器上当前目录下要下载的文件名  [下载到本地机器上

详解SpringBoot文件上传下载和多文件上传(图文)

最近在学习SpringBoot,以下是最近学习整理的实现文件上传下载的Java代码: 1.开发环境: IDEA15+ Maven+JDK1.8 2.新建一个maven工程: 3.工程框架 4.pom.xml文件依赖项 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation

Python Socketserver实现FTP文件上传下载代码实例

一.Socketserver实现FTP,文件上传.下载 目录结构 1.socketserver实现ftp文件上传下载,可以同时多用户登录.上传.下载 效果图: 二.上面只演示了下载,上传也是一样的,来不及演示了,上代码 1.客户端 import socket,hashlib,os,json,sys,time class Ftpclient(object): def __init__(self): self.client = socket.socket() def connect(self,ip,

Linux本机与服务器文件互传及Linux服务器文件上传下载命令写法

一.Linux下文件互传,scp命令实例 1.Linux下目录复制:本机->远程服务器 scp -r /home/abc/test1 root@192.168.0.1:/home/bcd/test2 (本机目录路径 远程机用户名@IP:/目录) 2.Linux下目录复制:远程服务器->本机 scp -r root@192.168.0.1:/home/bcd/test2 /home/abc/test1 (远程机用户名@IP:/目录 本机目录路径) 3.Linux下文件复制:远程服务器->本

java实现ftp文件上传下载功能

本文实例为大家分享了ftp实现文件上传下载的具体代码,供大家参考,具体内容如下 package getUrlPic; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPFile; import org

详解bootstrap-fileinput文件上传控件的亲身实践

经理让我帮服务器开发人员开发一个上传文件功能界面,我就想着以前使用过bootstrap-fileinput插件进行文件上传,很不错.赶紧就撸起来了. 1.下载压缩包.插件地址https://github.com/kartik-v/bootstrap-fileinput/ ,下载压缩包解压之后,拿出fileinput.min.js.fileinput.min.css.和中文需要引用的插件zh.js,因为这款插件默认的语言是英语.把这几个文件引入进页面 2.文件的引入顺序 引入bootstrap.m

基于python实现FTP文件上传与下载操作(ftp&sftp协议)

前言 FTP(File Transfer Protocol)是文件传输协议的简称.用于Internet上的控制文件的双向传输.同时,它也是一个应用程序(Application).用户可以通过它把自己的PC机与世界各地所有运行FTP协议的服务器相连,访问服务器上的大量程序和信息.如果用户需要将文件从自己的计算机上发送到另一台计算机上,可使用FTP上传(upload)或(put)操作,而更多种的情况是用户使用FTP下载(download)或获取(get)操作从FTP服务器上下载文件 在传输文件时我们

Android关于FTP文件上传和下载功能详解

本文实例为大家分享了Android九宫格图片展示的具体代码,供大家参考,具体内容如下 此篇博客为整理文章,供大家学习. 1.首先下载commons-net  jar包,可以百度下载. FTP的文件上传和下载的工具类: package ryancheng.example.progressbar; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.io.Outpu