Flex + Servlet 实现断点上传

如果只是上传大文件,使用cos是很好实现的。cos的上传原理是边读边写,开发人员可以自己分配缓存的大小。所以不会造成文件太大导致系统崩溃的后果。

但是后来要求实现断点续传的功能,我尝试了使用HTTP协议,不行,我印象中,HTTP协议来说,应该不好实现断点上传。后来查找资料,发现许多人使用ftp协议。如果要使用ftp,又需要在客户端安装applet,据说applet太麻烦也不太好用,applet一般很少有人去开发这个了还需要jvm环境。

最后找到一个使用FlashPlayer10实现断点上传的例子,发现是可行的。

因为现在很多浏览器都是支持flash的,所以都会安装了flash的activeX组件。起作用的 就应该是activeX组件了。

注:以下所包含的代码绝非原创,如有雷同绝对正常。

FlashPalyer10 以前的版本也可以上传文件,但大小却被限制在了100M以内。而10和以后的版本能够上传大文件。最大能到多少我没有试过,我试过的最大值为1G。

如果不熟悉FlashPlayer编程的请google Flex ActionScript3。

断点上传主要使用的是AS3的Socket类完成和服务器的通讯。服务器会监听两个Socket端口,一个端口是843,用来完成FlashPlayer的安全策略认证;另一个端口是用户自定义,用来完成文件上传。

安全策略认证如下:

1、客户端往服务器843端口发送以下内容。

  
  
  
  
  1. <policy-file-request/>  

2、服务器必须向客户端返回以下内容,说明验证成功。

 

  
  
  
  
  1. <?xml version=\"1.0\"?> 
  2. <cross-domain-policy> 
  3. <site-control permitted-cross-domain-policies="all"/> 
  4. <allow-access-from domain="*" to-ports="2424"/> 
  5. </cross-domain-policy>\0 

注意两个地方,to-ports表示客户端通过该端口向服务器发送接收数据。另外,结尾处的\0必须添加。

安全策略认证完成后,客户端才真正发起与目标端口的连接。

客户端的Socket监听两个事件:Event.CONNECT,ProgressEvent.SOCKET_DATA

这两个事件时AS定义的常量。Event.CONNECT在Scoeket连接时触发,可以在这里向服务发送文件名和文件大小的基本信息。ProgressEvent.SOCKET_DATA在Socket接收数据时触发,我们可以在这里向服务器传送文件的具体内容。

服务器得到上传的文件名后,在对应位置找是否已存在该文件,如果存在则得到文件大小并告诉客户端,客户端通过该信息决定从什么位置开始上传文件。如果不存在则创建文件并从头开始上传。

客户端每次上传一定大小的数据到服务器(大小自定义),如此循环直到上传完成。

下面上代码:

FinderLargerUpload.as

