公司要求实现一个大文件上传的功能,使用 JSP + SERVLET 。
如果只是上传大文件,使用cos是很好实现的。cos的上传原理是边读边写,开发人员可以自己分配缓存的大小。所以不会造成文件太大导致系统崩溃的后果。
但是后来要求实现断点续传的功能,我尝试了使用HTTP协议,不行。后来查找资料,发现许多人使用ftp协议。如果要使用ftp,又需要在客户端安装applet,据说applet太麻烦也不太好用,所以pass。
最后找到一个使用FlashPlayer10实现断点上传的例子,发现是可行的。
注:以下所包含的代码绝非原创,如有雷同绝对正常。
FlashPalyer10 以前的版本也可以上传文件,但大小却被限制在了100M以内。而10和以后的版本能够上传大文件。最大能到多少我没有试过,我试过的最大值为1G。
如果不熟悉FlashPlayer编程的请google Flex ActionScript3。
我以前也一直以为FlashPlayer就是用来看Flash的播放器,类似于暴风影音迅雷看看。
断点上传主要使用的是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接收数据时触发,我们可以在这里向服务器传送文件的具体内容。
服务器得到上传的文件名后,在对应位置找是否已存在该文件,如果存在则得到文件大小并告诉客户端,客户端通过该信息决定从什么位置开始上传文件。如果不存在则创建文件并从头开始上传。
客户端每次上传一定大小的数据到服务器(大小自定义),如此循环直到上传完成。
下面上代码:
Upload.as
- package com
- {
- import flash.events.Event;
- import flash.events.ProgressEvent;
- import flash.net.FileReference;
- import flash.net.Socket;
- import mx.controls.Alert;
- import mx.controls.ProgressBar;
- public class Upload
- {
- private var socket:Socket ;
- private var fileRef:FileReference ;
- private var progressBar:ProgressBar;
- private var status:int = 0;//上传成功状态
- private var successMethod:Function;
- public function getStatus():int{
- return status;
- }
- public function setStatus(){
- status = 1;
- successMethod.call();
- }
- public function Upload(fileRef:FileReference){
- socket = new Socket;
- this.fileRef = fileRef;
- var scope = this;
- //监听是否连接
- socket.addEventListener(Event.CONNECT,function conn(){
- var temp = 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(){
- //已上传大小
- 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();
- }
- }
- progressBar.setProgress(getProcess(uint(len),fileRef.data.length),100);
- });
- socket.addEventListener(Event.CLOSE,function close(){
- // Alert.show("传送完毕")
- })
- }
- private function getProcess(len , length):int{
- var result ;
- 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("127.0.0.1",2424); //连接服务器
- }
- }
- }
这是一个AS类,在构造函数中传入上传文件的对象,并初始化两个监听事件。
flex.mxml
- <?xml version="1.0" encoding="utf-8"?>
- <mx:Application backgroundColor="#FFFFFF" backgroundAlpha="2" xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="474" height="310"
- >
- <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:Label id="uploadLabel" text="请选择文件" fontSize="12" x="117" y="10" height="24" width="82"/>
- <mx:ProgressBar id="bar" labelPlacement="bottom" x="117" y="102" visible="false" mode="manual"/>
- <mx:Script>
- <![CDATA[
- import mx.controls.Alert;
- import com.Upload;
- import flash.net.Socket;
- import flash.external.ExternalInterface;
- 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;
- });
- // var gifFilter:FileFilter = new FileFilter("GIF Images", "*.gif", "GIFF");
- this.fileRef.browse();
- }
- private function toUpload():void{
- if(fileRef.data == null){
- // Alert.("请选择文件")
- this.fileBrowse();
- return;
- }
- var upload:Upload = new Upload(fileRef);
- upload.doUpload(bar , setSuccess);
- }
- private function setSuccess():void{
- ExternalInterface.call("setUploadFileName",fileRef.name);
- uploadLabel.text = "上传成功"
- doUpload.enabled= false;
- }
- ]]>
- </mx:Script>
- </mx:Application>
这是flex的mxml文件,点击“选择”按钮后,会弹出文件选择框,选择你要上传的文件后,程序会读取该文件的内容(这里的读取应该是一次性读取所有文件内容,所以文件较大时,会多花几秒钟)。点击上传按钮则开始与服务器进行连接。
LargeFileUploadPolicyListener.java
- package com;
- import java.net.ServerSocket;
- import java.net.Socket;
- import javax.servlet.ServletContextEvent;
- public class LargeFileUploadPolicyListener extends javax.servlet.http.HttpServlet implements javax.servlet.ServletContextListener{
- private static final long serialVersionUID = 1L;
- 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() {
- 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();
- }
- }
- }
随着服务器的启动而启动,开始侦听843端口的连接。
MyPolicyServerThread.java
- package com;
- import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.io.PrintStream;
- import java.io.UnsupportedEncodingException;
- import java.net.Socket;
- public class MyPolicyServerThread extends Thread {
- 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)) {
- 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();
- }
- }
- }
接收客户端发送过来的认证请求并进行验证。注意cross_xml 变量,客户端与服务器传送文件的端口就在这里设置的。
LargeFileUploadListener.java
- package com;
- import java.net.ServerSocket;
- import java.net.Socket;
- import javax.servlet.ServletContextEvent;
- public class LargeFileUploadListener extends javax.servlet.http.HttpServlet implements javax.servlet.ServletContextListener{
- private static final long serialVersionUID = 1L;
- 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() {
- System.out.println("大文件上传侦听开始");
- try{
- ServerSocket serverSocket= new ServerSocket(Integer.parseInt("2424"));//服务器套接字
- Socket clientSocket=null;
- while(true){
- clientSocket= serverSocket.accept();//获得客户端的请求的Socket
- System.out.println("已侦听到了客户端的请求。。。。。");
- new MyServerThread(clientSocket);
- }
- }catch (Exception e) {
- e.printStackTrace();
- }
- }
- };
- thread.start();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
随着服务器的启动而运行,侦听文件传送端口。
MyServerThread.java
- package com;
- 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;
- public class MyServerThread extends Thread {
- private Socket socket;
- public MyServerThread(Socket socket ) {
- this.socket = socket;
- this.start();
- }
- @Override
- public void run() {
- System.out.println("run");
- 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());
- //读取名称
- String fileName = dataInputStream.readUTF();
- String fileSize = dataInputStream.readUTF();
- File f = new File(this.getClass().getResource("").getPath());
- System.out.println(f.getPath().substring(0,f.getPath().indexOf("WEB-INF"))+"upload/");
- //检测上传文件是否存在
- String FilePath = f.getPath().substring(0,f.getPath().indexOf("WEB-INF"))+"upload"; //可使用配置文件形式将路径写清楚
- 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 = null;
- 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通信
- } catch (Exception e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }finally{
- if(fileLock!=null){
- }
- try {
- if(fileChannel!=null){
- fileChannel.close();
- }
- if(randomAccessFile!=null){
- randomAccessFile.close();
- }
- dataInputStream.close();
- dataOutputStream.close();
- socket.close();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
- }
接收上传文件类。