2011-05-24 91 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