一种理论:
HttpWebRequest 依赖于基础的 ServicePoint。ServicePoint 表示与 URL 的实际连接。就像您的浏览器在请求之间保持与 URL 的连接并重用该连接(以消除每个请求打开和关闭连接的开销)一样,ServicePoint 为 HttpWebRequest 执行相同的功能。
我认为您为 ServicePoint 设置的 BindIPEndPointDelegate 在每次使用 HttpWebRequest 时都没有被调用,因为 ServicePoint 正在重用连接。如果您可以强制关闭连接,那么对该 URL 的下一次调用应该导致 ServicePoint 需要再次调用 BindIPEndPointDelegate。
不幸的是,似乎 ServicePoint 接口并没有直接强制关闭连接的能力。
两种解决方案(每种方案都有稍微不同的结果)
1)对于每个请求,设置 HttpWebRequest.KeepAlive = false。在我的测试中,这导致每个请求都会调用绑定委托。
2)将 ServicePoint ConnectionLeaseTimeout 属性设置为零或某个较小的值。这将具有周期性地强制调用绑定委托的效果(不是每个请求都会调用)。
来自 文档:
您可以使用此属性来确保 ServicePoint 对象的活动连接不会无限期保持打开状态。该属性适用于应在定期间隔内放弃并重新建立连接的场景,例如负载平衡场景。
默认情况下,对于请求,当 KeepAlive 为 true 时,MaxIdleTime 属性设置因为不活动而关闭 ServicePoint 连接的超时时间。如果 ServicePoint 具有活动连接,则 MaxIdleTime 无效,连接将无限期保持打开状态。
当 ConnectionLeaseTimeout 属性设置为除 -1 以外的值,并且经过指定的时间后,在服务请求后通过在该请求中将 KeepAlive 设置为 false 来关闭活动 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);
};
servicePoint.ConnectionLeaseTimeout = 0;
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(uri);
req.KeepAlive = false;
return req;
}
}
以下(基本)测试会导致绑定委托针对每个请求被调用:
static void Main(string[] args)
{
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));
}
UseIP
类实例将使用相同的IP地址。 - Xaqron