2011-05-24 20 views
9

मैं एक वेब अनुरोध बनाना चाहते के लिए काम करता उपलब्ध आईपी से एक सर्वर पर संबोधित करते से तो मैं इस वर्ग का उपयोग करें:एक आईपी पता बाइंडिंग सिर्फ पहली बार

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

हां, मैं अपना आईपी पता तेजी से बदलना चाहता हूं। मुझे किस दृष्टिकोण पर जाना चाहिए? – Xaqron

+0

क्या आपको कोई अपवाद मिल रहा है? – alexD

+0

आप बाध्यकारी केवल पहली बार बनाने और इसे स्थिर या आवृत्ति चर में रखने की कोशिश कर सकते हैं? – Cilvic

उत्तर

14

एक सिद्धांत:

HttpWebRequest एक अंतर्निहित ServicePoint पर निर्भर करता है। सेवापॉइंट यूआरएल के वास्तविक कनेक्शन का प्रतिनिधित्व करता है। वैसे ही आपका ब्राउज़र अनुरोधों के बीच खुले यूआरएल से कनेक्शन रखता है और उस कनेक्शन का पुन: उपयोग करता है (प्रत्येक अनुरोध के साथ कनेक्शन खोलने और बंद करने के ऊपरी हिस्से को खत्म करने के लिए), सर्विसपॉइंट HttpWebRequest के लिए एक ही कार्य करता है।

मुझे लगता है कि BindIPEndPointDelegate जिसे आप सर्विसपॉइंट के लिए सेट कर रहे हैं उसे HttpWebRequest के प्रत्येक उपयोग पर नहीं कहा जा रहा है क्योंकि सेवापॉइंट कनेक्शन का पुन: उपयोग कर रहा है। यदि आप कनेक्शन को बंद करने के लिए मजबूर कर सकते हैं, तो उस यूआरएल के लिए अगली कॉल सर्विसपॉइंट को फिर से BindIPEndPointDelegate को कॉल करने की आवश्यकता होनी चाहिए।

दुर्भाग्य से, ऐसा नहीं लगता है कि सर्विसपॉइंट इंटरफ़ेस आपको कनेक्शन को बंद करने के लिए सीधे बल देने की क्षमता देता है।

दो समाधान (थोड़ा अलग परिणामों के साथ प्रत्येक)

1) प्रत्येक अनुरोध के लिए, HttpWebRequest.KeepAlive = false निर्धारित किया है। मेरे परीक्षण में, इसने बाईंड प्रतिनिधि को प्रत्येक अनुरोध के साथ एक-एक के लिए बुलाया।

2) सेवापॉइंट कनेक्शन LeaseTimeout संपत्ति को शून्य या कुछ छोटे मान पर सेट करें। इसका समय-समय पर बाध्य प्रतिनिधि को बुलाए जाने का असर होगा (प्रत्येक अनुरोध के साथ एक के लिए नहीं)।

documentation से

:

आप इस प्रॉपर्टी को सुनिश्चित करना है कि एक ServicePoint वस्तु की सक्रिय कनेक्शन अनिश्चित काल के लिए खुले रहते हैं नहीं है का उपयोग कर सकते हैं। यह संपत्ति उन परिदृश्यों के लिए लक्षित है जहां कनेक्शन को छोड़ दिया जाना चाहिए और समय-समय पर पुन: स्थापित किया गया है, जैसे भार संतुलन परिदृश्य।

डिफ़ॉल्ट रूप से, जब KeepAlive अनुरोध के लिए सच है, तो MaxIdleTime संपत्ति निष्क्रियता के कारण सेवापॉइंट कनेक्शन बंद करने के लिए टाइम-आउट सेट करती है। यदि सर्विसपॉइंट के सक्रिय कनेक्शन हैं, तो MaxIdleTime का कोई प्रभाव नहीं पड़ता है और कनेक्शन अनिश्चित काल तक खुला रहता है।

ConnectionLeaseTimeout संपत्ति -1 के अलावा किसी अन्य मान पर सेट किया जाता है, और निर्दिष्ट समय समाप्त होने के बाद, एक सक्रिय ServicePoint कनेक्शन उस अनुरोध में KeepAlivefalse करने के लिए की स्थापना करके एक अनुरोध सर्विसिंग के बाद बंद कर दिया है।

यह मान सेट करना सर्विसपॉइंट ऑब्जेक्ट द्वारा प्रबंधित सभी कनेक्शन को प्रभावित करता है।

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

सारांश - यदि आप प्रत्येक अनुरोध को नए आईपी पते से आना चाहते हैं, तो सुनिश्चित करें कि प्रत्येक अनुरोध के लिए HttpWebRequest.KeepAlive झूठा है। प्रदर्शन भुगतना होगा क्योंकि आप प्रत्येक अनुरोध के साथ कनेक्शन खोल रहे हैं और बंद कर रहे हैं। यदि आप किसी दिए गए यूआरआई के लिए उपयोग किए जाने वाले नए आईपी पते को मौलिक रूप से मजबूर करना चाहते हैं तो कनेक्शन LeaseTimeout का उपयोग करें। –

