Go中多线程下载原理是什么,如何构造一个下载器
导读:今天这篇给大家分享的知识是“Go中多线程下载原理是什么,如何构造一个下载器”,小编觉得挺不错的,对大家学习或是工作可能会有所帮助,对此分享发大家做个参考,希望这篇“Go中多线程下载原理是什么,如何构造一个下载器”文章能帮助大家解决问题。...
今天这篇给大家分享的知识是“Go中多线程下载原理是什么,如何构造一个下载器”,小编觉得挺不错的,对大家学习或是工作可能会有所帮助,对此分享发大家做个参考,希望这篇“Go中多线程下载原理是什么,如何构造一个下载器”文章能帮助大家解决问题。
本篇文章我们用Go实现一个简单的多线程下载器。
1.多线程下载原理
通过判断下载文件链接返回头信息中的 Accept-Ranges 字段,如果为 bytes 则表示支持断点续传。
然后在请求头中设置 Range 字段为 bytes=[start]-[end],以请求下载文件的分段部分,然后将所有分段合并为一个完整文件。
2.构造一个下载器
type HttpDownloader struct { url string filename string contentLength int acceptRanges bool // 是否支持断点续传 numThreads int // 同时下载线程数 }
2.1 为下载器提供初始化方法
func New(url string, numThreads int) *HttpDownloader { var urlSplits []string = strings.Split(url, "/") var filename string = urlSplits[len(urlSplits)-1] res, err := http.Head(url) check(err) httpDownload := new(HttpDownloader) httpDownload.url = url httpDownload.contentLength = int(res.ContentLength) httpDownload.numThreads = numThreads httpDownload.filename = filename if len(res.Header["Accept-Ranges"]) != 0 & & res.Header["Accept-Ranges"][0] == "bytes" { httpDownload.acceptRanges = true } else { httpDownload.acceptRanges = false } return httpDownload }
3.实现下载综合调度逻辑
如果不支持多线程下载,就使用单线程下载。
func (h *HttpDownloader) Download() { f, err := os.Create(h.filename) check(err) defer f.Close() if h.acceptRanges == false { fmt.Println("该文件不支持多线程下载,单线程下载中:") resp, err := http.Get(h.url) check(err) save2file(h.filename, 0, resp) } else { var wg sync.WaitGroup for _, ranges := range h.Split() { fmt.Printf("多线程下载中:%d-%d\n", ranges[0], ranges[1]) wg.Add(1) go func(start, end int) { defer wg.Done() h.download(start, end) } (ranges[0], ranges[1]) } wg.Wait() } }
3.1 下载文件分段
func (h *HttpDownloader) Split() [][]int { ranges := [][]int{ } blockSize := h.contentLength / h.numThreads for i:=0; ih.numThreads; i++ { var start int = i * blockSize var end int = (i + 1) * blockSize - 1 if i == h.numThreads - 1 { end = h.contentLength - 1 } ranges = append(ranges, []int{ start, end} ) } return ranges }
3.2 子线程下载函数
func (h *HttpDownloader) download(start, end int) { req, err := http.NewRequest("GET", h.url, nil) check(err) req.Header.Set("Range", fmt.Sprintf("bytes=%v-%v", start, end)) req.Header.Set("User-Agent", userAgent) resp, err := http.DefaultClient.Do(req) check(err) defer resp.Body.Close() save2file(h.filename, int64(start), resp) }
4. 保存下载文件函数
func save2file(filename string, offset int64, resp *http.Response) { f, err := os.OpenFile(filename, os.O_WRONLY, 0660) check(err) f.Seek(offset, 0) defer f.Close() content, err := ioutil.ReadAll(resp.Body) check(err) f.Write(content) }
5.完整代码
package main import ( "fmt" "strings" "log" "os" "net/http" "sync" "io/ioutil" ) const ( userAgent = `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36` ) type HttpDownloader struct { url string filename string contentLength int acceptRanges bool // 是否支持断点续传 numThreads int // 同时下载线程数 } func check(e error) { if e != nil { log.Println(e) panic(e) } } func New(url string, numThreads int) *HttpDownloader { var urlSplits []string = strings.Split(url, "/") var filename string = urlSplits[len(urlSplits)-1] res, err := http.Head(url) check(err) httpDownload := new(HttpDownloader) httpDownload.url = url httpDownload.contentLength = int(res.ContentLength) httpDownload.numThreads = numThreads httpDownload.filename = filename if len(res.Header["Accept-Ranges"]) != 0 & & res.Header["Accept-Ranges"][0] == "bytes" { httpDownload.acceptRanges = true } else { httpDownload.acceptRanges = false } return httpDownload } // 下载综合调度 func (h *HttpDownloader) Download() { f, err := os.Create(h.filename) check(err) defer f.Close() if h.acceptRanges == false { fmt.Println("该文件不支持多线程下载,单线程下载中:") resp, err := http.Get(h.url) check(err) save2file(h.filename, 0, resp) } else { var wg sync.WaitGroup for _, ranges := range h.Split() { fmt.Printf("多线程下载中:%d-%d\n", ranges[0], ranges[1]) wg.Add(1) go func(start, end int) { defer wg.Done() h.download(start, end) } (ranges[0], ranges[1]) } wg.Wait() } } // 下载文件分段 func (h *HttpDownloader) Split() [][]int { ranges := [][]int{ } blockSize := h.contentLength / h.numThreads for i:=0; ih.numThreads; i++ { var start int = i * blockSize var end int = (i + 1) * blockSize - 1 if i == h.numThreads - 1 { end = h.contentLength - 1 } ranges = append(ranges, []int{ start, end} ) } return ranges } // 多线程下载 func (h *HttpDownloader) download(start, end int) { req, err := http.NewRequest("GET", h.url, nil) check(err) req.Header.Set("Range", fmt.Sprintf("bytes=%v-%v", start, end)) req.Header.Set("User-Agent", userAgent) resp, err := http.DefaultClient.Do(req) check(err) defer resp.Body.Close() save2file(h.filename, int64(start), resp) } // 保存文件 func save2file(filename string, offset int64, resp *http.Response) { f, err := os.OpenFile(filename, os.O_WRONLY, 0660) check(err) f.Seek(offset, 0) defer f.Close() content, err := ioutil.ReadAll(resp.Body) check(err) f.Write(content) } func main() { var url string = "https://dl.softmgr.qq.com/original/im/QQ9.5.0.27852.exe" httpDownload := New(url, 4) fmt.Printf("Bool:%v\nContent:%d\n", httpDownload.acceptRanges, httpDownload.contentLength) httpDownload.Download() }
以上就是关于Go中多线程下载原理是什么,如何构造一个下载器的介绍啦,需要的朋友可以参考上述内容,希望对大家有帮助,欢迎关注网络,小编将为大家输出更多高质量的实用文章!
声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!
若转载请注明出处: Go中多线程下载原理是什么,如何构造一个下载器
本文地址: https://pptw.com/jishu/653434.html