65.9K
CodeProject 正在变化。 阅读更多。
Home

MyDownloader:一个多线程 C# 分段下载管理器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (408投票s)

2007年10月26日

CPOL

10分钟阅读

viewsIcon

2259436

downloadIcon

125056

管理多个分段下载并支持 HTTP、FTP 和 YouTube 视频下载的示例应用程序

Screenshot - MyDownloader1.png

引言

MyDownloader 是一款用 C# 编写的开源应用程序,几乎是一个完整的下载管理器。MyDownloader 具有许多管理下载的特性

  • 从 HTTP 和 FTP 进行分段下载
    • 智能分段:当一个分段下载完成后,启动另一个分段以帮助更快地完成另一个分段
    • 当分段或下载失败时自动重试
  • 允许暂停和恢复下载
  • 视频下载
    • 支持从以下网站下载视频
      • YouTube
      • Google Video
      • Break
      • PutFile
      • Meta Cafe
    • (新增) 支持将下载的视频转换为 MPEG、AVI 和 MP3(使用 ffmpeg)
    • (新增) 基于视频标题的视频文件名建议
  • 速度限制 — 避免占用所有带宽
  • 支持自动下载
    • (新增) 在特定时间限制带宽
    • (新增) 启动时启用“自动下载”的可能性,允许下载在应用程序启动时自动开始
    • 仅在允许的时间下载文件
    • 限制同时下载的数量
    • 当一个下载完成时,自动开始另一个下载
  • 支持需要身份验证的 FTP 站点
  • 支持镜像服务器
  • 从 HTTPS 下载
  • (新增) 从经过身份验证的 HTTP URL 下载
  • 使用声音和 XP 气球提示通知下载完成
  • 防病毒集成
  • 批量下载(输入通用 URL,如http://server/file(*).zipMyDownloader 会生成一组带数字或字母的 URL)
  • (新增) 上移/下移按钮,用于更改下载队列中的下载顺序
  • (新增) 错误修复和改进
  • (新增) 网页爬虫(Web Spider)
    • (新增) 从特定页面下载所有文件
    • (新增) 从特定页面下载所有图片
    • (新增) 允许按扩展名或名称过滤 URL
  • (新增) 支持将下载的视频转换为 MPEG、AVI 和 MP3(使用 ffmpeg)
  • (新增) 基于视频标题的视频文件名建议
  • (新增) 剪贴板监视器
  • (新增) Internet Explorer 集成
    • 当用户按住 ALT 键点击链接时下载链接
    • (新增) 在浏览视频网站(YouTube、Google Video 等)时,启用视频按钮,使用 MyDownloader 下载视频
    • (新增) 启动 MyDownloader 的按钮
  • (新增) 从文件导入 URL
    • (新增) 从本地文本文件
    • (新增) 从本地 HTML 文件

分段下载的工作原理

下载之所以可以分段,是因为 HTTP 和 FTP 协议都允许客户端指定流的起始位置。首先,MyDownloader向服务器发出请求以确定文件大小。然后,MyDownloader 按如下方式计算分段大小

segment size = min( (file size / number of segments), 
    minimum allowed segment size )

使用分段大小,MyDownloader 会创建一个新的请求,指定流的起始位置。这样,我们可以使用多线程技术并行地对同一文件进行多次请求。如果使用镜像服务器,此技术可以进一步提高传输速率。

使用代码:MyDownloader API

使用MyDownloader API 开始分段下载非常简单。请查看下面从MyDownloader 源代码中提取的代码。下载完成后,将在 Windows 时钟附近显示一个 XP 气球提示

MyDownloader2.png
// starts to listen to the event 'DownloadEnded' from DownloadManager
DownloadManager.Instance.DownloadEnded += 

new EventHandler<DownloaderEventArgs>(Instance_DownloadEnded);

// indicates that download should start immediately
bool startNow = true;

Downloader download = DownloadManager.Instance.Add(
    "http://jogos.download.uol.com.br/videos/pc/thewitcher12.wmv",
    @"c:\temp\thewitcher12.wmv",
    3,          // Three segments 

    startNow    // Start download now
    );  

void Instance_DownloadEnded(object sender, DownloaderEventArgs e)
{
    if (Settings.Default.ShowBallon && 
    AppManager.Instance.Application.NotifyIcon.Visible)
    {
        // Display the XP Balloon 

  }
finally
{
    DownloadManager.Instance.OnEndAddBatchDownloads();
}
      AppManager.Instance.Application.NotifyIcon.ShowBalloonTip(
            Settings.Default.BallonTimeout,
            AppManager.Instance.Application.MainForm.Text,
            String.Format("Download finished: {0}", e.Downloader.LocalFile),
            ToolTipIcon.Info);
     }
}

