优化多个网页的下载。 C#

时间:2011-05-19 16:53:22

标签: c# http optimization

我正在开发一个应用程序,我需要下载一堆网页,最好尽快。我现在这样做的方式是我有多个线程(100)拥有自己的System.Net.HttpWebRequest。这种作品,但我没有得到我想要的表现。目前我有600+ Mb / s的强大连接,而且最多只能使用10%(达到峰值)。我想我的策略存在缺陷,但我无法找到其他任何好办法。

另外:如果使用HttpWebRequest不是下载网页的好方法,请说出来:) 该代码已从java进行半自动转换。

谢谢:)

更新

public String getPage(String link){
   myURL = new System.Uri(link);
   myHttpConn = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(myURL);
   myStreamReader = new System.IO.StreamReader(new System.IO.StreamReader(myHttpConn.GetResponse().GetResponseStream(),
            System.Text.Encoding.Default).BaseStream,
                new System.IO.StreamReader(myHttpConn.GetResponse().GetResponseStream(),
                    System.Text.Encoding.Default).CurrentEncoding);

        System.Text.StringBuilder buffer = new System.Text.StringBuilder();

        //myLineBuff is a String
        while ((myLineBuff = myStreamReader.ReadLine()) != null)
        {
            buffer.Append(myLineBuff);
        }
   return buffer.toString();
}

3 个答案:

答案 0 :(得分:2)

一个问题是,您似乎每次发出两次请求:

myStreamReader = new System.IO.StreamReader(
    new System.IO.StreamReader(
        myHttpConn.GetResponse().GetResponseStream(),
        System.Text.Encoding.Default).BaseStream,
             new System.IO.StreamReader(myHttpConn.GetResponse().GetResponseStream(),
                 System.Text.Encoding.Default).CurrentEncoding);

GetResponse进行两次调用。由于我无法理解的原因,您还创建了两个流读取器。您可以将其拆分并简化它,并且还可以更好地处理错误...

var response = (HttpWebResponse)myHttpCon.GetResponse();
myStreamReader = new StreamReader(response.GetResponseStream(), Encoding.Default)

这应该会使您的有效吞吐量增加一倍。

此外,您可能希望确保处理您正在使用的对象。当您下载大量页面时,如果您不自行清理,则可以快速耗尽资源。在这种情况下,您应该致电response.Close()。见http://msdn.microsoft.com/en-us/library/system.net.httpwebresponse.close.aspx

答案 1 :(得分:2)

我将此答案添加为人们在

时可能会遇到的另一种可能性
  • 使用多线程应用从多个服务器下载
  • 使用Windows XP或Vista作为操作系统

这些操作系统的tcpip.sys驱动程序每秒限制为10个出站连接。这是速率限制,而不是连接限制,因此您可以拥有数百个连接,但不能启动超过10 / s。微软限制了这种限制,以限制某些类型的病毒/蠕虫的传播。这些方法是否有效超出了这个答案的范围。

在从多个服务器下载的多线程应用程序中,此限制可能表现为一系列超时。一旦达到10 / s限制,Windows将所有“半开”(新打开但尚未建立)的连接放入队列。例如,在我的应用程序中,我有20个线程准备好处理连接,但我发现有时我会从我知道正在运行和可访问的服务器上获得超时。

要验证是否发生这种情况,请检查系统下的操作系统事件日志。错误是:

  

EventID 4226: TCP/IP has reached the security limit imposed on the number of concurrent TCP connect attempts.

有许多对此错误的引用以及适用于删除限制的大量修补程序和修补程序。然而,因为P2P(Torrent)用户经常遇到这个问题,所以伪装成这个补丁的恶意软件数量相当多。

我需要每隔5分钟从超过1200台服务器(实际上是数据传感器)收集数据。我最初开发了应用程序(在WinXP上),重复使用20个线程来抓取服务器列表并将数据聚合到SQL数据库中。因为连接是基于计时器滴答事件启动的,所以这个错误经常发生,因为在它们的调用时,没有建立任何连接,因此10个会立即排队。

请注意,这不是必然的问题,因为当建立连接时,会排队那些排队。但是如果非排队连接建立起来很慢,那么这个时间会对超时产生负面影响排队连接的限制(根据我的经验)。查看我的应用程序日志文件的结果是,我会看到一批超时的连接,其次是大多数成功的连接。打开Web浏览器来测试“超时”连接令人困惑,因为服务器可用并且响应迅速。

我决定尝试HEX编辑tcpip.sys文件,这是a guide at speedguide.net上建议的。我的文件的校验和与指南不同(我有SP3而不是SP2),指南中的注释不一定有用。但是,我确实找到了a patch that worked for SP3并注意到应用后的立即差异。

根据我的发现,Windows 7没有此限制,并且自从将应用程序移动到基于Windows 7的计算机后,仍然没有超时问题。

答案 2 :(得分:1)

我做同样的事情,但有成千上万的传感器提供XML和文本内容。肯定会影响性能的因素不仅限于您的带宽和计算机的速度和功率,还包括您所联系的每台服务器的带宽和响应时间,超时延迟,每次下载的大小,以及远程互联网连接的可靠性。

正如评论所指出的,数百个线程不一定是个好主意。目前我发现一次运行20到50个线程似乎是最佳选择。在我的技术中,当每个线程完成下载时,它将从队列中获得下一个项目。

我在一个单独的线程上运行一个自定义ThreaderEngine类,该线程负责维护工作项的队列并根据需要分配线程。本质上,它是一个循环遍历线程数组的while循环。当线程完成时,它从队列中抓取下一个项目并再次启动线程。

我的每个线程实际上都是下载了几个单独的项目,但方法调用是相同的(。NET 4.0)

public static string FileDownload(string _ip, int _port, string _file, int Timeout, int ReadWriteTimeout, NetworkCredential _cred = null)
{
    string uri = String.Format("http://{0}:{1}/{2}", _ip, _port, _file);
    string Data = String.Empty;
    try
    {
        HttpWebRequest Request = (HttpWebRequest)WebRequest.Create(uri);
        if (_cred != null) Request.Credentials = _cred;
        Request.Timeout = Timeout; // applies to .GetResponse()
        Request.ReadWriteTimeout = ReadWriteTimeout; // applies to .GetResponseStream()
        Request.Proxy = null;
        Request.CachePolicy = new System.Net.Cache.RequestCachePolicy(System.Net.Cache.RequestCacheLevel.NoCacheNoStore);
        using (HttpWebResponse Response = (HttpWebResponse)Request.GetResponse())
        {
            using (Stream dataStream = Response.GetResponseStream())
            {
                if (dataStream != null)
                    using (BufferedStream buffer = new BufferedStream(dataStream))
                    using (StreamReader reader = new StreamReader(buffer))
                    {
                        Data = reader.ReadToEnd();
                    }
            }
            return Data;
        }
    }
    catch (AccessViolationException ave)
    {
        // ...
    }
    catch (Exception exc)
    {
        // ...
    }
}

使用此功能,我可以在不到5分钟的时间内从1200多台远程计算机(72MB)下载大约60KB。该机器是Core 2 Quad,配备2GB RAM,采用四个绑定的T1连接(~6Mbps)。