如果只是上传大文件,使用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端口发送以下内容。
- <policy-file-request/>
2、服务器必须向客户端返回以下内容,说明验证成功。
- <?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
注意两个地方,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>