大家都用过迅雷等下载工具,特点就是支持并发下载,断点续传。我们这里不介绍它,这个比较复杂了,逼人也不懂。本文只介绍狭义上的简易的断点续传和狭义上的多线程下载。跟之前一样,旨在研究原理,实际生活中基本没啥用,实测下来多线程下载比单线程下载还慢。。。太丢人了。
主要讲三个方面,如何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
本文所涉及到的完整源码请参考。
参考文献
有疑问加站长微信联系(非本文作者)