package cn.cnxingkong.finder.upload
{
	import flash.events.Event;
	import flash.events.ProgressEvent;
	import flash.net.FileReference;
	import flash.net.Socket;
	
	import mx.controls.Alert;
	import mx.controls.ProgressBar;
	
	import org.flexunit.internals.namespaces.classInternal;

	public class FinderLargerUpload
	{
		
		private var socket:Socket ;      
		private var fileRef:FileReference ; 
		private var progressBar:ProgressBar; 
		private var status:int = 0;//上传成功状态 
		private var successMethod:Function; 
		
		
		
		private var uploadCompletedMethod:Function;
		
		private var host:String="127.0.0.1";
		
		private var port:int=80;
		
		
		public function getStatus():int{ 
			return status; 
		} 
		
		public function setStatus():void{ 
			status = 1; 
			successMethod.call(); 
		} 
		
		public function setHost(pHost:String):void{
			this.host = pHost;
		}
		
		public function setPort(pPort:int):void{
			
			this.port = pPort;
		}
		
		/***
		 * 暂时不使用
		 * 
		 */
		public function setUploadCompletedMethod(pUploadCompletedMethod:Function):void
		{
			this.uploadCompletedMethod = pUploadCompletedMethod;
		}
		
		
		public function FinderLargerUpload(fileRef:FileReference){ 
			socket = new Socket; 
			this.fileRef = fileRef; 
			var scope = this; 
			//监听是否连接     
			socket.addEventListener(Event.CONNECT,function conn(){ 
				var temp:FileReference = scope.fileRef; 
				//发送名称   
				socket.writeUTF(temp.name); 
				socket.flush(); 
				//文件大小  
				try{ 
					socket.writeUTF(String(temp.data.length));   
				}catch(e:Error){ 
					Alert.show(e.message); 
					//Alert.show(e.getStackTrace()); 
				} 
				
				socket.flush(); 
				
			});    
			
			
			//监听接受数据   
			socket.addEventListener(ProgressEvent.SOCKET_DATA,function receiveData():void{ 
				
				//已上传大小   
				var len:String = socket.readUTF(); 
				//                   Alert.show(int(uint(len) / fileRef.data.length * 100)  + "") 
				
				if(len == "0"){ 
					if(fileRef.data.length < 1024){ 
						socket.writeBytes(fileRef.data);   
					}else{ 
						socket.writeBytes(fileRef.data,0,1024);  
					}   
					socket.flush();   
				}else{ 
					if((fileRef.data.length - uint(len)) > 1024){ 
						socket.writeBytes(fileRef.data,uint(len),1024);   
						socket.flush(); 
					}else{ 
						socket.writeBytes(fileRef.data,uint(len), fileRef.data.length - uint(len)); 
						socket.flush(); 
						setStatus(); 
					}   
					
				}   
				var currPos:Number = getProcess(uint(len),fileRef.data.length);
				progressBar.setProgress(currPos,100); 
				//progressBar.label= (currPos/100)*100 +"%";
				
			});  
			
			socket.addEventListener(Event.CLOSE,function close():void{
				if(uploadCompletedMethod==null)
					uploadCompletedMethod.call();
							  
			});
		} 
		
		private function getProcess(len:int , length:int):int{ 
			var result:int ; 
			if(length - len < 1024){ 
				result = 100; 
			}else{ 
				result = int(uint(len) / fileRef.data.length * 100);         
			} 
			return result; 
		} 
		
		public function doUpload(bar , fn){ 
			progressBar = bar; 
			successMethod = fn; 
			progressBar.visible = true; 
			//          socket..connect("192.168.0.121",2424); //连接服务器 
			socket.connect(host,port); //连接服务器  
		} 
		
		public function getSocket():Socket{
			return this.socket;
		}
		
		//关闭socket,停止上传
		public function close():void{
			this.socket.close();
		}
		
	}
}


这是一个AS类,在构造函数中传入上传文件的对象,并初始化两个监听事件。

