← Back to Blog

.NET HttpClient में प्रॉक्सी: SocketsHttpHandler, SOCKS5 और बिना सॉकेट लीक के रोटेशन

हम समझते हैं कि .NET HttpClient में SocketsHttpHandler के साथ प्रॉक्सी को सही तरीके से कैसे सेट करें, SOCKS5 को कैसे कनेक्ट करें और बिना सॉकेट थकावट के IP रोटेशन कैसे व्यवस्थित करें।

📅October 18, 2026

यदि आपने कभी SocketException: Address already in use पकड़ा है या देखा है कि .NET एप्लिकेशन में प्रॉक्सी के साथ काम करने के कुछ घंटों बाद अचानक उपलब्ध सॉकेट समाप्त हो जाते हैं, तो समस्या लगभग हमेशा HttpClient के गलत उपयोग में होती है। इस लेख में, हम देखेंगे कि SocketsHttpHandler के माध्यम से प्रॉक्सी को कैसे सेटअप करें, SOCKS5 को कनेक्ट करें और IP रोटेशन को इस तरह से व्यवस्थित करें कि कनेक्शन पूल एक तंग जगह में न बदल जाए।

HttpClient सॉकेट्स को "खाता" क्यों है

एक सामान्य गलती है कि हर अनुरोध के लिए HttpClient का नया उदाहरण using के अंदर बनाया जाता है। प्रॉक्सी के साथ काम करते समय यह समस्या और बढ़ जाती है: प्रॉक्सी सर्वर के माध्यम से प्रत्येक कनेक्शन को अलग TCP हैंडशेक की आवश्यकता होती है, और यदि आप प्रॉक्सी को चलते-फिरते बदलते हैं, तो कनेक्शन पूल का पुनः उपयोग नहीं किया जाता है, और सॉकेट TIME_WAIT स्थिति में लटक जाते हैं।

Windows पर यह विशेष रूप से महत्वपूर्ण है - डिफ़ॉल्ट रूप से सीमित संख्या में गतिशील पोर्ट उपलब्ध होते हैं (आमतौर पर 16384), और यदि एप्लिकेशन प्रॉक्सी के माध्यम से उच्च अनुरोध तीव्रता के साथ डेटा को पार्स करता है, तो सभी पोर्ट कुछ ही मिनटों में समाप्त हो सकते हैं। Linux पर स्थिति थोड़ी बेहतर है क्योंकि यह कर्नेल सेटिंग्स के कारण है, लेकिन यदि कनेक्शन का पुनः उपयोग नहीं किया जाता है तो समस्या बनी रहती है।

पुराना HttpClientHandler, जो .NET Framework के समय से उपलब्ध है, कनेक्शन के जीवन चक्र पर लचीला नियंत्रण नहीं देता है। यही कारण है कि .NET Core 2.1 से SocketsHttpHandler का उपयोग करने की सिफारिश की गई है - यह पूरी तरह से प्रबंधित कोड में लिखा गया है और कनेक्शन पूल, DNS टाइमआउट और प्रॉक्सी सेटिंग्स तक सीधी पहुंच प्रदान करता है।

SocketsHttpHandler: इसकी आवश्यकता क्यों है

SocketsHttpHandler वह परिवहन स्तर है, जिस पर .NET Core 2.1 से HttpClient डिफ़ॉल्ट रूप से Linux और macOS पर बनाया गया है, और .NET 5 से Windows पर भी। पुराने WinHttpHandler के विपरीत, यह:

  • एक PooledConnectionLifetime सेट करने की अनुमति देता है - पूल में कनेक्शन का जीवनकाल, जो रोटेटिंग प्रॉक्सी के साथ काम करते समय महत्वपूर्ण है;
  • एक IWebProxy तक पहुंच प्रदान करता है, जिसे Proxy डेलीगेट के माध्यम से प्रोग्रामेटिक रूप से चलती-फिरती परिभाषित किया जा सकता है;
  • ConnectCallback का समर्थन करता है - परिवहन को बदलने के लिए एक निम्न-स्तरीय हुक, जो SOCKS5 के लिए आवश्यक है;
  • सॉकेट्स को PooledConnectionIdleTimeout की समाप्ति पर सही तरीके से मुक्त करता है, बिना सिस्टम में कचरा छोड़े।

