理論:
HttpWebRequestは、基盤となるServicePointに依存しています。ServicePointは、URLへの実際の接続を表します。ブラウザがリクエスト間でURLへの接続を開いたままにし、その接続を再利用するのとほぼ同じ方法で(各リクエストで接続を開いたり閉じたりするオーバーヘッドを排除するため)、ServicePointはHttpWebRequestに対して同じ機能を実行します。
ServicePointが接続を再利用しているため、ServicePointに設定しているBindIPEndPointDelegateは、HttpWebRequestを使用するたびに呼び出されていないと思います。接続を強制的に閉じることができる場合は、そのURLを次に呼び出すと、ServicePointがBindIPEndPointDelegateを再度呼び出す必要があります。
残念ながら、ServicePointインターフェイスでは、接続を直接強制的に閉じることができるようには見えません。
2つの解決策(それぞれわずかに異なる結果)
1)リクエストごとに、HttpWebRequest.KeepAlive=falseを設定します。私のテストでは、これにより、バインドデリゲートがリクエストごとに1対1で呼び出されました。
2)ServicePointConnectionLeaseTimeoutプロパティをゼロまたは小さな値に設定します。これにより、バインドデリゲートが定期的に呼び出されるようになります(リクエストごとに1対1ではありません)。
ドキュメントから:
このプロパティを使用して、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;
}
}
次の(基本的な)テストの結果、バインドデリゲートがリクエストごとに呼び出されます。
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));
}