UploadApp.mxml

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
			   xmlns:s="library://ns.adobe.com/flex/spark" 
			   xmlns:mx="library://ns.adobe.com/flex/mx" 
			  backgroundColor="234223"
			    width="474" height="310">
	<s:layout>
		<s:BasicLayout/>
	</s:layout>
	<fx:Declarations>
		<!-- 将非可视元素(例如服务、值对象)放在此处 -->
	</fx:Declarations>
	
	
	<fx:Script>
		<![CDATA[
			import cn.cnxingkong.finder.upload.FinderLargerUpload;
			
			
			import flash.external.ExternalInterface;
			import flash.net.Socket;
			
			import mx.controls.Alert; 
			
			
			private var fileRef:FileReference ; 
			
			
			public function fileBrowse():void{ 
				fileRef = new FileReference(); 
				fileRef.addEventListener(Event.SELECT, function select(){
					
					fileRef.load();
					uploadLabel.text  =  "加载文件中...." 
				}); 
				fileRef.addEventListener(Event.COMPLETE,function onComplete(){ 
					uploadLabel.text = fileRef.name; 
					doUpload.enabled = true; 
					doPause.enabled = true;
				}); 
				
				//          var gifFilter:FileFilter = new FileFilter("GIF Images", "*.gif", "GIFF"); 
				this.fileRef.browse(); 
			} 
			
			var upload:FinderLargerUpload;
			private function toUpload():void{ 
				if(fileRef.data == null){ 
					//                  Alert.("请选择文件") 
					this.fileBrowse(); 
					return; 
				} 
				upload = new FinderLargerUpload(fileRef); 
				upload.setHost("127.0.0.1");
				upload.setPort(2424);
				upload.doUpload(bar , setSuccess);
				
				
			} 
			
			private function pauseUpload():void{
				upload.close();
			}
			
			private function setSuccess():void{ 
				ExternalInterface.call("setUploadFileName",fileRef.name);
				uploadLabel.text = "上传成功" 
				doUpload.enabled= false;
				doPause.enabled =false;
			} 
		]]>
	</fx:Script>
	
	
	
	
	<mx:Button id="uploadds" label="选择..." fontSize="12" click="fileBrowse()" x="117" y="42" height="33" width="82"/> 
	<mx:Button id="doUpload" label="上传" fontSize="12" enabled="false" click="toUpload()" x="207" y="42" height="33" width="82"/> 
	<mx:Button id="doPause" label="停止" fontSize="12" enabled="false" click="pauseUpload()" x="300" y="42" height="33" width="82"/> 
	<mx:Label id="uploadLabel" text="请选择文件" fontSize="12" x="117" y="10" height="24" width="82"/> 
	<mx:ProgressBar id="bar" labelPlacement="center" x="117" y="102" visible="false"/> 
</s:Application>




 

这是flex的mxml文件,点击“选择”按钮后,会弹出文件选择框,选择你要上传的文件后,程序会读取该文件的内容(这里的读取应该是一次性读取所有文件内容,所以文件较大时,会多花几秒钟)。点击上传按钮则开始与服务器进行连接。

 LargeFileUploadPolicyListener.java

package cn.cnxingkong.finder.largerupload;
import java.net.ServerSocket;
import java.net.Socket;

import javax.servlet.ServletException;

import org.apache.log4j.Logger;


public class LargeFileUploadPolicyListener extends javax.servlet.http.HttpServlet{   
   
   
    /**
	 * 
	 */
	private static final long serialVersionUID = 4220986582714447557L;
	
	private static Logger logger = Logger.getLogger(LargeFileUploadPolicyListener.class);
	
	private static Thread thread = null;  
	
	
   /*
    public void contextDestroyed(ServletContextEvent arg0) {   
        if(thread != null){   
            thread = null;   
        }   
    }   
   
    public void contextInitialized(ServletContextEvent arg0) {   
        try {     
            thread = new Thread() {   
                public void run() {   
                    System.out.println("大文件上传侦听开始。。。。"); 
                    try{   
                        ServerSocket policyServerSocket= new ServerSocket(Integer.parseInt("843"));//服务器套接字   
                           
                        Socket policyClientSocket = null;   
                           
                        Socket clientSocket=null;     
                        while(true){     
                               
                            policyClientSocket = policyServerSocket.accept(); //获得客户端的请求的Socket    
                            System.out.println("已侦听到了客户端的请求。。。。。"); 
                            new MyPolicyServerThread(policyClientSocket);   
                        }     
                    }catch (Exception e) {   
                        e.printStackTrace(); 
                    }   
                }   
            };   
            thread.start();   
        } catch (Exception e) {     
//            log.error("启动监听器异常:",e);   
            e.printStackTrace(); 
        }    
    }   */
	
