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






4.95/5 (408投票s)
管理多个分段下载并支持 HTTP、FTP 和 YouTube 视频下载的示例应用程序

引言
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(*).zip,
MyDownloader
会生成一组带数字或字母的 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 气球提示

// 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
类不同的类层次结构中实现。这样做是为了解决使用单一协议进行下载的限制。
为了更容易检索正确的IProtocolProvider
,ResourceLocation
类有一个工厂方法。此方法由Downloader
类使用。

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

下面,您可以查看我们如何从扩展加载设置以填充树视图
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
并通过IExtension
的UIExtension
属性返回它。在这个类中,我们实现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 页面。下面我们可以看到网页爬虫的截图

当文件下载完成(下载状态变为 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
接口的适当实例 — 我们需要做的(在“新建视频下载”中)就是强制正确的协议提供商类型,方法是设置ResourceLocation
的ProtocolProviderType
属性。
设置此属性后,当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 ...
}
注册后,我们需要发现需要使用哪个视频处理程序,并设置ResourceLocation
的ProtocolProviderType
属性为正确的协议提供商。这在“新建视频下载”窗口中完成,请看下面

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 并开始下载视频。继承的类(YouTubeDownloader
、GoogleVideoDownloader
)负责解析 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 中的文件

自动下载
自动下载通过MyDownloader
工具栏上的“双箭头”按钮激活(或 deaktiviert)。启用此功能后,MyDownloader
开始像批量下载器一样工作,完成下载队列中的每个下载。
下载的最大数量在“Options”对话框中配置。另一个优点是用户可以选择“自动下载”的工作时间,并且还可以选择在特定时间限制带宽使用。这可以通过选择“时间网格”轻松完成

自动下载是通过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 视频,下载按钮变为启用状态

要启用视频下载按钮,我们需要监听 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”窗口还有一个快捷方式可以启用“自动下载”,并设置同时下载的最大数量。

未来构想
这类项目是“无限的”,因此我在下面列出了一些未来实现的设想。作为任何开源项目,如果您愿意贡献,我们将非常高兴。
- 下载时添加和删除分段
- 屏幕保护程序运行时禁用速度限制的选项
- 集成 FireFox 并改进 Internet Explorer 集成
- 通过选择最快的镜像服务器来改进镜像功能
- 支持 MMS 协议
- 创建下载类别并允许为下载添加标签
- XY 图表显示带宽使用情况
- 下载完成后自动关机
- 下载完成后挂断互联网连接
- 支持Metalink
- 视频下载
- 创建一个与 IE 和 FF 集成的媒体监视器,允许用户从任何网站下载视频
希望您喜欢这些代码!如果您有任何问题或反馈,请随时与我联系。