第一次绑定IP地址就可以了

发布于 2024-11-09 11:50:55 字数 949 浏览 0 评论 0原文

我想从服务器上的一个可用 IP 地址发出 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

但是该解决方案第一次就有效!

I want to make a web request from one of available IP addresses on server so I use this class:

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);
    }
}

Then:

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

But the solution just works the first time!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

微暖i 2024-11-16 11:50:55

理论:

HttpWebRequest 依赖于底层 ServicePoint。 ServicePoint 表示与 URL 的实际连接。与浏览器在请求之间保持与 URL 的连接打开并重用该连接(以消除每个请求打开和关闭连接的开销)的方式非常相似,ServicePoint 对 HttpWebRequest 执行相同的功能。

我认为您为 ServicePoint 设置的 BindIPEndPointDelegate 不会在每次使用 HttpWebRequest 时被调用,因为 ServicePoint 正在重用连接。如果您可以强制关闭连接,则下次调用该 URL 应该会导致 ServicePoint 需要再次调用 BindIPEndPointDelegate。

不幸的是,ServicePoint 接口似乎无法让您直接强制关闭连接。

两种解决方案(每种结果略有不同)

1) 对于每个请求,设置 HttpWebRequest.KeepAlive = false。在我的测试中,这导致 Bind 委托在每个请求中都被一对一地调用。

2) 将ServicePoint ConnectionLeaseTimeout 属性设置为零或某个较小的值。这将产生定期强制调用 Bind 委托的效果(而不是针对每个请求一对一)。

来自文档

您可以使用此属性来确保 ServicePoint 对象的
活动连接不会无限期地保持打开状态。此属性是
适用于应断开连接的场景
定期重新建立,例如负载均衡场景。

默认情况下,当请求的 KeepAlive 为 true 时,MaxIdleTime
属性设置关闭 ServicePoint 连接的超时时间
不活动。如果 ServicePoint 有活动连接,则 MaxIdleTime
没有任何影响,并且连接无限期地保持打开状态。

当 ConnectionLeaseTimeout 属性设置为除
-1,并且在指定时间过后,通过将 KeepAlive 设置为,在服务请求后关闭活动 ServicePoint 连接
该请求为 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));
    }

A theory:

HttpWebRequest relies on an underlying ServicePoint. The ServicePoint represents the actual connection to the URL. Much in the same way your browser keeps a connection to a URL open between requests and reuses that connection (to eliminate the overhead of opening and closing the connection with each request), ServicePoint performs the same function for HttpWebRequest.

I think that the BindIPEndPointDelegate that you are setting for the ServicePoint is not being called on each use of HttpWebRequest because the ServicePoint is reusing the connection. If you could force the connection to close, then the next call to that URL should cause the ServicePoint to need to call BindIPEndPointDelegate again.

Unfortunately, it doesn't appear that the ServicePoint interface gives you the ability to directly force a connection to close.

Two solutions (each with slightly different results)

1) For each request, set HttpWebRequest.KeepAlive = false. In my test, this caused the Bind delegate to get called one-for-one with each request.

2) Set the ServicePoint ConnectionLeaseTimeout property to zero or some small value. This will have the effect of periodically forcing the Bind delegate to be called (not one-for-one with each request).

From the documentation:

You can use this property to ensure that a ServicePoint object's
active connections do not remain open indefinitely. This property is
intended for scenarios where connections should be dropped and
reestablished periodically, such as load balancing scenarios.

By default, when KeepAlive is true for a request, the MaxIdleTime
property sets the time-out for closing ServicePoint connections due to
inactivity. If the ServicePoint has active connections, MaxIdleTime
has no effect and the connections remain open indefinitely.

When the ConnectionLeaseTimeout property is set to a value other than
-1, and after the specified time elapses, an active ServicePoint connection is closed after servicing a request by setting KeepAlive to
false in that request.

Setting this value affects all connections managed by the ServicePoint object.

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;
    }
}

The following (basic) test results in the Bind delegate getting called for each request:

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));
    }
愛放△進行李 2024-11-16 11:50:55

问题可能是代表在每个新请求时都会重置。请尝试以下操作:

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

据我所知,端点会被缓存,因此即使清除委托在某些情况下也可能不起作用,并且无论如何它们都可能会被重置。作为最坏的情况,您可以卸载/重新加载应用程序域。

Problem may be with the delegate getting reset on each new request. Try below:

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

Also as far as I know, the endpoints are cached so even clearing the delegate may not work in some cases and they may get reset regardless. You may unload/reload the app domain as the worst case scenario.

红墙和绿瓦 2024-11-16 11:50:55

我对您的示例进行了一些更改并使其在我的机器上工作:

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 相同,它可能会始终返回相同的 ServicePoint(也许可以解释为什么)后续调用发生在同一个端点)。
  • 通过这种方式,我们可以确保,即使 URI 已经被请求,它也会使用所需的 IP,而不是使用 ServicePointManager 的一些先前的“缓存”。

I have changed your example a little and make it work on my machine:

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

I did that because:

  • I think the call to FindServicePoint actually does the request using the "default" ip, without even calling the binding delegate, to the URI you have specified. In my machine, at least, the BindIPEndPointDelegate was not called in the way you have presented (I know the request was made because I didn't set the Proxy and got a proxy authentication error);
  • In the documentation of ServicePointManager, it states that "If there is an existing ServicePoint object for that host and scheme, the ServicePointManager object returns the existing ServicePoint object; otherwise, the ServicePointManager object creates a new ServicePoint object" witch would probably return always the same ServicePoint if the URI was the same (maybe explaining why the subsequent calls is happening in the same EndPoint).
  • In this way we can be sure that, even when the URI has already been requested, it will use the desired IP instead of using some previous "caching" of ServicePointManager.
一个人的旅程 2024-11-16 11:50:55

我喜欢这个新类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;
    }
}

I like this new class UseIP.

There is a point at Specify the outgoing IP Address to use with WCF client about protecting yourself from IPv4/IPv6 differences.

The only thing that would need to change is the Bind method to be like this:

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: the Bind method being called multiple times.

What works for me is to remove any delegate link before I add it.

ServicePoint servicePoint = ServicePointManager.FindServicePoint(uri);
servicePoint.BindIPEndPointDelegate -= this.Bind;   // avoid duplicate calls to Bind
servicePoint.BindIPEndPointDelegate += this.Bind;

I also like the idea of caching the UseIP objects. So I added this static method to the UseIP class.

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;
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文