上篇博客是对从 Teahour.FM 下载 podcast 的简单记录,其中提到了那个下载程序支持某些「高级」特性:多线程、断点续传、超时重传。其实,这些特性大家也经常会听到,因为很多专业的下载工具都支持。
如何实现这些特性呢?技术上并不复杂。接下来,我就抛砖引玉,写一下自己的认识。如果有写得不对或不准确的地方,请指正。
需要注意的是:
- 下面的讨论仅适用于 HTTP 协议的文件下载,其它协议不在讨论之列。
- 下面的讨论重原理轻实现,你可以选择用自己熟悉的编程语言去实现这些特性。我用的是 Python 3(主要用
urllib
和threading
两个模块),实现起来比较简单。完整的代码在此。
下面,分别讨论这几个特性:
- 多线程
首先需要说明的是,这里的「多线程」是指用多个线程下载同一个文件,而不是说有多个线程、然后每个线程分别负责下载一个文件。
多线程能够显著提高文件下载速度。如何实现多线程下载文件呢?原理很简单,就是每个线程只负责下载文件的某一部分,待文件的所有部分都下载完成后,将文件的各个部分拼接起来,就是最终要下载的目标文件了。其中会利用到 HTTP response header 的 Content-Length 字段和 HTTP request header 的 Range 字段。
完整过程如下:
- 对目标 URL 发出 HTTP 请求。根据 HTTP response header 的 Content-Length 字段,得到要下载的目标文件大小 N。
- 根据线程数(自己视实际情况而定)和文件大小,划分各个线程的下载任务,各个线程的下载任务量最好接近。比如,如果有 3 个线程,则 3 个线程的任务分配大致分别为:[0, (1/3)*N], [(1/3)*N, (2/3)*N], [(2/3)*N, N]。
- 分配线程,各线程根据已分配的下载任务量。根据 HTTP request header 的 Range 字段,可以指定请求实体的子范围,也就是只下载文件的一部分。
- 各线程都下载完成后,将文件的各部分拼接起来,得到要下载的完整的目标文件,再将已下载的文件各部分删除。
关于 HTTP request header 的 Range 字段,可以参考这个链接。
- 断点续传
断点续传的原理比较简单:先得到已下载部分的大小,利用 HTTP request header 的 Range 字段,接着继续下载未下载的部分即可。
文件的断点续传与多线程本身没有关系,无论单/多线程下载,都可以使用断点续传技术。但是如果是多线程环境下的断点续传,需要保证前后两次下载的线程任务分配方案完全一致。
- 超时重传
超时重传也容易理解:对于发出的 HTTP 请求,设置一个 timeout 超时参数。如果发出的 HTTP 请求超时(在 timeout 时间内仍然没有建立连接),则重新发出 HTTP 请求。
最后,如何验证采用了这些「高级」特性下载下来的文件是正确的呢?
其实就是与以普通方式下载下来的文件做比较,比较两种不同方式下载下来的文件是否相同了。
Linux 下可以使用的工具有:diff
、cmp
以及一些 checksum 工具(md5sum
, sha1sum
等)。具体可以参考 StackOverflow 的这个链接。
另外,Python 自身也提供了一个用于文件比较的模块 filecmp
,可以用来比较两个文件是否相同。
参考链接: