11

サーバーで使用可能なIPアドレスの1つから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

しかし、このソリューションは初めて機能します。

4

4 に答える 4

16

理論:

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));
    }
于 2011-05-28T23:33:58.137 に答える
1

問題は、新しいリクエストごとにデリゲートがリセットされることにある可能性があります。以下をお試しください:

//servicePoint.BindIPEndPointDelegate = null; // Clears all delegates first, for testing
servicePoint.BindIPEndPointDelegate += delegate
    {
        var address = IPAddress.Parse(this.IP);
        return new IPEndPoint(address, 0);
    };

また、私が知る限り、エンドポイントはキャッシュされているため、デリゲートをクリアしても機能しない場合があり、関係なくリセットされる可能性があります。最悪のシナリオとして、アプリドメインをアンロード/リロードできます。

于 2011-05-28T23:57:26.530 に答える
0

私はあなたの例を少し変更し、それを私のマシンで動作させるようにしました:

public HttpWebRequest CreateWebRequest(Uri uri)
{
    HttpWebRequest wr = WebRequest.Create(uri) as HttpWebRequest;
    wr.ServicePoint.BindIPEndPointDelegate = new BindIPEndPoint(Bind);
    return wr;
}

私はそれをしました:

  • FindServicePointの呼び出しは、バインディングデリゲートを呼び出さなくても、指定したURIに対して「デフォルト」IPを使用して実際に要求を実行すると思います。私のマシンでは、少なくとも、BindIPEndPointDelegateあなたが提示した方法で呼び出されませんでした(プロキシを設定せず、プロキシ認証エラーが発生したため、リクエストが行われたことはわかっています)。
  • ServicePointManagerのドキュメントには、「そのホストとスキームに既存のServicePointオブジェクトがある場合、ServicePointManagerオブジェクトは既存のServicePointオブジェクトを返します。それ以外の場合、ServicePointManagerオブジェクトは新しいServicePointオブジェクトを作成します」と記載されています。 URIが同じである場合のServicePoint(おそらく、後続の呼び出しが同じEndPointで発生している理由を説明しています)。
  • このようにして、URIがすでに要求されている場合でも、以前のの「キャッシュ」を使用する代わりに、目的のIPを使用することを確認できますServicePointManager
于 2011-05-27T14:32:08.410 に答える
0

私はこの新しいクラスUseIPが好きです。

IPv4 / IPv6の違いから身を守るために、WCFクライアントで使用する送信IPアドレスを指定するポイントがあります。

変更する必要があるのは、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);
}

re: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;
    }
}
于 2013-01-28T01:52:53.427 に答える