在日常工作中,有时候需要到远程服务器上部署新版本的系统,由于远程服务器出于外网,所以每次都要开QQ连接,非常麻烦。索性就研究了下IHttpasyncHandler,并结合Juqery ProgressBar,打造了一款大文件传送器。其基本原理就是首先在客户端将大文件分段拆分,然后写入内存流,最后发送到服务器上。在上传的同时,会利用异步Handler来获取当前进度并推送到前台显示。图示效果如下(当前缓存设置为5000字节大小):
图示结果
(图1,传送到36%的时候)
(图2,传送到100%的时候)
异步Handler设计
下面来说说具体的实现方式,首先来说说实时通知这块:
using System;
using System.Collections.Generic;
using System.Web;
using AsyncThermometerDeamon.Handlers.Code; namespace AsyncThermometerDeamon.Handlers
{
public class FileUploadWatcher : IHttpAsyncHandler
{
public static List<AsyncResultDaemon> asyncResults = new List<AsyncResultDaemon>();
public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
{
AsyncResultDaemon asyncResult = new AsyncResultDaemon(context,cb,extraData);
asyncResults.Add(asyncResult);
return asyncResult;
} public void EndProcessRequest(IAsyncResult result)
{
asyncResults.Clear();
AsyncResultDaemon aResult = result as AsyncResultDaemon;
aResult.Send();
} public bool IsReusable
{
get { return false; }
} public void ProcessRequest(HttpContext context)
{
throw new NotImplementedException();
}
}
}
在程序中,首先申明了一个List容器,以便保存当前的请求。然后当有请求进来的时候,BeginProcessRequest会将当前请求进行初始化,然后加入到List容器中,最后将执行结果返回。
而在EndProcessRequest函数中,一旦当前的操作完成,将会触发此函数推送当前的进度数据到前台。
这里我们来看一下AsyncResultDaemon类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web; namespace AsyncThermometerDeamon.Handlers.Code
{
public class AsyncResultDaemon:IAsyncResult
{
public AsyncResultDaemon(HttpContext context, AsyncCallback cb,object extraData)
{
this.context = context;
this.cb = cb;
} private HttpContext context;
private AsyncCallback cb;
private object extraData;
public long percent;
public bool isCompleted = false; public void Send()
{
context.Response.Write(percent);
} public void CompleteTask()
{
if (cb != null)
{
cb(this);
this.isCompleted = true;
}
} public object AsyncState
{
get { return null; }
} public System.Threading.WaitHandle AsyncWaitHandle
{
get { return null; }
} public bool CompletedSynchronously
{
get { return false; }
} public bool IsCompleted
{
get { return isCompleted; }
}
}
}
这个类很简单,继承自IAsyncResult接口,主要用来返回异步执行结果的。当异步执行任务完毕后,其他函数可以通过调用CompleteTask方法来抛出任务完成事件,这个抛出的事件将会被EndProcessRequest接住,进而推送实时进度通知。
文件上传Handler设计
说完了异步Handler,再来说一说文件上传Handler:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.IO; namespace AsyncThermometerDeamon.Handlers
{
public class FileUpload : IHttpHandler
{
//设置发送的缓冲大小
private int bufferSize = 5000; public void ProcessRequest(HttpContext context)
{
//得到文件全路径及文件名称
string filePath = context.Request.QueryString["path"];
string fileName = Path.GetFileName(filePath);
byte[] byteToSend; FileStream fs = new FileStream(filePath, FileMode.Open);
fs.Position = 0;
long totalBytesLength = fs.Length; //文件分多少批发送
long totalBatch = 0;
if (totalBytesLength % bufferSize == 0)
totalBatch = totalBytesLength / bufferSize;
else
totalBatch = totalBytesLength / bufferSize + 1; //遍历
for (int i = 0; i < totalBatch; i++)
{
//设置当前需要获取的流的位置
fs.Position = i * bufferSize;
//如果是最后一批流数据
if (i == totalBatch - 1)
{
long lastBatchLength = totalBytesLength = totalBytesLength - i * bufferSize;
byteToSend = new byte[lastBatchLength];
fs.Read(byteToSend, 0, (int)lastBatchLength);
}
else
{
byteToSend = new byte[bufferSize];
fs.Read(byteToSend, 0, bufferSize);
} //避免闭包
int j = i; //写数据
using (FileStream fsWrite = new FileStream(@"C:\" + fileName, FileMode.Append, FileAccess.Write))
{
fsWrite.Write(byteToSend, 0, byteToSend.Length);
fsWrite.Flush();
} //预报当前发送状态
long percentage = (j+1)*100/totalBatch;
//while循环能够保证最后一批数据顺利推送到前台
while (FileUploadWatcher.asyncResults.Count == 0 && percentage == 100)
{ }
if (FileUploadWatcher.asyncResults.Count != 0)
{
FileUploadWatcher.asyncResults[0].percent = percentage;
FileUploadWatcher.asyncResults[0].CompleteTask();
}
}
fs.Close();
} public bool IsReusable
{
get
{
return false;
}
}
}
}
文件上传这块,主要是通过将大文件分割,然后放到一个容积为5000字节的缓冲区中,分段发送,以避免出现OutOfMemory的错误。所以,这种处理机制可以保证发送大数据的文件,比如说1GB大小的文件。在每次写文件的时候,程序会利用long percentage = (j+1)*100/totalBatch;来计算当前的进度,并且通过如下的代码来将当前的进度数据进行推送:
while (FileUploadWatcher.asyncResults.Count == 0 && percentage == 100)
{ }
if (FileUploadWatcher.asyncResults.Count != 0)
{
FileUploadWatcher.asyncResults[0].percent = percentage;
FileUploadWatcher.asyncResults[0].CompleteTask();
}
如果当前有请求,那么就可以调用异步Handler中的CompleteTask来报告本次的进度,CompleteTask将会抛出事件来触发EndProcessRequest,EndProcessRequest则会将当前的进度数据推送至前台。
While循环主要是保证最后一次数据推送能够正常完成,去掉这句,数据一直会显示完成度为99%。
前台设计
最后来看看前台设计吧:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="AsyncThermometerDeamon.Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<title>文件上传</title>
<style type="text/css">
body{font-size:12px;}
#txtPath{border:none;border-bottom:1px solid black;width:300px;}
#fileUpload{border:none;border-bottom:1px solid black;width:250px;background-color:White;height:25px;}
#btnUpload{border:none;background:url(Image/btn_01.gif) no-repeat;width:100px;height:30px;}
#result {width:400px;}
</style>
<link href="Styles/jquery-ui-1.8.16.custom.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="Scripts/jquery-1.6.4.min.js"></script>
<script src="Scripts/jquery-ui-1.8.16.custom.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function () {
$("#btnUpload").bind("click", function () {
StartFileUpload();
TriggerAjax();
});
});
var StartFileUpload = function () {
var filePath = $("#fileUpload").val();
var handerUrl = "Handlers/FileUpload.ashx?path=" + filePath;
$.ajax({
url: handerUrl,
type: "GET",
success: function (result) {},
error: function (result) {}
});
} var TriggerAjax = function () {
var handerUrl = "Handlers/FileUploadWatcher.ashx";
$.ajax({
url: handerUrl,
type: "GET",
success: function (result) {
var data = parseInt(result);
$("#result").progressbar({ value: data });
$("#percent").text(data+"%");
if (result != 100) {
TriggerAjax();
}
},
error: function (result) {
debugger;
alert(result);
}
});
}
</script>
</head>
<body>
<form id="form1" runat="server" >
请输入目标路径:<asp:TextBox ID="txtPath" runat="server" Text="\\192.168.0.180\MatiSoftDaemon\TestUploading" ></asp:TextBox>
<br />
<br />
请选择本地文件:<asp:FileUpload ID="fileUpload" runat="server" />
<input id="btnUpload" type="button" value="上传文件" />
<br />
<div id="result" ></div>
<div id="percent"></div>
</form>
</body>
</html>
源代码