.Net WebRequest 超时与 TCP 超时
.Net WebRequest Timeout versus TCP Timeout
问题
如何为单个 WebRequest 配置 TCP 超时?
上下文
根据文档,WebRequest.Timeout:
The length of time, in milliseconds, until the request times out, or
the value Timeout.Infinite to indicate that the request does not time
out. The default value is defined by the descendant class.
从不存在的端点(IIS 在请求的端口上没有服务绑定)请求 Web 资源失败,超时时间不同且恒定,约为 21 秒。
根据这个 serverfault answer 这似乎是连接 TCP 超时。
示例代码
public static async Task<string> WebRequestToString(string requestUri, int timeoutMilliseconds)
{
var request = WebRequest.Create(requestUri) as HttpWebRequest;
request.KeepAlive = false;
request.Timeout = timeoutMilliseconds;
request.ReadWriteTimeout = timeoutMilliseconds;
using (var response = await request.GetResponseAsync() as HttpWebResponse)
{
// Get the response stream
using (var reader = new StreamReader(response.GetResponseStream()))
{
var responseBody = await reader.ReadToEndAsync();
return responseBody;
}
}
}
static void Main(string[] args)
{
string uri = "http://10.15.1.24:8081/thiservicedoesnotexist/";
int timeoutMilliseconds = 10;
Stopwatch sw = new Stopwatch();
sw.Start();
try
{
WebRequestToString(uri, timeoutMilliseconds).Wait();
}
catch (AggregateException ex)
{
Console.WriteLine(ex.ToString());
}
sw.Stop();
Console.WriteLine("Elaped {0}ms", sw.ElapsedMilliseconds);
Console.ReadKey();
}
示例输出
System.AggregateException: One or more errors occurred. ---> System.Net.WebExcep
tion: Unable to connect to the remote server ---> System.Net.Sockets.SocketExcep
tion: A connection attempt failed because the connected party did not properly r
espond after a period of time, or established connection failed because connecte
d host has failed to respond 10.15.1.24:8081
at System.Net.Sockets.Socket.EndConnect(IAsyncResult asyncResult)
at System.Net.ServicePoint.ConnectSocketInternal(Boolean connectFailure, Sock
et s4, Socket s6, Socket& socket, IPAddress& address, ConnectSocketState state,
IAsyncResult asyncResult, Exception& exception)
--- End of inner exception stack trace ---
at System.Net.HttpWebRequest.EndGetResponse(IAsyncResult asyncResult)
at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar,
Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchron
ization)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNot
ification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at TimeoutTests.Program.<WebRequestToString>d__0.MoveNext() in \vmware-host\
shared folders\Documents\Visual Studio 2012\Projects\TimeoutTests\TimeoutTests\P
rogram.cs:line 20
--- End of inner exception stack trace ---
at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceled
Exceptions)
at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationTo
ken cancellationToken)
at System.Threading.Tasks.Task.Wait()
at TimeoutTests.Program.Main(String[] args) in \vmware-host\shared folders\D
ocuments\Visual Studio 2012\Projects\TimeoutTests\TimeoutTests\Program.cs:line 4
0
---> (Inner Exception #0) System.Net.WebException: Unable to connect to the remo
te server ---> System.Net.Sockets.SocketException: A connection attempt failed b
ecause the connected party did not properly respond after a period of time, or e
stablished connection failed because connected host has failed to respond 10.15.
1.24:8081
at System.Net.Sockets.Socket.EndConnect(IAsyncResult asyncResult)
at System.Net.ServicePoint.ConnectSocketInternal(Boolean connectFailure, Sock
et s4, Socket s6, Socket& socket, IPAddress& address, ConnectSocketState state,
IAsyncResult asyncResult, Exception& exception)
--- End of inner exception stack trace ---
at System.Net.HttpWebRequest.EndGetResponse(IAsyncResult asyncResult)
at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar,
Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchron
ization)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNot
ification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at TimeoutTests.Program.<WebRequestToString>d__0.MoveNext() in \vmware-host\
shared folders\Documents\Visual Studio 2012\Projects\TimeoutTests\TimeoutTests\P
rogram.cs:line 20<---
Elaped 21169ms
我很怀疑这个超时实际上和TCP连接超时是一样的。
webrequest超时的文档如下:
The length of time, in milliseconds, until the request times out, or the value Timeout.Infinite to indicate that the request does not time out. The default value is defined by the descendant class.
这是您请求级别的超时,即 HTTP。可能是与服务器的连接已建立,但创建响应需要很长时间。还有那个时间可以超时。
webrequest 如何处理这个并将其映射到 TCP 取决于实现。
建立连接的 TCP 超时不能是无限的。
我还阅读了以下内容:
The Timeout property indicates the length of time, in milliseconds, until the request times out and throws a WebException. The Timeout property affects only synchronous requests made with the GetResponse method. To time out asynchronous requests, use the Abort method.
你是异步的!
我最终通过创建一个基于 approach 的 GetResponseAsync 方法扩展来解决这个问题并且似乎工作正常:
public static class WebRequestExtensions
{
public static async Task<WebResponse> GetResponseAsyncWithTimeout(this WebRequest request)
{
var timeoutCancellationTokenSource = new CancellationTokenSource();
var responseTask = request.GetResponseAsync();
var completedTask = await Task.WhenAny(responseTask, Task.Delay(request.Timeout, timeoutCancellationTokenSource.Token));
if (completedTask == responseTask)
{
timeoutCancellationTokenSource.Cancel();
return await responseTask;
}
else
{
request.Abort();
throw new TimeoutException("The operation has timed out.");
}
}
}
问题
如何为单个 WebRequest 配置 TCP 超时?
上下文
根据文档,WebRequest.Timeout:
The length of time, in milliseconds, until the request times out, or the value Timeout.Infinite to indicate that the request does not time out. The default value is defined by the descendant class.
从不存在的端点(IIS 在请求的端口上没有服务绑定)请求 Web 资源失败,超时时间不同且恒定,约为 21 秒。
根据这个 serverfault answer 这似乎是连接 TCP 超时。
示例代码
public static async Task<string> WebRequestToString(string requestUri, int timeoutMilliseconds)
{
var request = WebRequest.Create(requestUri) as HttpWebRequest;
request.KeepAlive = false;
request.Timeout = timeoutMilliseconds;
request.ReadWriteTimeout = timeoutMilliseconds;
using (var response = await request.GetResponseAsync() as HttpWebResponse)
{
// Get the response stream
using (var reader = new StreamReader(response.GetResponseStream()))
{
var responseBody = await reader.ReadToEndAsync();
return responseBody;
}
}
}
static void Main(string[] args)
{
string uri = "http://10.15.1.24:8081/thiservicedoesnotexist/";
int timeoutMilliseconds = 10;
Stopwatch sw = new Stopwatch();
sw.Start();
try
{
WebRequestToString(uri, timeoutMilliseconds).Wait();
}
catch (AggregateException ex)
{
Console.WriteLine(ex.ToString());
}
sw.Stop();
Console.WriteLine("Elaped {0}ms", sw.ElapsedMilliseconds);
Console.ReadKey();
}
示例输出
System.AggregateException: One or more errors occurred. ---> System.Net.WebExcep
tion: Unable to connect to the remote server ---> System.Net.Sockets.SocketExcep
tion: A connection attempt failed because the connected party did not properly r
espond after a period of time, or established connection failed because connecte
d host has failed to respond 10.15.1.24:8081
at System.Net.Sockets.Socket.EndConnect(IAsyncResult asyncResult)
at System.Net.ServicePoint.ConnectSocketInternal(Boolean connectFailure, Sock
et s4, Socket s6, Socket& socket, IPAddress& address, ConnectSocketState state,
IAsyncResult asyncResult, Exception& exception)
--- End of inner exception stack trace ---
at System.Net.HttpWebRequest.EndGetResponse(IAsyncResult asyncResult)
at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar,
Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchron
ization)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNot
ification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at TimeoutTests.Program.<WebRequestToString>d__0.MoveNext() in \vmware-host\
shared folders\Documents\Visual Studio 2012\Projects\TimeoutTests\TimeoutTests\P
rogram.cs:line 20
--- End of inner exception stack trace ---
at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceled
Exceptions)
at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationTo
ken cancellationToken)
at System.Threading.Tasks.Task.Wait()
at TimeoutTests.Program.Main(String[] args) in \vmware-host\shared folders\D
ocuments\Visual Studio 2012\Projects\TimeoutTests\TimeoutTests\Program.cs:line 4
0
---> (Inner Exception #0) System.Net.WebException: Unable to connect to the remo
te server ---> System.Net.Sockets.SocketException: A connection attempt failed b
ecause the connected party did not properly respond after a period of time, or e
stablished connection failed because connected host has failed to respond 10.15.
1.24:8081
at System.Net.Sockets.Socket.EndConnect(IAsyncResult asyncResult)
at System.Net.ServicePoint.ConnectSocketInternal(Boolean connectFailure, Sock
et s4, Socket s6, Socket& socket, IPAddress& address, ConnectSocketState state,
IAsyncResult asyncResult, Exception& exception)
--- End of inner exception stack trace ---
at System.Net.HttpWebRequest.EndGetResponse(IAsyncResult asyncResult)
at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar,
Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchron
ization)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNot
ification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at TimeoutTests.Program.<WebRequestToString>d__0.MoveNext() in \vmware-host\
shared folders\Documents\Visual Studio 2012\Projects\TimeoutTests\TimeoutTests\P
rogram.cs:line 20<---
Elaped 21169ms
我很怀疑这个超时实际上和TCP连接超时是一样的。 webrequest超时的文档如下:
The length of time, in milliseconds, until the request times out, or the value Timeout.Infinite to indicate that the request does not time out. The default value is defined by the descendant class.
这是您请求级别的超时,即 HTTP。可能是与服务器的连接已建立,但创建响应需要很长时间。还有那个时间可以超时。
webrequest 如何处理这个并将其映射到 TCP 取决于实现。 建立连接的 TCP 超时不能是无限的。
我还阅读了以下内容:
The Timeout property indicates the length of time, in milliseconds, until the request times out and throws a WebException. The Timeout property affects only synchronous requests made with the GetResponse method. To time out asynchronous requests, use the Abort method.
你是异步的!
我最终通过创建一个基于 approach 的 GetResponseAsync 方法扩展来解决这个问题并且似乎工作正常:
public static class WebRequestExtensions
{
public static async Task<WebResponse> GetResponseAsyncWithTimeout(this WebRequest request)
{
var timeoutCancellationTokenSource = new CancellationTokenSource();
var responseTask = request.GetResponseAsync();
var completedTask = await Task.WhenAny(responseTask, Task.Delay(request.Timeout, timeoutCancellationTokenSource.Token));
if (completedTask == responseTask)
{
timeoutCancellationTokenSource.Cancel();
return await responseTask;
}
else
{
request.Abort();
throw new TimeoutException("The operation has timed out.");
}
}
}