我想从服务器上的一个可用IP地址发出Web请求,所以我使用这个类:
public class UseIP
{
public string IP { get; private set; }
public UseIP(string IP)
{
this.IP = IP;
}
public HttpWebRequest CreateWebRequest(Uri uri)
{
ServicePoint servicePoint = ServicePointManager.FindServicePoint(uri);
servicePoint.BindIPEndPointDelegate = new BindIPEndPoint(Bind);
return WebRequest.Create(uri) as HttpWebRequest;
}
private IPEndPoint Bind(ServicePoint servicePoint, IPEndPoint remoteEndPoint, int retryCount)
{
IPAddress address = IPAddress.Parse(this.IP);
return new IPEndPoint(address, 0);
}
}
然后:
UseIP useIP = new UseIP("Valid IP address here...");
Uri uri = new Uri("http://ip.nefsc.noaa.gov");
HttpWebRequest request = useIP.CreateWebRequest(uri);
// Then make the request with the specified IP address
但是解决方案第一次正常运作!
答案 0 :(得分:15)
理论:
HttpWebRequest依赖于底层的ServicePoint。 ServicePoint表示与URL的实际连接。与您的浏览器保持连接到请求之间的URL并重用该连接(以消除打开和关闭每个请求的连接的开销)的方式大致相同,ServicePoint为HttpWebRequest执行相同的功能。
我认为在每次使用HttpWebRequest时都没有调用为ServicePoint设置的BindIPEndPointDelegate,因为ServicePoint正在重用连接。如果您可以强制关闭连接,则下一次调用该URL会导致ServicePoint需要再次调用BindIPEndPointDelegate。
不幸的是,ServicePoint界面似乎没有让您能够直接强制关闭连接。
两种解决方案(每种解决方案略有不同)
1)对于每个请求,设置HttpWebRequest.KeepAlive = false。在我的测试中,这导致Bind委托与每个请求一对一调用。
2)将ServicePoint ConnectionLeaseTimeout属性设置为零或一些小值。这会产生定期强制调用绑定委托的效果(不是每个请求一对一)。
您可以使用此属性来确保ServicePoint对象 活动连接不会无限期保持打开状态。这个属性是 适用于应该删除连接的场景 定期重新建立,例如负载平衡方案。
默认情况下,当KeepAlive为请求时为true时,MaxIdleTime property设置关闭ServicePoint连接的超时时间 闲置。如果ServicePoint具有活动连接,则为MaxIdleTime 没有效果,连接无限期地保持打开状态。
当ConnectionLeaseTimeout属性设置为除以外的值时 -1,并且在指定的时间过去之后,通过将KeepAlive设置为for来处理请求后,将关闭活动的ServicePoint连接 在那个请求中是假的。
设置此值会影响ServicePoint对象管理的所有连接。
public class UseIP
{
public string IP { get; private set; }
public UseIP(string IP)
{
this.IP = IP;
}
public HttpWebRequest CreateWebRequest(Uri uri)
{
ServicePoint servicePoint = ServicePointManager.FindServicePoint(uri);
servicePoint.BindIPEndPointDelegate = (servicePoint, remoteEndPoint, retryCount) =>
{
IPAddress address = IPAddress.Parse(this.IP);
return new IPEndPoint(address, 0);
};
//Will cause bind to be called periodically
servicePoint.ConnectionLeaseTimeout = 0;
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(uri);
//will cause bind to be called for each request (as long as the consumer of the request doesn't set it back to true!
req.KeepAlive = false;
return req;
}
}
以下(基本)测试结果会为每个请求调用绑定委托:
static void Main(string[] args)
{
//Note, I don't have a multihomed machine, so I'm not using the IP in my test implementation. The bind delegate increments a counter and returns IPAddress.Any.
UseIP ip = new UseIP("111.111.111.111");
for (int i = 0; i < 100; ++i)
{
HttpWebRequest req = ip.CreateWebRequest(new Uri("http://www.yahoo.com"));
using (WebResponse response = req.GetResponse())
{
}
}
Console.WriteLine(string.Format("Req: {0}", UseIP.RequestCount));
Console.WriteLine(string.Format("Bind: {0}", UseIP.BindCount));
}
答案 1 :(得分:1)
问题可能是代表在每个新请求上重置。请尝试以下:
//servicePoint.BindIPEndPointDelegate = null; // Clears all delegates first, for testing
servicePoint.BindIPEndPointDelegate += delegate
{
var address = IPAddress.Parse(this.IP);
return new IPEndPoint(address, 0);
};
另据我所知,端点是缓存的,因此即使清除代理也可能在某些情况下无效,无论如何都可以重置。您可以卸载/重新加载应用程序域,这是最糟糕的情况。
答案 2 :(得分:0)
我稍微改变了你的例子,让它在我的机器上运行:
public HttpWebRequest CreateWebRequest(Uri uri)
{
HttpWebRequest wr = WebRequest.Create(uri) as HttpWebRequest;
wr.ServicePoint.BindIPEndPointDelegate = new BindIPEndPoint(Bind);
return wr;
}
我这样做是因为:
FindServicePoint
的调用实际上使用“默认”ip执行请求,甚至没有调用绑定委托给您指定的URI。在我的机器中,至少,BindIPEndPointDelegate
没有以您呈现的方式调用(我知道请求是因为我没有设置代理并且遇到代理身份验证错误); ServicePointManager
之前的某些“缓存”。答案 3 :(得分:0)
我喜欢这个新课 UseIP 。
Specify the outgoing IP Address to use with WCF client有一点关于保护自己免受IPv4 / IPv6差异的影响。
唯一需要改变的是Bind方法:
private IPEndPoint Bind(ServicePoint servicePoint, IPEndPoint remoteEndPoint, int retryCount)
{
if ((null != IP) && (IP.AddressFamily == remoteEndPoint.AddressFamily))
return new IPEndPoint(this.IP, 0);
if (AddressFamily.InterNetworkV6 == remoteEndPoint.AddressFamily)
return new IPEndPoint(IPAddress.IPv6Any, 0);
return new IPEndPoint(IPAddress.Any, 0);
}
重新:多次调用Bind方法。
对我来说有用的是在添加之前删除任何委托链接。
ServicePoint servicePoint = ServicePointManager.FindServicePoint(uri);
servicePoint.BindIPEndPointDelegate -= this.Bind; // avoid duplicate calls to Bind
servicePoint.BindIPEndPointDelegate += this.Bind;
我也喜欢缓存 UseIP 对象的想法。所以我将这个静态方法添加到 UseIP 类。
private static Dictionary<IPAddress, UseIP> _eachNIC = new Dictionary<IPAddress, UseIP>();
public static UseIP ForNIC(IPAddress nic)
{
lock (_eachNIC)
{
UseIP useIP = null;
if (!_eachNIC.TryGetValue(nic, out useIP))
{
useIP = new UseIP(nic);
_eachNIC.Add(nic, useIP);
}
return useIP;
}
}