协议抽象

在 MyDownloader 的早期版本中,协议支持是通过继承Downloader的类实现的。这是因为早期版本不支持镜像服务器,当时一个下载只能来自一个源。但现在,随着镜像服务器功能的加入,我们可以从 HTTP 服务器下载一个分段,从 FTP 服务器下载另一个分段。

因此,我对代码进行了重构,现在所有支持的协议(HTTP、FTP、HTTPS)都通过实现IProtocolProvider的类来实现。IProtocolProvider的具体实例由ProtocolProviderFactory创建,协议提供商类在与Downloader类不同的类层次结构中实现。这样做是为了解决使用单一协议进行下载的限制。

为了更容易检索正确的IProtocolProviderResourceLocation类有一个工厂方法。此方法由Downloader类使用。

MyDownloader6.png

插件架构

MyDownloader 的许多功能都是通过可扩展性概念实现的。因为 MyDownloader 中最重要的类提供了许多事件,所以扩展可以监听这些事件来更改应用程序的行为。另一个优点是每个扩展都有自己的设置。因此,Options 对话框需要根据扩展来创建。如果您在设计时打开 Options,您只会看到一个空的 Panel。

MyDownloader3.png

下面,您可以查看我们如何从扩展加载设置以填充树视图

for (int i = 0; i < App.Instance.Extensions.Count; i++)
{
    IExtension extension = App.Instance.Extensions[i];
    IUIExtension uiExtension = extension.UIExtension;
    
    Control[] options = uiExtension.CreateSettingsView();
    
    TreeNode node = new TreeNode(extension.Name);
    node.Tag = extension;

    for (int j = 0; j < options.Length; j++)
    {
        TreeNode optioNd = new TreeNode(options[j].Text);
        optioNd.Tag = options[j];
        node.Nodes.Add(optioNd);
    }

    treeOptions.Nodes.Add(node);
}

我在本文开头展示的DownloadManager 也不知道 HTTP 或 FTP。DownloadManager 接受在ProtocolProviderFactory中注册的协议,HTTP 和 FTP 协议由一个扩展进行注册。请查看 HTTP/FTP 下载扩展

public class HttpFtpProtocolExtension: IExtension
{
    #region IExtension Members

    public string Name
    {
        get { return "HTTP/FTP"; }
    }

    public IUIExtension UIExtension
    {
        get { return new HttpFtpProtocolUIExtension(); }
    }

    public HttpFtpProtocolExtension()
    {
        ProtocolProviderFactory.RegisterProtocolHandler("http", 
            typeof(HttpProtocolProvider));
        ProtocolProviderFactory.RegisterProtocolHandler("https", 
            typeof(HttpProtocolProvider));
        ProtocolProviderFactory.RegisterProtocolHandler("ftp", 
            typeof(FtpProtocolProvider));
    }

    #endregion

}

当想到 HTTP 下载时,HTTP 下载器需要哪些设置?代理是其中一个答案。许多用户都处于 HTTP 代理后面,在大多数公司中不允许直接连接到 HTTP 服务器。

因此,要公开我们HttpFtpProtocolExtension的设置,我们需要创建一个IUIExtension并通过IExtensionUIExtension属性返回它。在这个类中,我们实现CreateSettingsView方法,该方法返回将在 Options 对话框中显示的所有设置。

public class HttpFtpProtocolUIExtension : IUIExtension
{
    public System.Windows.Forms.Control[] CreateSettingsView()
    {
        // create the Proxy user control an return it.

        return new Control[] { new Proxy() };
    }

    public void PersistSettings(System.Windows.Forms.Control[] settingsView)
    {
        ... 
    }
    
    ...
}

HttpFtpProtocolUIExtension 类提供了一个名为CreateSettingsView的工厂方法。该方法创建一个控件数组,这些控件是扩展设置的可视化。Options 对话框使用此数组来填充选项的TreeView并在右侧面板中显示设置。

网页爬虫

网页爬虫在MyDownloader API 上运行,爬虫的唯一技巧是使用正则表达式解析 HTML 页面。下面我们可以看到网页爬虫的截图

MyDownloader8.png

当文件下载完成(下载状态变为 DownloaderState.Ended)时,爬虫会检查它是否是 HTML 文档(通过比较 MIME 类型),然后查找所有引用的超链接、图片、框架和 iframe。执行以下代码将所有页面引用添加到下载列表中

