如果只是上传大文件,使用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>