也谈文件的多线程下载

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

         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 Queue
import time
import threading
import urllib2
import datetime
import os
q=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
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

酷python

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值