...
if (download.RemoteFileInfo.MimeType.IndexOf("text/html",
    StringComparison.OrdinalIgnoreCase) < 0)
{
    return;
}
...
try
{
    DownloadManager.Instance.OnBeginAddBatchDownloads();

    using (Stream htmlStream = File.OpenRead(localFile))
    {
        using (HtmlParser parser = new HtmlParser(htmlStream))
        {
            AddUrls(parser.GetHrefs(context.BaseLocation), UrlType.Href);
            AddUrls(parser.GetImages(context.BaseLocation), UrlType.Img);
            AddUrls(parser.GetFrames(context.BaseLocation), UrlType.Frame);
            AddUrls(parser.GetIFrames(context.BaseLocation), UrlType.IFrame);
        }
    }
}
finally
{
    DownloadManager.Instance.OnEndAddBatchDownloads();
}

从 YouTube、Google Video(等)下载视频并进行转换

与许多MyDownloader功能一样,视频下载只是另一个扩展。诀窍在于VideoDownloadExtension和“新建视频下载”窗口。MyDownloader 中的所有 URL 都由ResourceLocation类表示 — 这个类有一个GetProtocolProvider方法,该方法返回IProtocolProvider接口的适当实例 — 我们需要做的(在“新建视频下载”中)就是强制正确的协议提供商类型,方法是设置ResourceLocationProtocolProviderType属性。

设置此属性后,当ResourceLocation类调用GetProtocolProvider时,创建的协议提供商将是存储在ProtocolProviderType中的类型,而不是在ProtocolProviderFactory中注册的提供商。这样,我们可以替换默认的协议提供商,并避免保存 HTML 内容,强制从网站下载视频。

第一步是在VideoDownloadExtension中注册视频协议提供商

public VideoDownloadExtension()
{
   handlers = new List<VideoDownloadHandler>();
   handlers.Add(new VideoDownloadHandler(YouTubeDownloader.SiteName, 
           YouTubeDownloader.UrlPattern, typeof(YouTubeDownloader)));
   handlers.Add(new VideoDownloadHandler(GoogleVideoDownloader.SiteName, 
           GoogleVideoDownloader.UrlPattern, typeof(GoogleVideoDownloader)));
   // ... register other sites here ...


}

注册后,我们需要发现需要使用哪个视频处理程序,并设置ResourceLocationProtocolProviderType属性为正确的协议提供商。这在“新建视频下载”窗口中完成,请看下面

MyDownloader4.png
VideoDownloadExtension extension;
...
extension = (VideoDownloadExtension)App.Instance.GetExtensionByType(
    typeof(VideoDownloadExtension));
...
handler = extension.GetHandlerByURL(txtURL.Text);
...
ResourceLocation rl = ResourceLocation.FromURL(txtURL.Text);
rl.ProtocolProviderType = handler.Type.AssemblyQualifiedName;

基本上,所有视频网站处理程序只需要解析 HTML 页面并返回 FLV 的 URL。这个过程有三个主要步骤

  • 从视频网站下载 HTML 页面
  • 解析 HTML 以查找视频 URL
  • 返回视频 URL

所有常见功能都在BaseVideoDownloader类中。该类检索 HTML 并开始下载视频。继承的类(YouTubeDownloaderGoogleVideoDownloader)负责解析 HTML 文本并将视频 URL 返回给基类。下面我们可以看到如何从 YouTube 页面上的 FLV 文件获取 URL

public class YouTubeDownloader: BaseVideoDownloader
{
   public const string SiteName = "You Tube";

   //http://www.youtube.com/watch?v=5zOevLN3Tic


   public const string UrlPattern = 
      @"(?:[Yy][Oo][Uu][Tt][Uu][Ee]\.[Cc][Oo][Mm]/watch\?v=)(\w[\w|-]*)";

   protected override ResourceLocation ResolveVideoURL(string url, string pageData, 
         out string videoTitle)
   {
      videoTitle = TextUtil.JustAfter(pageData,
          "< meta name=\"title\" content=\"", "\">"); 
      
      return ResourceLocation.FromURL(String.Format("{0}/get_video?video_id={1}&t={2}", 
       TextUtil.GetDomain(url), TextUtil.JustAfter(url, "v=", "&"), 
       TextUtil.JustAfter(pageData, "&t=", "&hl=")));
   }
}

下载后,视频可以转换为 MPEG、AVI 或 MP3(仅音频),这个过程使用一个外部开源工具:ffmpeg。MyDownloader 使用 FLV 文件名和转换参数调用这个命令行工具。如果您想查看发送到 ffmpeg 的参数的详细信息,我建议您下载本文的代码/演示项目。

选择远程 ZIP 文件中的文件

这是 MyDownloder 的另一个非常酷的功能。有时,您需要下载一个大的 ZIP 文件,只是因为您想要 ZIP 中的单个文件,在“新建下载”窗口中,如果用户勾选“选择 ZIP 中的文件”选项,MyDownloader 将枚举 ZIP 中的文件,并允许用户仅选择我们想要下载的文件。

