为了把事情放到上下文中,想象一个程序使用FtpWebRequest.BeginGetResponse()从FTP服务器检索文件的大小。如果由于某些连接错误而无法获得文件大小,则会在一段时间后再次尝试。它还允许用户取消整个操作。
重试特定次数后出现问题:如果FtpWebRequest.BeginGetResponse()在同一端点上连接的次数超过ServicePointManager.DefaultConnectionLimit,则后续回调不会被调用,等待句柄将无限期阻止。
使用TCPView,可能会发现在第一次ServicePointManager.DefaultConnectionLimit(失败)连接尝试后没有尝试新连接。
让我用一些人为的例子来证明这个问题。
假设,
同步版
static void Main(string[] args){
var uri = new Uri("ftp://192.168.1.2/test.txt");
for (var z = 0; z < ServicePointManager.DefaultConnectionLimit + 1; z += 1){
try {
var request = (FtpWebRequest) WebRequest.Create(uri);
request.Method = WebRequestMethods.Ftp.GetFileSize;
using (var response = request.GetResponse())
Console.WriteLine(response.ContentLength);
} catch (WebException ex){
Console.WriteLine(ex.Status);
}
}
}
以控制台输出终止,
ConnectFailure
ConnectFailure
ConnectFailure
而,
static void Main(string[] args){
var uri = new Uri("ftp://192.168.1.2/test.txt");
for (var z = 0; z < ServicePointManager.DefaultConnectionLimit + 1; z += 1){
var request = (FtpWebRequest) WebRequest.Create(uri);
request.Method = WebRequestMethods.Ftp.GetFileSize;
request.BeginGetResponse(PrintFileSizeCallback, request);
}
Console.ReadKey(false);
}
static void PrintFileSizeCallback(IAsyncResult ar){
try {
using (var response = ((FtpWebRequest) ar.AsyncState).EndGetResponse(ar))
Console.WriteLine(response.ContentLength);
} catch (WebException ex){
Console.WriteLine(ex.Status);
}
}
将打印,
ConnectFailure
ConnectFailure
并且永远不会发生第三次回调。
即使等待异步方法完成也没有区别:
static void Main(string[] args){
var uri = new Uri("ftp://192.168.1.2/test.txt");
for (var z = 0; z < ServicePointManager.DefaultConnectionLimit + 1; z += 1){
try {
var request = (FtpWebRequest) WebRequest.Create(uri);
request.Method = WebRequestMethods.Ftp.GetFileSize;
var responseAsyncResult = request.BeginGetResponse(null, null);
using (var response = request.EndGetResponse(responseAsyncResult))
Console.WriteLine(response.ContentLength);
} catch (WebException ex){
Console.WriteLine(ex.Status);
}
}
}
将输出,
ConnectFailure
ConnectFailure
并挂起。
如果等待FtpWebRequest.GetResponseAsync(),则会发现相同的行为。
现在有趣的是:如果192.168.1.2 运行前面任何一个示例的机器的IP地址,或者192.168.1.2被环回地址替换,问题就会消失。
好的,让我们对前面的例子进行一些小修改:
static void Main(string[] args){
var uri = new Uri("ftp://192.168.1.2/test.txt");
for (var z = 0; z < ServicePointManager.DefaultConnectionLimit + 1; z += 1){
try {
var request = WebRequest.Create(uri);
var responseAsyncResult = request.BeginGetResponse(null, null);
using (var response = request.EndGetResponse(responseAsyncResult))
Console.WriteLine(response.ContentLength);
} catch (WebException ex){
Console.WriteLine(ex.Status);
}
}
}
同样,它会像其他人一样失败,除非地址是本地/环回。
但是如果将uri方案改为http,那么一切都很好......
那么,这是一个错误还是我做错了什么?