簡單的檔案上傳是將整個檔案在一起此請求中將檔案上傳至伺服器中,而對戶外作業平台而言,網路的穩定性是一個問題,加之平台的大檔案性,故而在該平台中採用斷點續傳方式來上傳檔案較為合適。
斷點續傳簡單來講,用戶端與伺服器端通訊,瞭解到已傳輸檔案的大小後,而後在按照一定的檔案塊大小對剩餘未傳輸的檔案進行分塊傳輸,伺服器端則將這些塊檔案一點點寫入到同一個檔案中,從而形成一個完成檔案,達到斷點續傳的目的。用戶端邏輯示意如下所示:
Flex的檔案上傳下載會使用到FileReference,而在FlashPlayer10以後提供了Load方法和data屬性。在FileReference執行完load方法後,data中便儲存了FileReference所代表的檔案的資料,其為ByteArray對象,即我們可以以位元組的形式訪問Flex載入的檔案,而這也為在斷點續傳檔案時對檔案進行分塊處理提供了可能性。
Flex與java通訊上傳檔案的實現方式可以有多種,本文採用socket通訊方式來傳輸檔案。Flex端代碼如下所示:
//利用socketsocket = new Socket;socket.connect("12.156.53.29",1234); //串連伺服器//監聽是否串連 socket.addEventListener(Event.CONNECT,function conn(){ //發送名稱 socket.writeUTF(fileReference.name); socket.flush(); //檔案大小 socket.writeUTF(String(fileReference.data.length)); socket.flush();}); //監聽接受資料socket.addEventListener(ProgressEvent.SOCKET_DATA,function receiveData(){//已上傳大小var len:String = socket.readUTF(); if(len == "0"){ if(fileReference.data.length < 1024){ socket.writeBytes(fileReference.data); }else{ socket.writeBytes(fileReference.data,0,1024); } socket.flush(); }else{ if((fileReference.data.length - uint(len)) > 1024){ socket.writeBytes(fileReference.data,uint(len),1024);}else{ socket.writeBytes(fileReference.data,uint(len), fileReference.data.length - uint(len));}socket.flush(); }});//監聽串連關閉 socket.addEventListener(Event.CLOSE,functioncloseConn(){ }); 在flash.net包中存在Socket類,在文檔的描述中,我們可以瞭解到socket通訊需要使用通訊端策略檔案。在實際的socket通訊過程中,我們在用戶端不管發送什麼資訊,在服務端的socket第一次接收的資訊都會是<policy-file-request/>。這個資訊是在要求服務端提供給用戶端socket通訊的策略檔案,在該檔案中指定通訊的連接埠號碼等資訊。這個過程涉及到Flash Player安全機制,不過多講述,瞭解到flex進行跨域socket通訊時預設必須要在843連接埠上接收Flash Player的策略檔案請求。此處需要注意的是對策略檔案的請求和斷點續傳過程主動發起的請求同服務端的socket串連是兩個獨立的串連,在處理完策略檔案請求串連後,我們要關閉策略檔案請求的串連,這樣Flash Player會自動重新串連,從而可實現斷點續傳的socket串連,否則我們的主動請求將無法串連上。
針對上面術的內容,我做了這樣處理:建立兩個兩個listener,一個監聽對策略檔案的請求,一個監聽對斷點續傳socket串連的請求,後者是我們的主請求。在每個監聽器使用多執行緒,每次接受到socket串連請求,就會建立一個線程用於處理策略檔案請求或者斷點續傳請求。
Web.xml中配置listener
<!-- 大檔案上傳連接埠監聽器 --> <listener> <display-name>myListener</display-name> <listener-class>com. fileoperation.LargeFileUploadListener</listener-class> </listener><!-- 大檔案上傳連接埠安全性原則監聽器 --> <listener> <display-name>policyListener</display-name> <listener-class>com. fileoperation.LargeFileUploadPolicyListener</listener-class> </listener>
LargeFileUploadPolicyListener.java:
package com.fileoperation;import java.net.ServerSocket;import java.net.Socket;import javax.servlet.ServletContextEvent;import org.apache.log4j.Logger;public class LargeFileUploadPolicyListener extends javax.servlet.http.HttpServlet implements javax.servlet.ServletContextListener{private static final long serialVersionUID = 1L;private static final Logger log = Logger.getLogger(LargeFileUploadListener.class);private static Thread thread = null;@SuppressWarnings("deprecation")public void contextDestroyed(ServletContextEvent arg0) {if(thread != null){thread = null;}}public void contextInitialized(ServletContextEvent arg0) {try { thread = new Thread() {public void run() {log.info("大檔案上傳偵聽開始。。。。"); try{ServerSocket policyServerSocket= new ServerSocket(Integer.parseInt("843"));//伺服器通訊端 Socket policyClientSocket = null; Socket clientSocket=null; while(true){ policyClientSocket = policyServerSocket.accept(); //獲得用戶端的請求的Socket log.info("已偵聽到了用戶端的請求。。。。。"); new MyPolicyServerThread(policyClientSocket); } }catch (Exception e) {log.error("接收大檔案異常:",e);}}};thread.start(); } catch (Exception e) { log.error("啟動監聽器異常:",e); } }}MyPolicyServerThread.java:
package com. fileoperation;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.PrintStream;import java.io.UnsupportedEncodingException;import java.net.Socket;import org.apache.log4j.Logger;public class MyPolicyServerThread extends Thread {private static final Logger log = Logger.getLogger(MyServerThread.class);private Socket socket; private final String policy_xml = "<policy-file-request/>"; private final String cross_xml = "<?xml version=\"1.0\"?>" + "<cross-domain-policy>" + "<site-control permitted-cross-domain-policies=\"all\"/>" + "<allow-access-from domain=\"*\" to-ports=\"1234\"/>" + "</cross-domain-policy>\0";public MyPolicyServerThread(Socket socket) {this.socket = socket;this.start();}@Overridepublic void run() {try {BufferedReader readerIn = new BufferedReader(new InputStreamReader(socket.getInputStream()));PrintStream printOut = new PrintStream(socket.getOutputStream());char[] by = new char[22];readerIn.read(by, 0, 22);String s = new String(by);if (s.equals(policy_xml)) {System.out.println("接收policy-file-request認證");printOut.print(cross_xml);printOut.flush();readerIn.close();printOut.close();socket.close();System.out.println("完成policy-file-request認證");} } catch (UnsupportedEncodingException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}}上面是策略檔案socket串連的處理過程,此過程是Flash Player預設發起的,在用戶端無需我們人工幹預,只需要在服務端進行處理即可,在上述策略檔案中我們發現,其指定了to-ports=\"1234\",即指明了socket主動請求的socket連接埠號碼為1234,而在關閉此策略socket串連後,FlashPlayer重新串連的連接埠號碼即為1234了。下面的服務端處理過程是與我們的主動請求(斷點續傳)相關聯的,在上文斷點續傳邏輯圖中,唯寫明了用戶端的邏輯,而服務端的邏輯實際上是與之相對對應的,而核心是java.io.RandomAccessFile將檔案分批寫入相應檔案下。下面代碼中發起的線程執行情況是與前文所講Flex端的socket相互連信共同實現斷點續傳上傳檔案功能的。
LargeFileUploadListener.java
package com. fileoperation;import java.net.ServerSocket;import java.net.Socket;import javax.servlet.ServletContextEvent;import org.apache.log4j.Logger;public class LargeFileUploadListener extends javax.servlet.http.HttpServlet implements javax.servlet.ServletContextListener{private static final long serialVersionUID = 1L;private static final Logger log = Logger.getLogger(LargeFileUploadListener.class);private static Thread thread = null;@SuppressWarnings("deprecation")public void contextDestroyed(ServletContextEvent arg0) {if(thread != null){thread = null;}}public void contextInitialized(ServletContextEvent arg0) {try { thread = new Thread() {public void run() {log.info("大檔案上傳偵聽開始。。。。"); try{ ServerSocket serverSocket= new ServerSocket(Integer.parseInt("1234"));//伺服器通訊端 Socket clientSocket=null; while(true){ clientSocket= serverSocket.accept();//獲得用戶端的請求的Socket log.info("已偵聽到了用戶端的請求。。。。。"); new MyServerThread(clientSocket); } }catch (Exception e) {log.error("接收大檔案異常:",e);}}};thread.start(); } catch (Exception e) { log.error("啟動監聽器異常:",e); } }}MyServerThread.java:
package com.ibm.rise.workplace.fileoperation;import java.io.DataInputStream;import java.io.DataOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream;import java.io.RandomAccessFile;import java.net.Socket;import java.nio.channels.FileChannel;import java.nio.channels.FileLock;import java.util.Map;import java.util.StringTokenizer;import org.apache.log4j.Logger;public class MyServerThread extends Thread {private static final Logger log = Logger.getLogger(MyServerThread.class);private Socket socket;public MyServerThread(Socket socket) {this.socket = socket;this.start();}@Overridepublic void run() {DataInputStream dataInputStream = null;DataOutputStream dataOutputStream = null;RandomAccessFile randomAccessFile = null;boolean isInfoSubmission = false; Document docInfoSubmission = null; Double totalSize = 0.0; DocProperty docProperty = null; try {dataInputStream = new DataInputStream(socket.getInputStream());dataOutputStream = new DataOutputStream(socket.getOutputStream()); //讀取名稱String fileName = dataInputStream.readUTF();String fileSize = dataInputStream.readUTF();//檢測上傳檔案是否存在String FilePath = “file path”;//可使用設定檔形式將路徑寫清楚 StringTokenizer st = new StringTokenizer(FilePath.toString(),"/"); String toAddPath = st.nextToken()+"/"; String toTestPath = toAddPath; while(st.hasMoreTokens()){ toAddPath = st.nextToken()+"/"; toTestPath += toAddPath; File inbox = new File(toTestPath); if(!inbox.exists()) { inbox.mkdir(); } } //檢測上傳位置 File file = new File( FilePath + "/" + fileName); long position = 0; if(file.exists()){ position = file.length(); } //通知用戶端已傳大小 dataOutputStream.writeUTF(String.valueOf(position)); dataOutputStream.flush(); byte[] buffer = null; int read = 0; while(true){ //檢測上傳位置 file = new File( FilePath + "/" + fileName); position = 0; if(file.exists()){ position = file.length(); } //rw代表寫流(隨機讀寫) randomAccessFile = new RandomAccessFile(file,"rw"); FileLock fileLock = null; FileChannel fileChannel = null; fileChannel = randomAccessFile.getChannel(); fileLock = fileChannel.tryLock(); //拿到了檔案鎖,寫入資料 if(fileLock != null){ randomAccessFile.seek(position); read = 0; buffer = new byte[1024]; read = dataInputStream.read(buffer); randomAccessFile.write(buffer,0,read); if(fileLock != null){ fileLock.release(); fileLock = null; } if(randomAccessFile != null){ randomAccessFile.close(); randomAccessFile = null; } } //檢測已上傳的大小 file = new File( FilePath + "/" + fileName); position = 0; if(file.exists()){ position = file.length(); } System.out.println("檔案 " + fileName + " 已傳輸 " + String.valueOf(position)+ "位元組"); //判斷檔案是否傳輸完成 if(position >= Long.parseLong(fileSize)){ //檔案傳輸完成 System.out.println("檔案 " + fileName + " 已傳輸完畢,總大小為" + String.valueOf(position) + "位元組"); break ; }else{ //通知用戶端已傳大小 dataOutputStream.writeUTF(String.valueOf(position)); dataOutputStream.flush(); } }// END WHILE //跳出while迴圈,即已結束檔案上傳,則終止socket通訊 dataInputStream.close(); dataOutputStream.close(); socket.close(); } catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}flex利用socket通訊,並不需要像通過remoteObject方式訪問java端服務一樣配置相關服務,只需要在flex端的mxml或as檔案中進行socket串連時寫明串連的ip和連接埠即可進行通訊,但需要注意的是需要對FlashPlayer所要求的策略檔案進行相應處理。此外斷點續傳所要做的核心內容是將整個檔案在一次請求中傳遞改為多次請求中分塊傳遞,服務端利用相關類對區塊檔案進行整合即可。