该功能基于文章从远程 ZIP 存档中提取文件以及Unruled Boy(请参阅文章末尾的评论)的更新版本。下面我们可以看到“新建下载”窗口如何显示 ZIP 文件并允许用户选择 ZIP 中的文件

MyDownloader7.png

自动下载

自动下载通过MyDownloader 工具栏上的“双箭头”按钮激活(或 deaktiviert)。启用此功能后,MyDownloader 开始像批量下载器一样工作,完成下载队列中的每个下载。

下载的最大数量在“Options”对话框中配置。另一个优点是用户可以选择“自动下载”的工作时间,并且还可以选择在特定时间限制带宽使用。这可以通过选择“时间网格”轻松完成

MyDownloader5.png

自动下载是通过DownloadManager的事件(DownloadAdded、DownloadEnded)实现的。当这些事件中的任何一个被引发时,扩展将开始下载,并遵守同时下载的最大数量

using (DownloadManager.Instance.LockDownloadList(false))
{
   int count = GetActiveJobsCount();
   int maxJobs = Settings.Default.MaxJobs;

   if (count < maxJobs)
   {
      for (int i = 0; 
         i < DownloadManager.Instance.Downloads.Count && (count < maxJobs); 
         i++)
      {
         if (DownloadManager.Instance.Downloads[i].State != 
            DownloaderState.Ended &&

            ! DownloadManager.Instance.Downloads[i].IsWorking())
         {
            DownloadManager.Instance.Downloads[i].Start();
            count ++;
         }
      }
   }
}

Internet Explorer 集成

浏览器集成是任何下载管理器的关键功能。这个新版本的MyDownloader 引入了一个非常简单的 Internet Explorer (IE) 集成。IE 集成是一个 IE 工具栏,它基于BandObjectLib构建,具有三个主要功能

  • 当用户浏览允许下载视频的视频网站时,将启用快捷按钮,用户可以下载该视频
  • 当用户按住 Alt 键时,替换 IE 下载窗口
  • 启动 MyDownloader 的快捷方式

下面我们可以看到 IE 显示一个空页面,并且下载按钮被禁用,第二个图像显示 IE 显示一个 YouTube 视频,下载按钮变为启用状态

MyDownloader10.png

要启用视频下载按钮,我们需要监听 IE 的AfterNavigate事件,然后检查 LocationURL 属性是否来自视频网站的 URL

void AfterNavigate(object iDisp, ref object URL)
{
   SHDocVw.WebBrowser IEDocument = GetIEDocument();            

   btnDownload.Enabled = videoSites.IsVideoSite(IEDocument.LocationURL);
}

要替换 IE 下载窗口(仅在按下 Alt 键时),可以使用FileDownload事件

void FileDownload(bool ActiveDocument, ref bool Cancel)
{
   if (!ActiveDocument)
   {
      if ((Control.ModifierKeys & Keys.Alt) == Keys.Alt)
      {
         Cancel = true;

         if ((DateTime.Now - lastDownload).TotalSeconds >= 1.9)
         {
            ThreadPool.QueueUserWorkItem(
               delegate(object state)
               {
                  DownloadURL(lastUrl);
               });

            lastDownload = DateTime.Now;
         }
      }
   }
}

从文件导入 URL

MyDownloader 的另一个新功能是“从文件导入 URL”窗口,它允许用户从文本文件或 HTML 文件导入 URL。文本文件每行必须有一个 URL。对于 HTML,将使用与网页爬虫相同的 HTML 解析器提取 URL。

文件中找到的所有 URL 都将添加到下载列表中。“从文件导入 URL”窗口还有一个快捷方式可以启用“自动下载”,并设置同时下载的最大数量。

MyDownloader9.png

未来构想

这类项目是“无限的”,因此我在下面列出了一些未来实现的设想。作为任何开源项目,如果您愿意贡献,我们将非常高兴。

  • 下载时添加和删除分段
  • 屏幕保护程序运行时禁用速度限制的选项
  • 集成 FireFox 并改进 Internet Explorer 集成
  • 通过选择最快的镜像服务器来改进镜像功能
  • 支持 MMS 协议
  • 创建下载类别并允许为下载添加标签
  • XY 图表显示带宽使用情况
  • 下载完成后自动关机
  • 下载完成后挂断互联网连接
  • 支持Metalink
  • 视频下载
    • 创建一个与 IE 和 FF 集成的媒体监视器,允许用户从任何网站下载视频

希望您喜欢这些代码!如果您有任何问题或反馈,请随时与我联系。

© . All rights reserved.