• 用 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
    ", 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 子线程下载函数

    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
    ", 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
    Content:%d
    ", httpDownload.acceptRanges, httpDownload.contentLength)
    
        httpDownload.Download()
    }
    
  • 相关阅读:
    PHP的Tag标签
    PHP 常量
    MySQL知识点链接
    Codeforces Round #593 (Div. 2)D(螺旋形模拟)
    【PAT甲级】1060 Are They Equal (25 分)(需注意细节的模拟)
    【PAT甲级】1059 Prime Factors (25 分)
    【PAT甲级】1058 A+B in Hogwarts (20 分)
    【PAT甲级】1057 Stack (30 分)(分块)
    【PAT甲级】1056 Mice and Rice (25 分)
    Atcoder Grand Contest 039C(容斥原理,计数DP)
  • 原文地址:https://www.cnblogs.com/qxcheng/p/15378472.html
Copyright © 2020-2023  润新知