बुनियादी सेटअप इस प्रकार है:

var handler = new SocketsHttpHandler
{
    PooledConnectionLifetime = TimeSpan.FromMinutes(5),
    PooledConnectionIdleTimeout = TimeSpan.FromSeconds(30),
    MaxConnectionsPerServer = 50,
    AutomaticDecompression = DecompressionMethods.All
};

var client = new HttpClient(handler)
{
    Timeout = TimeSpan.FromSeconds(30)
};

मुख्य नियम: HttpClient इस हैंडलर के साथ एक सिंगलटन होना चाहिए या IHttpClientFactory के माध्यम से बनाया जाना चाहिए - इसे हर अनुरोध पर फिर से न बनाएं, अन्यथा कनेक्शन पूल का पूरा अर्थ समाप्त हो जाता है।

SocketsHttpHandler के माध्यम से प्रॉक्सी सेटअप

HTTP/HTTPS प्रॉक्सी के लिए, हैंडलर स्तर पर Proxy प्रॉपर्टी सेट करना पर्याप्त है। यह डेटा सेंटर प्रॉक्सी और रेजिडेंशियल प्रॉक्सी के लिए काम करता है, जो HTTP CONNECT प्रोटोकॉल के माध्यम से पहुंच प्रदान करते हैं:

var proxy = new WebProxy("http://proxy-host:8000")
{
    Credentials = new NetworkCredential("login", "password")
};

var handler = new SocketsHttpHandler
{
    Proxy = proxy,
    UseProxy = true,
    PooledConnectionLifetime = TimeSpan.FromMinutes(3)
};

var client = new HttpClient(handler);

var response = await client.GetAsync("https://example.com");
Console.WriteLine(await response.Content.ReadAsStringAsync());

यदि विभिन्न होस्टों के लिए विभिन्न प्रॉक्सी कनेक्ट करने की आवश्यकता है (उदाहरण के लिए, एक डोमेन के लिए प्रमाणीकरण सीधे होता है, जबकि स्क्रैपिंग - प्रॉक्सी के माध्यम से), तो स्थिर वस्तु के बजाय डेलीगेट का उपयोग करें:

var handler = new SocketsHttpHandler
{
    Proxy = new WebProxy(),
    UseProxy = true
};

handler.Proxy = new CustomProxy(uri =>
{
    return uri.Host.Contains("target-site")
        ? new Uri("http://proxy-host:8000")
        : null;
});

मार्केटप्लेस के डेटा पार्सिंग कार्यों के लिए - जैसे कि Wildberries या Ozon पर कीमतों की निगरानी - अक्सर दोनों दृष्टिकोणों को संयोजित किया जाता है: मुख्य ट्रैफ़िक डेटा सेंटर प्रॉक्सी के माध्यम से जाता है जैसे डेटा सेंटर प्रॉक्सी, और कैप्चा सेवाओं या प्रमाणीकरण के लिए अनुरोध सीधे होते हैं।

.NET में SOCKS5 समर्थन

.NET 6 से, SocketsHttpHandler ने प्रॉक्सी URI में स्कीम निर्दिष्ट करके SOCKS4/SOCKS4a/SOCKS5 का मूल समर्थन प्राप्त किया - किसी तीसरे पक्ष की लाइब्रेरी की आवश्यकता नहीं है:

var handler = new SocketsHttpHandler
{
    Proxy = new WebProxy(new Uri("socks5://proxy-host:1080"))
    {
        Credentials = new NetworkCredential("login", "password")
    },
    UseProxy = true
};

var client = new HttpClient(handler);
var response = await client.GetAsync("https://httpbin.org/ip");

