也谈文件的多线程下载

来源:互联网 发布:医药b2b平台源码下载 编辑:程序博客网 时间:2024/04/29 19:55

如果是一个线程下载整个文件,我想,这谁都能够轻易的写出代码,但如果要求使用多线程进行下载,恐怕相当一部分人就无从下手了。所谓多线程,无非是每个线程只下载文件的一部分,最后将各个部分合并成一个文件。所以我们只要能解决以下几个问题就好了:

         1、 获得文件的整体大小

         2、 每个线程分配一个下载范围

         3、 每个线程按照各自的下载范围进行下载

         4、 最后合并各个部分


         我们就按照上面的过程逐一进行拆解。首先,获得文件的大小,我查了一些网上流传的帖子,获得文件大小的方式都是直接向服务器发送一个get请求,然后根据响应头获得文件的大小。这里有一个小问题,那就是这个get请求发出去后,服务器发回来的可不只是响应头,还有文件流。但这一次请求根本不会去处理这个文件流,在我看来这是一种浪费,因为我们本可以用更好的办法,head请求。服务器收到head请求后,返回的只是响应头,文件实体部分不会返回,这样,就减少了不必要的浪费。

         

         获得了文件的大小后,就可以轻松的进行分配了,比如有三个线程,那么就平均分配一下好了,比如文件大小为3000个字节,那么每个线程就下载1000个字节好了,如果是3002个字节,那剩余的两个字节分配给最后一个线程,三个线程的下载分配是1000,1000,1002。

 

         前两步都完成了,最关键的是第三步,我想,这是很多人都表示为难的地方。其实呢,只要稍微对http协议了解一点的同学就能想到可以向服务器发起对文件的分段请求。比如,我就请求这个文件的第1000到第2000个字节的请求,服务器在收到请求后也会只发送文件的这一段。这里以python为例:

         

range = 'bytes=%s-%s' % (self.startindex,self.end)        headers = {'Range':range}        req = urllib2.Request(self.url, None,headers)

          发出去的请求,明确的要求了请求文件数据的范围


         最后一步合并,只需要等待3个线程运行结束就好了,我这里使用一个全局的Queue,每个线程完成下载后就往Queue里put一个数值,当Queue里的数值个数等于线程数时就是所有线程运行结束的时候

        全部代码如下


        

#coding=utf-8'''Created on 2015-10-12先向服务器发送head请求,获得到文件的大小,然后起多个线程进行下载,最后合并@author: kwsy2015'''import Queueimport timeimport threadingimport urllib2import datetimeimport osq=Queue.Queue()class MyDownLoad(threading.Thread):    def __init__(self,url,start,end,id,dir,filename):        self.url = url        self.startindex = start        self.end = end        self.id = id         self.dir = dir        self.filename = filename        threading.Thread.__init__(self,name="producer Thread-%d" % id)    def run(self):                global q        range = 'bytes=%s-%s' % (self.startindex,self.end)        headers = {'Range':range}        req = urllib2.Request(self.url, None,headers)        f = urllib2.urlopen(req)         self.filename = self.dir + self.filename        self.filename = unicode(self.filename,'utf-8')        with open(self.filename, "wb") as code:             while True:                  data = f.read(1024)                  if not data:                    break                code.write(data)        q.put(1)def GetFileSize(url):    request = urllib2.Request(url)    request.get_method = lambda : 'HEAD'    response = urllib2.urlopen(request)    return int(response.info()['Content-Length'])def DownLoadFile(url,threadCount,dir):    if threadCount<2:        threadCount = 2    filename = url.split("/")[-1]    filesize = GetFileSize(url)        blocksize = filesize/threadCount    for i in range(threadCount):        start = i*blocksize        if i is threadCount-1:            end = filesize-1        else:             end = (i+1)*blocksize-1        id = i        tmpfilename = '%s-%d' % (filename,i)        print tmpfilename        dlthread = MyDownLoad(url,start,end,id,dir,tmpfilename)        dlthread.start()def Merge(dir,name):    dir = unicode(dir,'utf-8')        lst = []    for parent,dirnames,filenames in os.walk(dir):        for filename in filenames:            fullname = os.path.join(parent,filename)            fullname = fullname.encode("utf-8")             lst.append(fullname)    file = open(os.path.join(dir,name),'wb')    filename = lst[1].split("-")[-2]    count = len(lst)    for i in range(count-1):        mergefile = '%s-%d' % (filename,i)        mergefile = unicode(mergefile,'utf-8')        print mergefile        mfile = open(mergefile,'rb')        while True:            data = mfile.read(1024)            if not data:                break            file.write(data)        mfile.close()    file.close()    if __name__ == '__main__':    starttime = datetime.datetime.now()    DownLoadFile('http://mn2.pc6.com/rm/python2.7.zip',3,'f:/测试/')    while not q.qsize() is 3:        pass        Merge('f:/测试/','python2.7.zip')    endtime = datetime.datetime.now()    print (endtime - starttime).seconds

0 0
原创粉丝点击