接著上一篇進行學習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檔案,我想大家已經是很熟悉了吧,呵呵.
以上就是本文的全部內容,希望對大家的學習有所協助,也希望大家多多支援雲棲社區。