上传HTTP进度跟踪
本文关键字:跟踪 HTTP 上传 | 更新日期: 2023-09-27 18:15:00
我有WPF应用程序,我正在写的帖子文件到一个社交网络。上传本身工作得很好,但是我想提供一些关于我上传的进度的指示。
我尝试了很多方法来做到这一点:
1) HttpWebRequest。GetStream方法:
using (
var FS = File.Open(
localFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
long len = FS.Length;
HttpWebRequest request = (HttpWebRequest) WebRequest.Create(url);
request.Method = "POST";
request.ProtocolVersion = HttpVersion.Version11;
request.ContentType = "multipart/form-data; boundary=--AaB03x";
//predata and postdata is two byte[] arrays, that contains
//strings for MIME file upload (defined above and is not important)
request.ContentLength = predata.Length + FS.Length + postdata.Length;
request.AllowWriteStreamBuffering = false;
using (var reqStream = request.GetRequestStream())
{
reqStream.Write(predata, 0, predata.Length);
int bytesRead = 0;
int totalRead = 0;
do
{
bytesRead = FS.Read(fileData, 0, MaxContentSize);
totalRead += bytesRead;
reqStream.Write(fileData, 0, bytesRead);
reqStream.Flush(); //trying with and without this
//this part will show progress in percents
sop.prct = (int) ((100*totalRead)/len);
} while (bytesRead > 0);
reqStream.Write(postdata, 0, postdata.Length);
}
HttpWebResponse responce = (HttpWebResponse) request.GetResponse();
using (var respStream = responce.GetResponseStream())
{
//do things
}
}
2) WebClient方式(更短):
void UploadFile (url, localFilePath)
{
...
WebClient client = new WebClient();
client.UploadProgressChanged += new UploadProgressChangedEventHandler(UploadPartDone);
client.UploadFileCompleted += new UploadFileCompletedEventHandler(UploadComplete);
client.UploadFileAsync(new Uri(url), localFilePath);
done.WaitOne();
//do things with responce, received from UploadComplete
JavaScriptSerializer jssSer = new JavaScriptSerializer();
return jssSer.Deserialize<UniversalJSONAnswer>(utf8.GetString(UploadFileResponce));
//so on...
...
}
void UploadComplete(object sender, UploadFileCompletedEventArgs e)
{
UploadFileResponce=e.Result;
done.Set();
}
void UploadPartDone(object sender, UploadProgressChangedEventArgs e)
{
//this part expected to show progress
sop.prct=(int)(100*e.BytesSent/e.TotalBytesToSend);
}
3) Even TcpClient way:
using (
var FS = File.Open(
localFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
long len = FS.Length;
long totalRead = 0;
using (var client = new TcpClient(urli.Host, urli.Port))
{
using (var clearstream = client.GetStream())
{
using (var writer = new StreamWriter(clearstream))
using (var reader = new StreamReader(clearstream))
{
//set progress to 0
sop.prct = 0;
// Send request headers
writer.WriteLine("POST " + urli.AbsoluteUri + " HTTP/1.1");
writer.WriteLine("Content-Type: multipart/form-data; boundary=--AaB03x");
writer.WriteLine("Host: " + urli.Host);
writer.WriteLine("Content-Length: " + (predata.Length + len + postdata.Length).ToString());
writer.WriteLine();
//some data for MIME
writer.Write(utf8.GetString(predata));
writer.Flush();
int bytesRead;
do
{
bytesRead = FS.Read(fileData, 0, MaxContentSize);
totalRead += bytesRead;
writer.BaseStream.Write(fileData, 0, bytesRead);
writer.BaseStream.Flush();
sop.prct = (int) ((100*totalRead)/len);
} while (bytesRead > 0)
writer.Write(utf8.GetString(postdata));
writer.Flush();
//read line of response and do other thigs...
respStr = reader.ReadLine();
...
}
}
}
}
在所有情况下,文件都成功地发送到服务器。但是进度总是这样的:在几秒钟内从0运行到100,然后等待直到文件真正上传(大约5分钟-文件是400MB)。
所以我认为来自文件的数据被缓冲在某个地方,我跟踪的不是上传,而是缓冲数据。然后必须等待,直到它被上传。
我的问题是:
1)是否有办法跟踪实际的上传数据?方法Stream.Write()或Flush()(正如我在某处读到的,对NetworkStream不起作用)直到从服务器接收到TCP数据包收到的确认才返回。
2)或者我可以拒绝缓冲(AllowWriteStreamBUffering为HttpWebRequest不工作)?
3)进一步"向下"并尝试使用套接字是否有意义?
更新:
为了避免在UI上显示进度的方式上有任何疑问,我重写了代码来记录文件。下面是代码:
using (var LogStream=File.Open("C:''123.txt",FileMode.Create,FileAccess.Write,FileShare.Read))
using (var LogWriter=new StreamWriter(LogStream))
using (var FS = File.Open(localFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
long len = FS.Length;
HttpWebRequest request = (HttpWebRequest) WebRequest.Create(url);
request.Timeout = 7200000; //2 hour timeout
request.Method = "POST";
request.ProtocolVersion = HttpVersion.Version11;
request.ContentType = "multipart/form-data; boundary=--AaB03x";
//predata and postdata is two byte[] arrays, that contains
//strings for MIME file upload (defined above and is not important)
request.ContentLength = predata.Length + FS.Length + postdata.Length;
request.AllowWriteStreamBuffering = false;
LogWriter.WriteLine(DateTime.Now.ToString("o") + " Start write into request stream. ");
using (var reqStream = request.GetRequestStream())
{
reqStream.Write(predata, 0, predata.Length);
int bytesRead = 0;
int totalRead = 0;
do
{
bytesRead = FS.Read(fileData, 0, MaxContentSize);
totalRead += bytesRead;
reqStream.Write(fileData, 0, bytesRead);
reqStream.Flush(); //trying with and without this
//sop.prct = (int) ((100*totalRead)/len); //this part will show progress in percents
LogWriter.WriteLine(DateTime.Now.ToString("o") + " totalRead= " + totalRead.ToString() + " / " + len.ToString());
} while (bytesRead > 0);
reqStream.Write(postdata, 0, postdata.Length);
}
LogWriter.WriteLine(DateTime.Now.ToString("o") + " All sent!!! Waiting for responce... ");
LogWriter.Flush();
HttpWebResponse responce = (HttpWebResponse) request.GetResponse();
LogWriter.WriteLine(DateTime.Now.ToString("o") + " Responce received! ");
using (var respStream = responce.GetResponseStream())
{
if (respStream == null) return null;
using (var streamReader = new StreamReader(respStream))
{
string resp = streamReader.ReadToEnd();
JavaScriptSerializer jssSer = new JavaScriptSerializer();
return jssSer.Deserialize<UniversalJSONAnswer>(resp);
}
}
}
和这里是结果(我剪中间):
2011-11-19T22:00:54.5964408+04:00 Start write into request stream.
2011-11-19T22:00:54.6404433+04:00 totalRead= 1048576 / 410746880
2011-11-19T22:00:54.6424434+04:00 totalRead= 2097152 / 410746880
2011-11-19T22:00:54.6434435+04:00 totalRead= 3145728 / 410746880
2011-11-19T22:00:54.6454436+04:00 totalRead= 4194304 / 410746880
2011-11-19T22:00:54.6464437+04:00 totalRead= 5242880 / 410746880
2011-11-19T22:00:54.6494438+04:00 totalRead= 6291456 / 410746880
.......
2011-11-19T22:00:55.3434835+04:00 totalRead= 408944640 / 410746880
2011-11-19T22:00:55.3434835+04:00 totalRead= 409993216 / 410746880
2011-11-19T22:00:55.3464837+04:00 totalRead= 410746880 / 410746880
2011-11-19T22:00:55.3464837+04:00 totalRead= 410746880 / 410746880
2011-11-19T22:00:55.3464837+04:00 All sent!!! Waiting for responce...
2011-11-19T22:07:23.0616597+04:00 Responce received!
你可以看到程序认为它上传了~400MB大约2秒。7分钟后,文件实际上传,我收到响应。
再次更新:
似乎这发生在WIndows 7下(不包括x64或x86)。当我在XP下运行我的代码时,一切都工作得很好,进度显示得绝对正确
自从这个问题发布以来已经一年多了,但我认为我的帖子可能对某些人有用。
我在显示进度时遇到了同样的问题,它的行为完全像你描述的那样。所以我决定使用HttpClient正确显示上传进度。然后我遇到了有趣的bug -当我有Fiddler启动HttpClient开始以意想不到的方式显示其上传进度,就像在WebClient/HttpWebRequest上面,所以我想也许这是一个问题,为什么WebClient显示上传进度不正确(我想我已经启动了)。所以我再次尝试了WebClient(没有启动类似小提琴的应用程序),所有的工作都应该,上传进度有正确的值。我在几台安装了win7和XP的PC上进行了测试,在所有情况下,进度都显示正确。
所以,我认为像Fiddler这样的程序(可能不仅仅是一个Fiddler)对WebClient和其他。net类如何显示上传进度有一些影响。
this discussion approve it:
HttpWebRequest不工作,除非fiddler运行
你可以使用WebClient
's UploadFile
上传文件,而不是使用写入文件作为文件流。为了跟踪接收和上传的数据的百分比,您可以使用UploadFileAsyn
并订阅其事件。
在下面的代码中,我使用了UploadFileAsyn来同步上传文件,但它不需要是同步的,只要你不处理上传程序的实例。
class FileUploader : IDisposable
{
private readonly WebClient _client;
private readonly Uri _address;
private readonly string _filePath;
private bool _uploadCompleted;
private bool _uploadStarted;
private bool _status;
public FileUploader(string address, string filePath)
{
_client = new WebClient();
_address = new Uri(address);
_filePath = filePath;
_client.UploadProgressChanged += FileUploadProgressChanged;
_client.UploadFileCompleted += FileUploadFileCompleted;
}
private void FileUploadFileCompleted(object sender, UploadFileCompletedEventArgs e)
{
_status = (e.Cancelled || e.Error == null) ? false : true;
_uploadCompleted = true;
}
private void FileUploadProgressChanged(object sender, UploadProgressChangedEventArgs e)
{
if(e.ProgressPercentage % 10 == 0)
{
//This writes the pecentage data uploaded and downloaded
Console.WriteLine("Send: {0}, Received: {1}", e.BytesSent, e.BytesReceived);
//You can have a delegate or a call back to update your UI about the percentage uploaded
//If you don't have the condition (i.e e.ProgressPercentage % 10 == 0 )for the pecentage of the process
//the callback will slow you upload process down
}
}
public bool Upload()
{
if (!_uploadStarted)
{
_uploadStarted = true;
_client.UploadFileAsync(_address, _filePath);
}
while (!_uploadCompleted)
{
Thread.Sleep(1000);
}
return _status;
}
public void Dispose()
{
_client.Dispose();
}
}
客户机代码: using (FileUploader uploader = new FileUploader("http://www.google.com", @"C:'test.txt"))
{
uploader.Upload();
}
你可以在FileUploadProgressChanged
事件处理程序上注册一个自定义回调(可能是一个委托)来更新你的WPF UI。
如果你对事件的回调做了任何IO,那么上传进度改变的事件会被更频繁地调用,这将减慢下载进度。最好是不频繁的更新,例如下面的代码每10%更新一次。
private int _percentageDownloaded;
private void FileUploadProgressChanged(object sender, UploadProgressChangedEventArgs e)
{
if (e.ProgressPercentage % 10 == 0 && e.ProgressPercentage > _percentageDownloaded)
{
_percentageDownloaded = e.ProgressPercentage;
//Any callback instead of printline
Console.WriteLine("Send: {0} Received: {1}", e.BytesSent, e.BytesReceived);
}
}
我的建议是使用新的HTTPClient类(在。net 4.5中可用)。它支持进度。
这篇文章对我帮助很大:http://www.strathweb.com/2012/06/drag-and-drop-files-to-wpf-application-and-asynchronously-upload-to-asp-net-web-api/
上传文件的代码:
private void HttpSendProgress(object sender, HttpProgressEventArgs e)
{
HttpRequestMessage request = sender as HttpRequestMessage;
Console.WriteLine(e.BytesTransferred);
}
private void Window_Loaded_1(object sender, RoutedEventArgs e)
{
ProgressMessageHandler progress = new ProgressMessageHandler();
progress.HttpSendProgress += new EventHandler<HttpProgressEventArgs>(HttpSendProgress);
HttpRequestMessage message = new HttpRequestMessage();
StreamContent streamContent = new StreamContent(new FileStream("e:''somefile.zip", FileMode.Open));
message.Method = HttpMethod.Put;
message.Content = streamContent;
message.RequestUri = new Uri("{Here your link}");
var client = HttpClientFactory.Create(progress);
client.SendAsync(message).ContinueWith(task =>
{
if (task.Result.IsSuccessStatusCode)
{
}
});
}
这个问题已经困扰我至少一天了。我开始使用WebClient.UploadFileAsync
,接下来尝试HttpClient
的ProgressMessageHandler
,然后为HttpClient
API滚动我自己的HttpContent
。这些方法(对我来说)都不起作用。
它出现了HttpWebRequest
,它位于大多数(所有?). net Http抽象的底部,如WebClient
和HttpClient
,默认情况下缓冲请求和响应流,我通过在ILSpy中查看它来确认。
正如其他人注意到的,你可以让你的请求使用一种或另一种方式的分块编码,这将有效地禁用请求流的缓冲,但这仍然不会修复进度报告。
我发现有必要在我发送的每个块之后刷新请求流,以便准确地反映发送进度,否则您的数据将简单地在管道中进一步缓冲一步(可能在NetworkStream或OS中的某个地方,没有检查)。下面的示例代码为我工作,也做了一个极简的工作,从HttpWebResponse转换回HttpResponseMessage(你可能不需要,YMMV)。
public async Task<HttpResponseMessage> UploadFileAsync( string uploadUrl, string absoluteFilePath, Action<int> progressPercentCallback )
{
var length = new FileInfo( absoluteFilePath ).Length;
var request = new HttpWebRequest( new Uri(uploadUrl) ) {
Method = "PUT",
AllowWriteStreamBuffering = false,
AllowReadStreamBuffering = false,
ContentLength = length
};
const int chunkSize = 4096;
var buffer = new byte[chunkSize];
using (var req = await request.GetRequestStreamAsync())
using (var readStream = File.OpenRead(absoluteFilePath))
{
progressPercentCallback(0);
int read = 0;
for (int i = 0; i < length; i += read)
{
read = await readStream.ReadAsync( buffer, 0, chunkSize );
await req.WriteAsync( buffer, 0, read );
await req.FlushAsync(); // flushing is required or else we jump to 100% very fast
progressPercentCallback((int)(100.0 * i / length));
}
progressPercentCallback(100);
}
var response = (HttpWebResponse)await request.GetResponseAsync();
var result = new HttpResponseMessage( response.StatusCode );
result.Content = new StreamContent( response.GetResponseStream() );
return result;
}
快速猜测,您正在UI线程上运行此代码。你需要在新线程上运行上传内容。这时你有两个选择。1)在UI线程上运行计时器并更新UI。2)你使用Invoke(因为你不能从另一个线程访问UI)调用来更新UI。
在第一个例子中,我认为你的进度条显示的是你从磁盘上的文件写入流的速度有多快,而不是实际的上传进度(这就是为什么它发生的100%真的很快,然后上传chugs *)。
我可能错了^^,没有WPF经验,但我已经从Silverlight上传了大量文件到WCF,并且那里使用的模型(如您所做的)将文件分解成块。发送每个区块。当你从服务器得到一个响应("block 26 received ok"),更新进度条,因为你不能(或不应该)更新进度条,除非你/知道/块x做了-一个很好的方法知道,如果服务器说它得到了它。
*我希望我能在5分钟内上传400Mb。会花我一整天的时间……
我也有同样的问题。我花了很多时间,解决了如下问题:防病毒停住。当我把它关掉的时候,我的程序运行得很好。