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

来源:岁月联盟 编辑:zhuzhu 时间:2008-08-31

  五、断点续传

  对于熟用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 FilesJavajre1.5.0_12libsecurityjava.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}${