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