一个理论:
HttpWebRequest 依赖于底层的 ServicePoint。ServicePoint 表示与 URL 的实际连接。与您的浏览器保持与请求之间打开的 URL 的连接并重用该连接的方式非常相似(以消除每个请求打开和关闭连接的开销),ServicePoint 对 HttpWebRequest 执行相同的功能。
我认为您为 ServicePoint 设置的 BindIPEndPointDelegate 不会在每次使用 HttpWebRequest 时被调用,因为 ServicePoint 正在重用连接。如果您可以强制关闭连接,那么对该 URL 的下一次调用应该会导致 ServicePoint 需要再次调用 BindIPEndPointDelegate。
不幸的是,ServicePoint 界面似乎没有让您能够直接强制关闭连接。
两种解决方案(每种解决方案的结果略有不同)
1) 对于每个请求,设置 HttpWebRequest.KeepAlive = false。在我的测试中,这导致 Bind 委托在每个请求中被一对一地调用。
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);
};
//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;
}
}
以下(基本)测试导致每个请求都调用 Bind 委托:
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));
}