【问题标题】:Keep TCP port open using httpclient in C#在 C# 中使用 httpclient 保持 TCP 端口打开
【发布时间】:2014-12-29 19:31:28
【问题描述】:

我是异步编程的新手,我正在尝试使用 httpclient 为页面内容触发批量 URL 请求。 这是我的尝试:

    private async void ProcessUrlAsyncWithHttp(HttpClient httpClient, string purl)
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();
        HttpResponseMessage response = null;
        try
        {
            Interlocked.Increment(ref _activeRequestsCount);
            var request = new HttpRequestMessage()
            {
                RequestUri = new Uri(purl),
                Method = HttpMethod.Get,
            };
            request.Headers.TryAddWithoutValidation("User-Agent", "MozillaMozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36");
            request.Headers.TryAddWithoutValidation("Accept", "text/html,*.*");
            request.Headers.TryAddWithoutValidation("Connection", "Keep-Alive");
            request.Headers.TryAddWithoutValidation("Accept-Encoding", "gzip, deflate, sdch");
            request.Headers.TryAddWithoutValidation("Accept-Language", "en-US,en;q=0.8");

            response = await httpClient.SendAsync(request).ConfigureAwait(false);
            string html = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
            response.Dispose();
            if (IsCaptcha(html)) throw new Exception("Captcha was returned");
            request.Dispose();
            Interlocked.Increment(ref _successfulCalls);
        }
        catch (HttpRequestException hex)
        {
            Console.WriteLine("http:" + hex.Message);
            Interlocked.Increment(ref _failedCalls);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.GetType().AssemblyQualifiedName + " " + ex.Message);
            Interlocked.Increment(ref _failedCalls);
        }
        finally
        {
            Interlocked.Decrement(ref _activeRequestsCount);
            Interlocked.Decrement(ref _itemsLeft);
            if (response != null) response.Dispose();
            if (httpClient != null) httpClient.Dispose();
            sw.Stop();
            DateTime currentTime = DateTime.UtcNow;
            TimeSpan elapsedTillNow = (currentTime - _overallStartTime).Duration();
            Console.WriteLine("Left:" + _itemsLeft + ", Current execution:" + sw.ElapsedMilliseconds + " (ms), Average execution:" + Math.Round((elapsedTillNow.TotalMilliseconds / (_totalItems - _itemsLeft)), 0) + " (ms)");

            lock(_syncLock)
            {
                if (_itemsLeft == 0)
                {
                    _overallEndTime = DateTime.UtcNow;
                    this.DisplayTestResults();
                }
            }                
        }

    }

如您所见,我将一个 httpclient 传递给该函数,并且每次下载 URL 时它都会被销毁。我知道这是一种矫枉过正的做法,理想情况下我们应该重用 httpclient。但是由于我不能为每个 URL 使用具有不同代理的单个 httpclient(处理程序需要传递给 httpclient 的构造函数并且不能更改,因此如果不重新创建 httpclient 对象就不能提供新的代理),我需要使用这个接近。

在调用方,我有一个非常基本的代码:

    public async void TestAsyncWithHttp()
    {
        ServicePointManager.DefaultConnectionLimit = 10;
        //ServicePointManager.UseNagleAlgorithm = false; 
        List<string> urlList = SetUpURLList();
        urlList = urlList.GetRange(1, 50);
        _itemsLeft = urlList.Count();
        _totalItems = _itemsLeft;
        List<string> proxies = new List<string>();
        proxies.Add("124.161.94.8:80");
        proxies.Add("183.207.228.8:80");
        proxies.Add("202.29.97.5:3128");
        proxies.Add("210.75.14.158:80");
        proxies.Add("203.100.80.81:8080");
        proxies.Add("218.207.172.236:80");
        proxies.Add("218.59.144.120:81");
        proxies.Add("218.59.144.95:80");
        proxies.Add("218.28.35.234:8080");
        proxies.Add("222.88.236.236:83");
        Random rnd = new Random();
        foreach (string url in urlList)
        {
            int ind = rnd.Next(0, proxies.Count-1);
            var httpClientHandler = new HttpClientHandler
                    {
                        Proxy = new WebProxy(proxies.ElementAt(ind), false),
                        UseProxy = true
                    };
            HttpClient httpClient = new HttpClient(httpClientHandler);
            //HttpClient httpClient = new HttpClient();

            httpClient.Timeout = TimeSpan.FromMinutes(2);
            ProcessUrlAsyncWithHttp(httpClient, url);
        }
    }

问题是: 1)为什么每个请求都会关闭 TCP 端口。我想打开端口的最大连接数并在调用中重用它们。例如,在上面的示例中,我可以有 10 个并发连接。因此,我希望它打开 10 个 TCP 端口,然后其余 40 个请求可以串联使用这 10 个端口。这是 httpwebrequest 中预期的正常行为。我有一个使用 httpwebrequest 的工作代码,它描述了这种重用端口的行为。可以根据需要为任何想要查看的人发布该代码。因此,尽管它基于 httpwebrequest,但 httpclient 并没有模仿这种行为,这有点奇怪。

2) 对于此类调用,我们如何将 autoredirect 分配为 false? 3)我打算将此功能用于多个呼叫 - 说大约 50K。代码编写方式中可能需要更正的任何错误 4) 假设我以某种方式设法使用单个 httpclient 对象而不是每个请求一个对象。有什么方法可以确保我读取所有这些单独请求的 cookie 并在必要时更改它们,同时记住我有一个用于整个 URL 请求集的 httpclient 类?

Tks 卡洛尔

【问题讨论】:

  • 连接重用不依赖于 HttpClient 重用。每次只需创建一个新客户。连接使用标准 .NET 池基础结构进行池化。
  • 是的,我明白这一点。但是使用 httpwebrequest/response 编写了一个类似的代码段,第一次打开的 TCP 端口将用于以后的请求。所以问题是为什么使用类似的 httpclient 代码不会使用端口(当我们知道它在内部使用 httpwebrequest/response 时)

标签: asynchronous dotnet-httpclient


【解决方案1】:

根据我的经验(我曾经遇到过类似的 TCP 端口拥塞问题,因为端口总是关闭,当我访问一个每分钟大约 6000 个连接的服务器时)重用 HttpClientHandler 对象就足够了,它实际上管理着连接池,并始终为每个请求重新创建 HttpClient 对象(使用带有 HttpClientManager 参数的构造函数)。

希望这会有所帮助。

马蒂亚斯

【讨论】:

    【解决方案2】:

    您是否尝试过将 HttpClient 代码放入类中并创建 10 个类,每个类都有一个 HttpClient?

    【讨论】:

      猜你喜欢
      • 2012-02-12
      • 1970-01-01
      • 2014-06-25
      • 1970-01-01
      • 1970-01-01
      • 2011-09-17
      • 2016-06-06
      • 2013-01-23
      • 2010-12-26
      相关资源
      最近更新 更多