多线程分块下载中文件的组织形式

在多线程下载工具中,对于文件的处理方式,大体分为两种:
  1. 每个线程分别保存自己的块为单独文件,下载成功后,再合并成一个文件。prozilla采用的就是这种方式。
  2. 各个程序共享一个文件,通过lseek类的系统调用将文件指针拉到指定为止,然后直接填充。如我写的myget。
可以发现,方法2要比1少一个文件合并的过程,当然效率也会较前者高了。记得当时用prozilla比较难以忍受的就是相当地弱智,即使一个文件只分了一个线程下载,也就是说并没有分块,它也要合并!虽然,这个问题很容易修正,但终因为它的下载后合并的方式浪费时间和系统资源而被我放弃。

对于方法2,各个实现方式也存在略微的差别,在linux平台下,文件系统一般是允许将读写指针拉到大于文件大小的位置的,其中的“空白”部分被形象地称为“空洞”;而在windows平台下,据说这样的操作是不允许的(如果我错了,请更正我!),所以flashget采用了得到文件大小后,把当前文件用“0”填充到最终大小,进而seek指针,这个变通的方法。不过flashget的方法虽然在文件填充的时候同样和方法1差不多地浪费系统资源,但是它却能带来另外一个“副作用”,在文件下载前就能探测到当前文件系统是否用充足的空间可用,不过话在说回来,这个代价可是相当大的。其实,得到当前文件系统的空闲大小,有更加廉价的方法,比如Linux下的statfs和fstatfs系统调用,但是,因为它只是统计下载开始时的文件系统剩余量,如果有其他进程在同时操作文件系统,那么他们之间就可能产生竞争,还好,文件系统空间不够用的情况并不是很多。

如果想支持“断点续传”,还需要再对下载中断时的临时文件格式进行分析。在1的实现方式中,一切似乎都比较容易,得到块文件的大小也就得到了文件已经下载了多少的信息;在方法2中,我们不得不费番周折,如果把下载的上下文信息保存到另外一个文件,这无疑会令用户迷惑,所以很少下载软件采用这种方式,那么如何在一个文件系统中保存这些信息呢?

在0.1.x系列的myget中,我采用的是在文件的末尾处(这里指的是文件下载完成后的末尾位置)保存这个信息。所以,为了准确地得到这个信息,我们就不得不先从服务器得到文件的实际大小,这直接导致了我们在临时文件中保存URL是徒劳的,用户不得不在其他的地方记住URL。

那么,我为什么不把信息保存在文件的头部呢?起初还是因为“合并”的问题,这里似乎用合并不太合理,就是说如何从临时文件高效地变成目标文件的问题。如果保存在末尾,我们可用简单地用truncate/ftruncate系统调用截断文件;如果保存在头部,我们就没有类似的系统调用可用了,不知道从哪里听说过Reiserfs的作者提议过truncate文件头部和从文件中任意穿插进新内容的功能,但不知道结果。所以,我们只能另寻出路了。

早晨,灵机一动,计上心来!我们可以将上下文信息在文件头部所占用的空间内的数据,保存到某个地方,然后在truncate之前把这个东西再次拷贝回来不就行了么?进一步地演变成了如下的格式:

/*
 * +--------+---------+----------+-------------------------------+---------+
 * | offset | moveLen | data ... | orignal head data (<=9bytes) | padding |
 * ---------+---------+----------+-------------------------------+---------+
 */

其中, offset是一个64bit的无符号整形,保存真正的padding信息在这个文件中的偏移量; moveLen表示从文件头部搬移到紧挨着padding信息之前的数据量,一般其值为9,除非原始文件大小小于9。其他部分也就不言自明了。

上面提到的格式,应该会在myget的下个版本采用,不过下个版本真的遥遥无期啊!
 

你可能感兴趣的:(多线程,windows,linux,平台,64bit,下载工具)