Привязка IP-адреса работает только в первый раз - PullRequest
10 голосов
/ 24 мая 2011

Я хочу сделать веб-запрос с одного из доступных IP-адресов на сервере, поэтому я использую этот класс:

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 ]

15 голосов
/ 29 мая 2011

Теория:

HttpWebRequest опирается на базовую ServicePoint.ServicePoint представляет фактическое соединение с URL.Во многом аналогично тому, как ваш браузер поддерживает соединение с URL-адресом открытым между запросами и повторно использует это соединение (чтобы исключить издержки на открытие и закрытие соединения с каждым запросом), ServicePoint выполняет ту же функцию для HttpWebRequest.

Я думаю, что BindIPEndPointDelegate, который вы устанавливаете для ServicePoint, не вызывается при каждом использовании HttpWebRequest, потому что ServicePoint повторно использует соединение.Если вы могли бы принудительно закрыть соединение, то следующий вызов этого URL-адреса должен заставить ServicePoint снова вызывать BindIPEndPointDelegate.

К сожалению, не похоже, что интерфейс ServicePoint дает вам возможность напрямуюпринудительно закрыть соединение.

Два решения (каждое с немного отличающимися результатами)

1) Для каждого запроса установите HttpWebRequest.KeepAlive = false.В моем тесте это приводило к тому, что делегат Bind вызывался один-на-один с каждым запросом.

2) Установите для свойства ServicePoint ConnectionLeaseTimeout значение ноль или небольшое значение.Это будет периодически приводить к вызову делегата Bind (не один к одному с каждым запросом).

Из документации 1016 *:

Вы можете использовать это свойство, чтобы гарантировать, что активные соединения объекта ServicePoint не остаются открытыми бесконечно.Это свойство предназначено для сценариев, в которых соединения должны периодически сбрасываться и восстанавливаться, например, для сценариев балансировки нагрузки.

По умолчанию, когда KeepAlive имеет значение true для запроса, свойство MaxIdleTime устанавливает время ожидания для закрытия соединений ServicePoint.из-за бездействия.Если у ServicePoint есть активные соединения, MaxIdleTime не имеет никакого эффекта, и соединения остаются открытыми в течение неопределенного времени.

Когда для свойства ConnectionLeaseTimeout установлено значение, отличное от -1, и по истечении указанного времени активное соединение ServicePoint будетзакрывается после обслуживания запроса путем установки для KeepAlive значения false в этом запросе.

Установка этого значения влияет на все соединения, управляемые объектом 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));
    }
1 голос
/ 29 мая 2011

Возможно, проблема в том, что делегат сбрасывается при каждом новом запросе.Попробуйте ниже:

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

Также, насколько я знаю, конечные точки кэшируются, поэтому даже очистка делегата может не работать в некоторых случаях, и они могут быть сброшены независимо.Вы можете выгрузить / перезагрузить домен приложения в худшем случае.

0 голосов
/ 28 января 2013

Мне нравится этот новый класс UseIP .

Есть точка на Укажите исходящий IP-адрес для использования с клиентом WCF о защите от 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);
}

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;
    }
}
0 голосов
/ 27 мая 2011

Я немного изменил ваш пример и заставил его работать на моей машине:

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 гласит, что «если для этого хоста и схемы существует существующий объект ServicePoint, объект ServicePointManager возвращает существующий объект ServicePoint; в противном случае объект ServicePointManager создает новый объект ServicePoint»,возможно, возвращать всегда одну и ту же точку обслуживания, если URI был одним и тем же (возможно, объяснять, почему последующие вызовы происходят в одной и той же конечной точке).
  • Таким образом, мы можем быть уверены, что даже когда URI уже был запрошен, он будет использовать нужный IP-адрес вместо некоторого предыдущего «кэширования» ServicePointManager.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...