एक महत्वपूर्ण बिंदु: .NET 6 से पहले, इस तरह की कोई सुविधा नहीं थी, और डेवलपर्स को SOCKS5 हैंडशेक को TLS कनेक्शन स्थापित करने से पहले मैन्युअल रूप से करने के लिए अपना ConnectCallback लिखना पड़ता था। यदि आपका प्रोजेक्ट अभी भी .NET Core 3.1 या .NET 5 पर है, तो यह मार्ग अभी भी प्रासंगिक है:

var handler = new SocketsHttpHandler
{
    ConnectCallback = async (context, cancellationToken) =>
    {
        var socket = new Socket(SocketType.Stream, ProtocolType.Tcp)
        {
            NoDelay = true
        };

        await socket.ConnectAsync("proxy-host", 1080, cancellationToken);
        await PerformSocks5Handshake(socket, context.DnsEndPoint, cancellationToken);

        return new NetworkStream(socket, ownsSocket: true);
    }
};

व्यावहारिक रूप से, प्रोडक्शन प्रोजेक्ट्स के लिए .NET 6+ पर अपडेट करना और अंतर्निहित कार्यान्वयन का उपयोग करना आसान है - यह रनटाइम में परीक्षणों द्वारा कवर किया गया है और हैंडशेक प्रोटोकॉल का समर्थन करने की आवश्यकता नहीं है।

HttpClient को फिर से बनाए बिना प्रॉक्सी रोटेशन

IP रोटेशन को लागू करते समय एक सामान्य गलती है कि हर प्रॉक्सी परिवर्तन पर एक नया HttpClient (और इसलिए एक नया हैंडलर और एक नया कनेक्शन पूल) बनाया जाता है। यह निश्चित रूप से अनवांटेड सॉकेट्स के संचय की ओर ले जाता है। सही दृष्टिकोण यह है कि एक कस्टम IWebProxy लागू करें, जो हर कॉल पर अलग प्रॉक्सी पता लौटाता है:

public class RotatingProxy : IWebProxy
{
    private readonly string[] _proxies;
    private int _index = -1;

    public RotatingProxy(string[] proxies) => _proxies = proxies;

    public ICredentials Credentials { get; set; }

    public Uri GetProxy(Uri destination)
    {
        var next = Interlocked.Increment(ref _index) % _proxies.Length;
        return new Uri(_proxies[next]);
    }

    public bool IsBypassed(Uri host) => false;
}

यह क्लास उसी एकमात्र SocketsHttpHandler से जुड़ती है:

var handler = new SocketsHttpHandler
{
    Proxy = new RotatingProxy(new[]
    {
        "http://proxy1:8000",
        "http://proxy2:8000",
        "http://proxy3:8000"
    }),
    UseProxy = true,
    PooledConnectionLifetime = TimeSpan.FromSeconds(60)
};

var client = new HttpClient(handler);

यह योजना आंतरिक सॉकेट पूल का पुनः उपयोग करती है, लेकिन प्रत्येक अनुरोध वास्तव में नए IP के माध्यम से जाता है। यदि आप प्रत्येक प्रॉक्सी के लिए समानांतर कनेक्शन पूल बना रहे हैं (गति के लिए), तो एक उचित PooledConnectionLifetime सेट करें - आमतौर पर 30-90 सेकंड, ताकि कनेक्शन उस समय से पहले समाप्त न हों जब प्रॉक्सी प्रदाता की ओर IP समाप्त हो जाए। Avito जैसे पार्सिंग कार्यों या Instagram API से डेटा संग्रह के लिए रेसिडेंशियल प्रॉक्सी का उपयोग करना अधिक सुविधाजनक है, जिसमें प्रदाता की ओर रोटेशन होता है - तब कोड में पता बदलने की आवश्यकता नहीं होती, बस एक "इनपुट पॉइंट" बनाए रखना होता है, जिसमें स्वचालित रूप से आउटपुट IP बदलता है।

PooledConnectionLifetime और DNS रोटेशन