+0

@ जो: मैंने 'HttpWebRequest' के' KeepAlive' को 'false' पर सेट करने का परीक्षण किया है। कुछ वेबसाइटें ऐसे ग्राहक की सेवा करने से इनकार करती हैं। – Xaqron

+0

ठीक है - अगर ऐसा है, तो आपके पास दो विकल्प हैं: ConnectionLeaseTimeout = 0 का उपयोग करें। आप कभी-कभी आईपी पते का पुन: उपयोग करेंगे, लेकिन आपके अनुरोधों के बारे में 60% (मेरे परीक्षण से) बाइंड प्रतिनिधि को कॉल करेंगे। यदि यह स्वीकार्य नहीं है, तो HttpWebRequest का उपयोग करके 'आपके लिए काम नहीं करेगा। आपको एक वेब क्लाइंट का अपना संस्करण लिखना होगा जो KeepAlive शीर्षलेख भेजता है लेकिन अनुरोध के बाद कनेक्शन बंद कर देता है। आप System.Net.Socket क्लास का उपयोग कर ऐसा कर सकते हैं। –

0

एक छोटे और यह मेरे मशीन पर काम करने के मैं अपने उदाहरण से बदल दिया है:

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

मुझे लगता है कि क्योंकि आपने क्या किया:

  • मुझे लगता है कि FindServicePoint करने के लिए कॉल वास्तव में उपयोग करते हुए अनुरोध करता है " डिफ़ॉल्ट "आईपी, बाध्यकारी प्रतिनिधि को भी बुलाए बिना, यूआरआई को आपने निर्दिष्ट किया है। मेरी मशीन में, कम से कम, BindIPEndPointDelegate आपके द्वारा प्रस्तुत किए गए तरीके से नहीं बुलाया गया था (मुझे पता है कि अनुरोध किया गया था क्योंकि मैंने प्रॉक्सी सेट नहीं किया था और प्रॉक्सी प्रमाणीकरण त्रुटि मिली है);
  • ServicePointManager के दस्तावेज़ीकरण में, यह कहता है कि "यदि उस मेजबान और योजना के लिए कोई मौजूदा सेवापॉइंट ऑब्जेक्ट है, तो ServicePointManager ऑब्जेक्ट मौजूदा सेवापॉइंट ऑब्जेक्ट देता है, अन्यथा, ServicePointManager ऑब्जेक्ट एक नई सर्विसपॉइंट ऑब्जेक्ट बनाता है" चुड़ैल शायद वापस आ जाएगा यदि यूआरआई समान था तो हमेशा एक ही सेवापॉइंट (हो सकता है कि बाद में कॉल उसी एंडपॉइंट में क्यों हो रहा है)।
  • इस तरह से हम यह सुनिश्चित कर सकते हैं कि, जब भी यूआरआई से पहले ही अनुरोध किया गया हो, तो यह ServicePointManager के कुछ पिछले "कैशिंग" का उपयोग करने के बजाय वांछित आईपी का उपयोग करेगा।
+0

मैंने पहले यह कोशिश की है। यदि आप तेजी से आईपी पता बदलते हैं तो आप पाएंगे कि यह काम नहीं करता है। समस्या यह है कि प्रतिनिधि 'स्थिर' होना चाहिए ताकि आप एक ही समय में अलग-अलग आईपी पते से उसी 'उरी' को कनेक्ट नहीं कर सकें। – Xaqron

+0

पुन: ** शायद उसी यूरी के लिए हमेशा एक ही सेवापॉइंट वापस कर दें। ** सच! विचित्रता यह है कि यह _any_ उरी के लिए एक ही सेवापॉइंट देता है जो एक ही रिमोट आईपी एड्रेस को संबोधित करता है। जैसे ** http: // 1.2.3.4/FirstTarget** और ** http: // 1.2.3.4/SecondTarget** एक ही सेवापॉइंट लौटाएं। –

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

मुझे यह नई कक्षा उपयोग करें पसंद है।

आईपीवी 4/आईपीवी 6 मतभेदों से खुद को बचाने के बारे में Specify the outgoing IP Address to use with WCF client पर एक बिंदु है।

केवल बात यह है कि बदलने की जरूरत है कि इस तरह से आबद्ध विधि है:

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 वस्तुओं कैशिंग के विचार की तरह। इसलिए मैंने इस स्थिर विधि को उपयोग आईडी कक्षा में जोड़ा।

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

ओह। माफ़ कीजिये। मैंने संपत्ति का प्रकार ** आईपी ** ** आईपीएड्रेस ** बदल दिया है इसलिए मुझे इसे हर बार पार्स नहीं करना पड़ेगा। मैं उसका उल्लेख करना भूल गया। –

संबंधित मुद्दे