	public void init() throws ServletException {
		try {     
            thread = new Thread() {   
                public void run() {   
                    logger.info("大文件上传策略认证侦听开始。。。。"); 
                    try{   
                        ServerSocket policyServerSocket= new ServerSocket(Integer.parseInt("843"));//服务器套接字   
                           
                        Socket policyClientSocket = null;   
                        while(true){     
                            policyClientSocket = policyServerSocket.accept(); //获得客户端的请求的Socket    
                            logger.info("已侦听到了客户端策略认证的请求。。。。。"); 
                            new MyPolicyServerThread(policyClientSocket);   
                        }     
                    }catch (Exception e) {   
                       logger.error(e.toString()); 
                    }   
                }   
            };   
            thread.start();   
        } catch (Exception e) {     
        	  logger.error(e.toString());
        }
	}
}   

 

随着服务器的启动而启动,开始侦听843端口的连接。

MyPolicyServerThread.java

package cn.cnxingkong.finder.largerupload;
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 Logger logger = Logger.getLogger(LargeFileUploadPolicyListener.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=\"2424\"/>" +   
            "</cross-domain-policy>\0";   
   
    public MyPolicyServerThread(Socket socket) { 
        this.socket = socket;   
        this.start();   
    }   
   
    @Override   
    public 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)) {   
                logger.info("接收policy-file-request认证");   
                printOut.print(cross_xml);   //发送 cross_xml
                printOut.flush();   
                readerIn.close();   
                printOut.close();   
                socket.close();   
                logger.info("完成policy-file-request认证");   
            }    
   
        } catch (Exception e) { 
        	 logger.error(e.toString()); 
        }   
    }   
}   



接收客户端发送过来的认证请求并进行验证。注意cross_xml 变量,客户端与服务器传送文件的端口就在这里设置的。

LargeFileUploadListener.java

package cn.cnxingkong.finder.largerupload;

import java.net.ServerSocket;
import java.net.Socket;

import javax.servlet.ServletException;

import org.apache.log4j.Logger;
 
public class LargeFileUploadListener extends javax.servlet.http.HttpServlet{   
	private static Logger logger = Logger.getLogger(LargeFileUploadPolicyListener.class);
     private static final long serialVersionUID = 1L;   
     private static Thread thread = null;
	@Override
	public void init() throws ServletException {
		   try {     
	            thread = new Thread() {   
	                public void run() { 
	                    logger.info("大文件上传侦听开始"); 
	                     try{ 
	                 
	                         ServerSocket serverSocket= new ServerSocket(Integer.parseInt("2424"));//服务器套接字     
	                       Socket clientSocket=null;     
	                         while(true){     
	                            clientSocket= serverSocket.accept();//获得客户端的请求的Socket     
	                             logger.info("已侦听到了客户端开始上传文件的请求。。。。。"); 
	                            new MyServerThread(clientSocket);     
	                       }     
	                    }catch (Exception e) {   
	                       e.printStackTrace(); 
	                     }   
	                 }   
	             };   
	            thread.start();   
	         } catch (Exception e) {     
	             logger.error(e.toString());
	         }    
	} 
  
   /*  @SuppressWarnings("deprecation")   
    public void contextDestroyed(ServletContextEvent arg0) {   
         if(thread != null){   
             thread = null;   
        }   
    }   
    
     public void contextInitialized(ServletContextEvent arg0) { 
        try {     
            thread = new Thread() {   
                public void run() { 
                    logger.info("大文件上传侦听开始"); 
                     try{ 
                 
                         ServerSocket serverSocket= new ServerSocket(Integer.parseInt("2424"));//服务器套接字     
                       Socket clientSocket=null;     
                         while(true){     
                            clientSocket= serverSocket.accept();//获得客户端的请求的Socket     
                             logger.info("已侦听到了客户端的请求。。。。。"); 
                            new MyServerThread(clientSocket);     
                       }     
                    }catch (Exception e) {   
                       e.printStackTrace(); 
                     }   
                 }   
             };   
            thread.start();   
         } catch (Exception e) {     
             e.printStackTrace(); 
         }    
     }   
        */
     
     
 }   

 

随着服务器的启动而运行,侦听文件传送端口。

MyServerThread.java

 