यदि प्रॉक्सी प्रदाता DNS रोटेशन का उपयोग करता है (एक होस्टनेम हर बार अलग IP में हल होता है), तो सीमित PooledConnectionLifetime सेट करना महत्वपूर्ण है। डिफ़ॉल्ट रूप से, यह Infinite है, और .NET पुराने IP के लिए कनेक्शन को तब तक बनाए रखेगा जब तक कि DNS अपडेट को नजरअंदाज किया जाएगा।

var handler = new SocketsHttpHandler
{
    PooledConnectionLifetime = TimeSpan.FromSeconds(30), // कनेक्शन को फिर से बनाना
    PooledConnectionIdleTimeout = TimeSpan.FromSeconds(15),
    MaxConnectionsPerServer = 20
};

यह विशेष रूप से मोबाइल प्रॉक्सी के लिए महत्वपूर्ण है, जहां प्रदाता द्वारा IP परिवर्तन हर 1-10 मिनट में हो सकता है, जो मोबाइल ऑपरेटर के रोटेशन के तहत होता है। यदि कनेक्शन को लंबे समय तक बनाए रखा जाए, तो अनुरोध पहले से ही मुक्त IP के माध्यम से जारी रहेंगे, और कभी-कभी - टाइमआउट त्रुटि भी प्राप्त कर सकते हैं। ऐसे परिदृश्यों के लिए, मोबाइल प्रॉक्सी बहुत उपयुक्त होते हैं - वे एंटी-फ्रॉड सिस्टम के प्रति उच्चतम विश्वास देते हैं, लेकिन कनेक्शन के TTL को ठीक से सेट करने की आवश्यकता होती है, विशेष रूप से क्लाइंट स्तर पर।

PooledConnectionLifetime का छोटा मान मुफ्त नहीं है - प्रत्येक कनेक्शन का पुनर्निर्माण नया TCP+TLS हैंडशेक का अर्थ है, जो 50-200 मिलीसेकंड की देरी जोड़ता है। IP की ताजगी और प्रदर्शन के बीच संतुलन अनुभवजन्य रूप से निर्धारित किया जाता है: मूल्य निगरानी कार्यों के लिए 60-120 सेकंड पर्याप्त है, जबकि उच्च आवृत्ति वाले खाता फार्मिंग के लिए - कभी-कभी 10-20 सेकंड की आवश्यकता होती है।

.NET एप्लिकेशनों के लिए प्रॉक्सी प्रकारों की तुलना

प्रॉक्सी प्रकार प्रोटोकॉल अनुशंसित PooledConnectionLifetime कार्य
डेटा सेंटर प्रॉक्सी HTTP/HTTPS, SOCKS5 5-10 मिनट पार्सिंग, API इंटीग्रेशन, उच्च गति
रेसिडेंशियल प्रॉक्सी HTTP/HTTPS, SOCKS5 30-90 सेकंड कीमतों की निगरानी, भू-प्रतिबंधों को बायपास करना
मोबाइल प्रॉक्सी HTTP/HTTPS, SOCKS5 10-30 सेकंड सोशल मीडिया में स्वचालन, एंटी-फ्रॉड-संवेदनशील API

IHttpClientFactory और प्रॉक्सी के साथ टाइप किए गए क्लाइंट

ASP.NET Core प्रोजेक्ट्स में HttpClient को IHttpClientFactory के माध्यम से पंजीकृत करने की सिफारिश की जाती है - यह डेवलपर से हैंडलर के जीवन चक्र के प्रबंधन की जिम्मेदारी को हटा देता है और आंतरिक SocketsHttpHandler का स्वचालित रूप से पुनः उपयोग करता है।

builder.Services.AddHttpClient("ProxyClient")
    .ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
    {
        Proxy = new WebProxy(new Uri("socks5://proxy-host:1080"))
        {
            Credentials = new NetworkCredential("login", "password")
        },
        UseProxy = true,
        PooledConnectionLifetime = TimeSpan.FromMinutes(2)
    })
    .SetHandlerLifetime(TimeSpan.FromMinutes(5));

