Java实现FTP批量大文件上传下载篇2
作者:ycb1689
接着上一篇进行学习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}${/}jmf.log", "write"; permission java.io.FilePermission "${user.home}${/}.JMStudioCfg", "write"; permission java.net.SocketPermissio "*", "connect,accept"; permission java.io.FilePermission "C:\WINNT\TEMP\*", "write"; permission java.io.FilePermission "C:\WINNT\TEMP\*", "delete"; permission java.awt.AWTPermission "showWindowWithoutWarningBanner"; permission javax.sound.sampled.AudioPermission "record"; // //操作Socket端口的各种权限 permission java.net.SocketPermission "-", "listen"; permission java.net.SocketPermission "-", "accept"; permission java.net.SocketPermission "-", "connect"; permission java.net.SocketPermission "-", "resolve"; permission java.security.AllPermission; }; grant signedBy "saili" { permission java.net.SocketPermission "*:1024-65535", "connect,accept,resolve"; permission java.net.SocketPermission "*:80", "connect"; permission java.net.SocketPermission "-", "listen, accept, connect, listen, resolve", signedBy "ganja"; permission java.net.SocketPermission "-", "accept"; permission java.net.SocketPermission "-", "connect"; permission java.net.SocketPermission "-", "resolve"; permission java.security.AllPermission; };
笔者在本项目中,为了使用客户端的用户设置更加的方便与简单,将上面的文件采用VB或C#来做成一个小程序来写。然后将JRE及些exe共同打成一个EXE包,当JRE安装完成后,此小程序负责找到JRE在操作系统中的安装路径,并在程序中写出此java.policy文件,覆盖原有的文件。如此一来,用户就只需安装一个EXE文件,从而简化了安装的操作次数。
七、Applet回调服务器
JavaScript与Applet之间能够相互通讯给我们带来了很多方便,Java与JavaScript互相补充,以开发功能更完美的Web应用程序。B/S下能够充分利用java的优势,给我们带来更多的网络体验,方便用户。笔者用的比较多的是利用Swing组件开发的应用程序利用Applet实现B/s下架构,这样能够充分显示Swing组件的优势,便于系统升级,便于维护;还有就是在WEB下,有时客户端要使用本地的硬件资源,笔者所知道的是通过Applet来实现,通过Applet去调用javaAPI来实现。 我们具体来看看JavaScript与Applet之间到底是怎样通讯的呢?
1.JavaScript访问Applet
<applet name="appletName" ....../>//JavaScript访问Applet属性。
window.document.appletName.appletField (属性必须是public的,"window.document."也可以不写) //JavaScript访问Applet方法。
window.document.appletName.appletMethod (方法必须是public的,"window.document."也可以不写)。
2.Applet访问JavaScript
Live Connect提供了Java与JavaScript的接口,可以允许在Java Applet小程序中使用JavaScript。
需要用到一个jar包,在C:\Program Files\Java\目录下找,大概有5M多,其实就是打开看哪个有netscape.javascript.JSObject。如果没有装个NetScape或从网上下都可以。 可以把它重命名为netscape.jar(不是必须的),一定要加入到classpath,目的是使开发的时候能够编译。特别注意的是:部署时不需要包括netscape.jar,因为整个包会下载到客户端,影响速度。
//引入netscape类 import netscape.javascript.JSObject; import netscape.javascript.JSException; //可允许在小程序中处理异常事件 public void callJavaScript(String callBackJavascript) { JSObject window = JSObject.getWindow(this); // 获取JavaScript窗口句柄,引用当前文档窗口 JSObject docment = (JSObject) window.getMember("document"); form=(JSObject)doc.getMember("textForm"); //访问JavaScript form对象 textField=(JSObject)form.getMember("textField");访问JavaScript text对象 text=(String) textField.getMember("value"); //获取文本区的值 // 调用JavaScript的alert()方法 // window.eval("alert(\"This alert comes from Java!\")"); window.call(callBackJavascript, null);// 参数用数组的形式表示。 }
八、运行效果
1.上传
(1).启动上传上面
(2).上传中
(3).上传中
(4).上传成功
2.下载
(1)下载文件的保存路径
(2)下载中
(3)下载中
(4)下载成功
九、小结
在本文中,笔者将在实际项目中的上传下载问题的解决方案进行了阐述,通过采用FTP协议,来达到批量的,基本Web的大文件的上传下载。同时通过Applet技术实现在客户端对本地资源进行访问。就一些大家经常遇到的实际功能如进度条、断点续传、FTP内外网映射等问题进行了初步的探讨。这是笔者基于一些FTP的Java客户端库的基础应用。希望对读者有一定的借鉴作用。对其中一些未尽事宜进行补充。还有一些比较容易而且网上都有说明或实例的内容在此没有一一列举。如FTP在服务器端Serv-U软件如何建立FTP服务、Applet在JSP页面中的嵌入方式及参数传递方法、在Eclipse或是NetBeans下开始Applet等内容,由于篇幅的限制,并没有详尽描述,请读者参考网上的例子或其他参考资料。
下载地址:http://xiazai.jb51.net/201608/yuanma/FTPTransfer(jb51.net).rar
注释,考虑到版权的问题,没有把JAVA类文件发上来,不过这样的JAR文件如何还原成java文件,我想大家已经是很熟悉了吧,呵呵.
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。