使用 go 實(shí)現(xiàn)多線程下載器的方法
本篇文章我們用Go實(shí)現(xiàn)一個(gè)簡(jiǎn)單的多線程下載器。
1.多線程下載原理
通過(guò)判斷下載文件鏈接返回頭信息中的 Accept-Ranges 字段,如果為 bytes 則表示支持?jǐn)帱c(diǎn)續(xù)傳。
然后在請(qǐng)求頭中設(shè)置 Range 字段為 bytes=[start]-[end],以請(qǐng)求下載文件的分段部分,然后將所有分段合并為一個(gè)完整文件。
2.構(gòu)造一個(gè)下載器
type HttpDownloader struct { url string filename string contentLength int acceptRanges bool // 是否支持?jǐn)帱c(diǎn)續(xù)傳 numThreads int // 同時(shí)下載線程數(shù) }
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.實(shí)現(xiàn)下載綜合調(diào)度邏輯
如果不支持多線程下載,就使用單線程下載。
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; i<h.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 子線程下載函數(shù)
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. 保存下載文件函數(shù)
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 // 是否支持?jǐn)帱c(diǎn)續(xù)傳 numThreads int // 同時(shí)下載線程數(shù) } 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 } // 下載綜合調(diào)度 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; i<h.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() }
到此這篇關(guān)于使用 go 實(shí)現(xiàn)多線程下載器的文章就介紹到這了,更多相關(guān)go多線程下載器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
瞅一眼就能學(xué)會(huì)的GO并發(fā)編程使用教程
隨著互聯(lián)網(wǎng)的普及,互聯(lián)網(wǎng)用戶人數(shù)原來(lái)越多,這對(duì)系統(tǒng)的性能帶來(lái)了巨大的挑戰(zhàn)。這個(gè)時(shí)候就需要并發(fā)編程了,本文為大家整理了詳細(xì)的GO并發(fā)編程使用教程,讓你看完就能學(xué)會(huì)2023-02-02Go基礎(chǔ)教程系列之defer、panic和recover詳解
這篇文章主要介紹了Go基礎(chǔ)教程系列之defer、panic和recover,需要的朋友可以參考下2022-04-04實(shí)現(xiàn)像php一樣方便的go ORM數(shù)據(jù)庫(kù)操作示例詳解
這篇文章主要為大家介紹了實(shí)現(xiàn)像php一樣方便的go ORM數(shù)據(jù)庫(kù)操作示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12大多數(shù)Go程序員都走過(guò)的坑盤點(diǎn)解析
這篇文章主要為大家介紹了大多數(shù)Go程序員都走過(guò)的坑盤點(diǎn)解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12一文理解Goland協(xié)程調(diào)度器scheduler的實(shí)現(xiàn)
本文主要介紹了Goland協(xié)程調(diào)度器scheduler的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06golang實(shí)現(xiàn)大文件上傳功能全過(guò)程
Go語(yǔ)言可以用來(lái)實(shí)現(xiàn)大文件傳輸,下面這篇文章主要給大家介紹了關(guān)于golang實(shí)現(xiàn)大文件上傳功能的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-07-07GO語(yǔ)言中Chan實(shí)現(xiàn)原理的示例詳解
這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言中Chan實(shí)現(xiàn)原理的相關(guān)資料,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Go語(yǔ)言有一定的幫助,需要的可以參考一下2023-02-02golang反向代理設(shè)置host不生效的問(wèn)題解決
在使用golang的httputil做反向代理的時(shí)候,發(fā)現(xiàn)一個(gè)奇怪的現(xiàn)象,上游網(wǎng)關(guān)必須要設(shè)置host才行,不設(shè)置host的話,golang服務(wù)反向代理請(qǐng)求下游會(huì)出現(xiàn)http 503錯(cuò)誤,接下來(lái)通過(guò)本文給大家介紹golang反向代理設(shè)置host不生效問(wèn)題,感興趣的朋友一起看看吧2023-05-05