我已经编写了一个应用程序,部分可以从特定的Web服务下载文件。该代码使用HttpClient进行调用。问题是我偶尔会收到一条失败的请求,并带有以下异常消息:
无法从传输连接读取数据:连接已关闭。
我确实遇到过这些博客帖子,其中作者必须将协议版本恢复为1.0,禁用保持活动状态,并限制服务点连接的数量:
我遵循了这些指示,因为我知道如何并仍然得到错误。我还确保保留一个HttpClient实例(遵循Singleton原则)。
有趣的是,在运行Fiddler时我还没有得到错误,这让我觉得有一些事情可以在客户端完成,因为Fiddler似乎在做一些事情以保持连接活着(虽然这个问题太零散了,但这可能是一个红鲱鱼)。
还有几点说明:
错误总是发生在下载过程中(从未发起请求时)。
文件继续下载到失败点(首先没有延长的暂停或延迟)。
- UPDATE -
错误发生在以下行:
responseTask.Wait(cancellationTokenSource.Token);
以下是完整的例外情况:
System.AggregateException occurred HResult=-2146233088 Message=One
or more errors occurred. Source=mscorlib StackTrace:
at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
at Form1.StartDownload() in c:\Projects\Visual Studio 2012\Demo\Demo\Form1.cs:line 88 InnerException:
System.Net.Http.HttpRequestException
HResult=-2146233088
Message=Error while copying content to a stream.
InnerException: System.IO.IOException
HResult=-2146232800
Message=Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host.
Source=System
StackTrace:
at System.Net.ConnectStream.EndRead(IAsyncResult asyncResult)
at System.Net.Http.HttpClientHandler.WebExceptionWrapperStream.EndRead(IAsyncResult
asyncResult)
at System.Net.Http.Handlers.ProgressStream.EndRead(IAsyncResult
asyncResult)
at System.Net.Http.StreamToStreamCopy.BufferReadCallback(IAsyncResult ar)
InnerException: System.Net.Sockets.SocketException
HResult=-2147467259
Message=An existing connection was forcibly closed by the remote host
Source=System
ErrorCode=10054
NativeErrorCode=10054
StackTrace:
at System.Net.Sockets.NetworkStream.EndRead(IAsyncResult asyncResult)
InnerException:
- 更新#2 -
我想我会尝试更改内容阅读中的完成选项'阅读标题'虽然在不同的位置(TODO评论的位置,读取内容流),但同样的例外情况也失败了。
- 更新#3 -
我可以确认Web服务(在IIS中托管)正在中止连接(IIS日志显示win32状态代码为1236 - ERROR_CONNECTION_ABORTED)。为了尝试缩小范围,MinFileBytesPerSec配置数据库属性设置为零(在客户端暂时停止下拉数据的情况下),连接仍然被中止。我已经仔细检查了所有超时和缓冲区大小,我认为无济于事。此刻抓住空气稀薄。任何想法都将不胜感激。
客户端设置:
private void SetupClient()
{
// In case we're taxing the web server, limit the number
// connections we're allowed to make to one.
ServicePointManager.DefaultConnectionLimit = 1;
// Set up the progress handler so that we can keep track of the download progress.
_progressHandler = new ProgressMessageHandler();
_progressHandler.HttpReceiveProgress += ProgressHandler_HttpReceiveProgress;
// Create our HttpClient.
_client = HttpClientFactory.Create(_progressHandler);
_client.BaseAddress = new Uri("http://localhost");
_client.Timeout = TimeSpan.FromMinutes(30);
_client.DefaultRequestHeaders.TransferEncodingChunked = true;
}
下载逻辑:
private void StartDownload()
{
// Create the request.
using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Download"))
{
// Revert the protocol version and turn off keep alive in accordance with:
// http://briancaos.wordpress.com/2012/07/06/unable-to-read-data-from-the-transport-connection-the-connection-was-closed/
// http://briancaos.wordpress.com/2012/06/15/an-existing-connection-was-forcibly-closed-by-the-remote-host/
request.Version = new Version("1.0");
request.Headers.Add("Keep-Alive", "false");
// Set the cancellation token's timeout to 30 minutes.
int timeoutInMilliseconds = 30 * 60 * 1000;
using (CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(timeoutInMilliseconds))
{
// Making sure that the message isn't "complete" until everything is read in so we can cancel it at anytime.
Task<HttpResponseMessage> responseTask = _client.SendAsync(request, HttpCompletionOption.ResponseContentRead);
responseTask.Wait(cancellationTokenSource.Token);
using (HttpResponseMessage response = responseTask.Result)
{
if (!response.IsSuccessStatusCode)
{
throw new Exception("Request failed!");
}
Task<Stream> streamTask = response.Content.ReadAsStreamAsync();
using (Stream contentStream = streamTask.Result)
{
// TODO: Save to disk.
}
}
}
}
}