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

接着上一篇进行学习java文件上传下载1。

五、断点续传 

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

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

在实现的过程中,RandomAccessFile类变得很有用。此类的实例支持对随机存取文件的读取和写入。随机存取文件的行为类似存储在文件系统中的一个大型字节数组。存在指向该隐含数组的光标或索引,称为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机存取文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。写入隐含数组的当前末尾之后的输出操作导致该数组扩展。该文件指针可以通过 getFilePointer 方法读取,并通过 seek 方法进行设置。 

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

可以在net.sf.jftp.net. 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)
        {
          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”记录的用途。当然,不同的程序对资源访问权限的要求可能不一样,可以根据项目需要进行调整与选择。 

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}${
上一篇:java多线程-读写锁原理
下一篇:Java单例模式实现的几种方式