大家都用过迅雷等下载工具,特点就是支持并发下载,断点续传。我们这里不介绍它,这个比较复杂了,逼人也不懂。本文只介绍狭义上的简易的断点续传和狭义上的多线程下载。跟之前一样,旨在研究原理,实际生活中基本没啥用,实测下来多线程下载比单线程下载还慢。。。太丢人了。

主要讲三个方面,如何HTTP的并发下载、通过Golang进行多协程开发、如何断点续传。

HTTP的并发下载

thunder://
  HTTP头   对应值   含义  
  Content-Length   14247   HTTP响应的Body大小,下载的时候,Body就是文件,也可以认为是文件大小,单位是比特  
  Content-Disposition   inline; filename=”bryce.jpg”   是MIME协议的扩展,MIME协议指示MIME用户代理如何显示附加的文件。当浏览器接收到头时,它会激活文件下载。这里还包含了文件名  
  Accept-Ranges   bytes   允许客户端以bytes的形式获取文件  
  Range   bytes=0-511   分块获取数据,这里表示获取第0到第511的数据,共512字节  

如果要下载一个文件,想知道这些文件的信息,例如文件名、文件大小、是否支持并发下载、文件类型都可以从响应的头里面获取。如何在下载前获得到这些内容而不是下载中获取,可以用HTTP提供的HEAD方法。HEAD方法只响应HTTP的头部分,不包含Body部分。

req, err := http.NewRequest("HEAD", get.Url, nil)
resp, err := get.GetClient.Do(req)
net/urlmime
get.ContentLength = int(resp.ContentLength)
get.MediaType, get.MediaParams, _ = mime.ParseMediaType(get.Header.Get("Content-Disposition"))
log.Printf("Get %s MediaType:%s, Filename:%s, Length %d.\n", get.Url, get.MediaType, get.MediaParams["filename"], get.ContentLength)

输出

2015/07/02 09:56:47 Get http://7b1h1l.com1.z0.glb.clouddn.com/bryce.jpg MediaType:inline, Filename:bryce.jpg, Length 14247.
Accept-Ranges
if get.Header.Get("Accept-Ranges") != "" {
	log.Printf("Server %s support Range by %s.\n", get.Header.Get("Server"), get.Header.Get("Accept-Ranges"))
} else {
	log.Printf("Server %s doesn't support Range.\n", get.Header.Get("Server"))
}
Range
range_i := fmt.Sprintf("%d-%d", get.DownloadRange[i][0], get.DownloadRange[i][1])
log.Printf("Download #%d bytes %s.\n", i, range_i)

defer get.TempFiles[i].Close()

req, err := http.NewRequest("GET", get.Url, nil)
req.Header.Set("Range", "bytes="+range_i)
resp, err := get.GetClient.Do(req)
defer resp.Body.Close()

最后将下载好的保持到文件里。这里是等这个块都下载完之后再写入硬盘,下载完之后都是保持在内存里面。

cnt, err := io.Copy(get.TempFiles[i], resp.Body)

多线程开发

channelsyncWaitGroup
AddWait
for i, _ := range get.DownloadRange {
	get.WG.Add(1)
	go get.Download(i)
}
get.WG.Wait()
Done
defer get.WG.Done()

断点续传

os.FileInfo
for i := 0; i < len(get.DownloadRange); i++ {
	range_i := fmt.Sprintf("%d-%d", get.DownloadRange[i][0], get.DownloadRange[i][1])
	temp_file, err := os.OpenFile(get.FilePath+"."+range_i, os.O_RDONLY|os.O_APPEND, 0)
	if err != nil {
		temp_file, _ = os.Create(get.FilePath + "." + range_i)
	} else {
		fi, err := temp_file.Stat()
		if err == nil {
			get.DownloadRange[i][0] += int(fi.Size())
		}
	}
	get.TempFiles = append(get.TempFiles, temp_file)
}

大概简单的原理就是这些,前面说了,比项目无法用于实际用途,原因如下:

wgetfmt.Println("abc\rcde")\r

本文所涉及到的完整源码请参考。

参考文献

有疑问加站长微信联系(非本文作者)