package cn.cnxingkong.finder.largerupload;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.Socket;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.StringTokenizer;

import org.apache.log4j.Logger;

public class MyServerThread extends Thread {
	private static Logger logger = Logger
			.getLogger(LargeFileUploadPolicyListener.class);
	private Socket socket;
	private String fileName;
	private long position = 0;

	private String uploadFileRootPath = "upload";

	public void setUploadRootPath(String uploadFileRootPath) {
		this.uploadFileRootPath = uploadFileRootPath;
	}

	public MyServerThread(Socket socket) {
		this.socket = socket;
		this.start();
	}

	@Override
	public void run() {

		logger.info("数据上传中。。。");
		DataInputStream dataInputStream = null;
		DataOutputStream dataOutputStream = null;
		RandomAccessFile randomAccessFile = null;
		FileLock fileLock = null;
		FileChannel fileChannel = 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());

			// 读取名称
			fileName = dataInputStream.readUTF();

			String fileSize = dataInputStream.readUTF();

			File f = new File(this.getClass().getResource("").getPath());
			logger.info(f.getPath()
					.substring(0, f.getPath().indexOf("WEB-INF"))
					+ uploadFileRootPath + "/"+fileName);

			// 检测上传文件是否存在
			String FilePath = f.getPath().substring(0,
					f.getPath().indexOf("WEB-INF"))
					+ uploadFileRootPath; // 可使用配置文件形式将路径写清楚
			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);

			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 = null;
				fileChannel = null;
				fileChannel = randomAccessFile.getChannel();

				// 保证当前只有自己操作此文件
				fileLock = fileChannel.tryLock();

				// 拿到了文件锁,写入数据
				if (fileLock != null) {
					randomAccessFile.seek(position);

					read = 0;
					buffer = new byte[1024];

					if (!"0".equals(fileSize)) {
						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();
				}
				// logger.info("文件  " + fileName + "  已传输  " +
				// String.valueOf(position)+ "字节");

				// 判断文件是否传输完成
				if (position >= Long.parseLong(fileSize)) {
					// 文件传输完成
					logger.info("文件  " + fileName + "  已传输完毕,总大小为"
							+ String.valueOf(position) + "字节");
					break;
				} else {
					// 通知客户端已传大小
					dataOutputStream.writeUTF(String.valueOf(position));
					dataOutputStream.flush();
				}
			} // END WHILE

			// 跳出while循环,即已结束文件上传,则终止socket通信

		} catch (Exception e) {
			// TODO Auto-generated catch block
			logger.info("文件  " + fileName + " 传输中断,总大小为"
					+ String.valueOf(position) + "字节");
			  logger.error(e.toString());
		} finally {
			if (fileLock != null) {
				try {
					fileLock.release();
					fileLock = null;
					logger.info("文件  " + fileName + " lock release");
				} catch (Exception e) {
					  logger.error(e.toString());
				}
			}
			try {
				if (fileChannel != null) {
					fileChannel.close();
				}
				if (randomAccessFile != null) {
					randomAccessFile.close();
				}
				dataInputStream.close();
				dataOutputStream.close();
				socket.close();
			} catch (Exception e) {
				  logger.error(e.toString());
			}
		}
	}
}

接收上传文件类。

Web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 
	xmlns="http://java.sun.com/xml/ns/javaee" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
	http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
  
   <servlet>
	    <servlet-name>largeFileUploadPolicyListener</servlet-name>
	    <servlet-class>cn.cnxingkong.finder.largerupload.LargeFileUploadPolicyListener</servlet-class>
	    <load-on-startup>3</load-on-startup>
	</servlet>
	
	<servlet>
	    <servlet-name>largeFileUploadListener</servlet-name>
	    <servlet-class>cn.cnxingkong.finder.largerupload.LargeFileUploadListener</servlet-class>
	    <load-on-startup>3</load-on-startup>
	</servlet> 
</web-app>




你可能感兴趣的:(Flex + Servlet 实现断点上传)