SetHandlerLifetime पर ध्यान दें - यह फैक्ट्री के भीतर हैंडलर्स के रोटेशन का एक अलग तंत्र है (डिफ़ॉल्ट रूप से 2 मिनट), जिसे PooledConnectionLifetime के साथ भ्रमित नहीं होना चाहिए। फैक्ट्री समय-समय पर एक नया हैंडलर बनाती है, लेकिन पुराने कनेक्शन सक्रिय अनुरोधों को समाप्त होने तक अपना समय बिताते रहते हैं - गार्बेज कलेक्टर उन्हें स्वचालित रूप से साफ करेगा।

विभिन्न प्रॉक्सी पूलों के साथ परिदृश्यों के लिए, विभिन्न नामित क्लाइंट्स को पंजीकृत करना सुविधाजनक है - उदाहरण के लिए, अमेरिका के लिए अलग और यूरोप के लिए अलग, यदि कार्य Google Ads या Facebook Ads के विज्ञापन परीक्षण का है, तो विभिन्न क्षेत्रों से:

builder.Services.AddHttpClient("US-Proxy")
    .ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
    {
        Proxy = new WebProxy("http://us-proxy-host:8000"),
        UseProxy = true
    });

builder.Services.AddHttpClient("EU-Proxy")
    .ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
    {
        Proxy = new WebProxy("http://eu-proxy-host:8000"),
        UseProxy = true
    });

सॉकेट लीक का निदान

यदि आपको सॉकेट के संचय का संदेह है, तो अनुमान न लगाएं - जांचें। Linux पर, प्रक्रिया के खुले कनेक्शनों की संख्या देखने का एक त्वरित तरीका:

# TIME_WAIT में कनेक्शनों की संख्या
ss -tan state time-wait | wc -l

# एक विशेष प्रक्रिया (PID) के सभी कनेक्शन
ls -la /proc/{PID}/fd | grep socket | wc -l

Windows पर, समान आंकड़े netstat -ano कमांड द्वारा PID प्रक्रिया के अनुसार फ़िल्टरिंग के साथ प्राप्त होते हैं। .NET स्तर पर गहरे निदान के लिए, अंतर्निहित उपकरण dotnet-counters का उपयोग करें, जो वास्तविक समय में System.Net.Http के मेट्रिक्स को दिखाता है:

dotnet-counters monitor -p {PID} --counters System.Net.Http

# प्रमुख काउंटर:
# current-connections
# connections-duration
# requests-failed-rate

यदि current-connections लगातार बढ़ता है और कभी कम नहीं होता - यह स्पष्ट संकेत है कि हैंडलर बहुत बार पुनः बनाया जा रहा है, या PooledConnectionIdleTimeout को Infinite पर सेट किया गया है, और अनुरोध ठीक से समाप्त नहीं हो रहे हैं (उदाहरण के लिए, प्रतिक्रिया का शरीर अंत तक नहीं पढ़ा जाता है, और कनेक्शन पूल में वापस नहीं लौटता)।

एक और सामान्य कारण लीक का - भूले हुए response.Content, जिसे नहीं पढ़ा गया या डिस्पोज़ नहीं किया गया। हमेशा using var response = ... या प्रतिक्रिया के साथ काम करने के बाद Dispose() का स्पष्ट कॉल करें।

निष्कर्ष

.NET में प्रॉक्सी के साथ सही तरीके से काम करने के तीन सिद्धांत हैं: पुराने कार्यान्वयनों के बजाय SocketsHttpHandler का उपयोग करें, HttpClient को सिंगलटन के रूप में रखें (या IHttpClientFactory के माध्यम से), और प्रॉक्सी रोटेशन को कस्टम IWebProxy के माध्यम से लागू करें, न कि पूरे क्लाइंट को फिर से बनाकर। .NET 6 से SOCKS5 का मूल समर्थन है, जो हैंडशेक प्रोटोकॉल को लिखने की आवश्यकता से छुटकारा देता है।

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