2011-05-24 85 views
9

我想打一个web请求从可用IP之一,所以我使用这个类的服务器地址:绑定的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 

但解决方案只是第一次运作!

+0

是的,我想快速更改我的IP地址。我应该走什么路? – Xaqron 2011-05-24 18:05:26

+0

你有没有例外? – alexD 2011-05-24 18:18:55

+0

您可以尝试仅在第一次进行绑定并将其保存在静态变量或实例变量中? – Cilvic 2011-05-24 18:22:06

回答

14

一个理论:

HttpWebRequest的依赖于一个潜在的ServicePoint。 ServicePoint表示到URL的实际连接。与浏览器保持与请求之间打开的URL的连接方式大致相同,并重新使用该连接(以消除打开和关闭每个请求的连接开销),ServicePoint为HttpWebRequest执行相同的功能。

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

不幸的是,ServicePoint接口看起来并不能让您直接强制关闭连接。

两种溶液(每个具有略微不同的结果)

1)对于每个请求,设置HttpWebRequest.KeepAlive =假。在我的测试中,这导致绑定委托与每个请求一对一调用。

2)将ServicePoint ConnectionLeaseTimeout属性设置为零或某个小值。这将会周期性地强制调用绑定代理(不是每个请求都是一对一的)。

documentation

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

默认情况下,当请求的KeepAlive为true时,由于 处于非活动状态,因此MaxIdleTime 属性会设置关闭ServicePoint连接的超时时间。如果ServicePoint具有活动连接,则MaxIdleTime 不起作用,并且连接将无限期地保持打开状态。

当ConnectionLeaseTimeout属性被设定为比 -1以外的值,并经过指定的时间之后,活性的ServicePoint连接正在服务通过设置的KeepAlive到 在该请求假的请求后关闭。

设置此值会影响由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)); 
    } 
+0

摘要 - 如果您希望每个请求来自新的IP地址,请确保HttpWebRequest.KeepAlive对于每个请求都为false。性能将受到影响,因为您正在打开和关闭每个请求的连接。如果您想偶尔强制一个新的IP地址用于给定的URI,请使用ConnectionLeaseTimeout。 – 2011-05-29 00:10:37

+0

@Joe:我测试了将'HttpWebRequest'的'KeepAlive'设置为'false'。有些网站拒绝为这样的客户提供服务。 – Xaqron 2011-05-30 20:02:46

+0

好的 - 如果是这样的话,你有两个选择:使用ConnectionLeaseTimeout = 0。您偶尔会重用IP地址,但约60%(来自我的测试)您的请求将调用绑定代理。如果这是不可接受的,那么使用HttpWebRequest将不会为你工作。您需要编写自己的Web客户端版本,该Web客户端发送KeepAlive标头,但在请求后关闭连接。您可以使用System.Net.Socket类来完成此操作。 – 2011-05-30 23:00:13

0

我已经改变了你的榜样一点,使我的机器上工作:

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(也许解释为什么后续调用发生在同一EndPoint中)。
  • 通过这种方式,我们可以确定,即使已经请求了URI,它也将使用所需的IP,而不是使用先前的“缓存”ServicePointManager
+0

我以前试过这个。如果您快速更改IP地址,您会发现它不起作用。问题是委托应该是'static',所以你不能同时连接来自不同IP地址的同一个'Uri'。 – Xaqron 2011-05-27 16:52:22

+0

re:**可能总是返回同一个Uri的同一个ServicePoint ** True !.奇怪的是,它返回同一个远程IP地址的_any_ Uri相同的ServicePoint。例如。 ** http:// 1.2.3.4/FirstTarget**和** http:// 1.2.3.4/SecondTarget**返回相同的ServicePoint。 – 2013-01-28 01:37:53

1

的问题可能是在每个新的请求委托得到复位。尝试下面:

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

而且据我所知,端点缓存,因此即使清除代表可能无法在某些情况下工作,他们可能会不顾复位。作为最糟糕的情况,您可能会卸载/重新加载应用程序域。

0

我喜欢这个新班级UseIP

Specify the outgoing IP Address to use with WCF client关于保护自己免受IPv4/IPv6差异的要点。

,将需要改变的唯一事情是要这样的绑定方法:

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

重:绑定方法被调用多次

对我而言,适用于删除任何代理链接之前添加它。

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

糟糕。抱歉。我将** IP **的类型更改为** IPAddress **,这样我就不必每次都解析它。我忘了提到这一点。 – 2013-01-28 02:14:03