golang 基于 Web 和 CLI 的下载管理器 二

发表于 2021-03-02  1.22k 次阅读


开始并发版下载功能

并发版相当于同时下载这个文件,将这个文件平分x份,每个下载器只下载自己的那部分,下载完成后把这部分写到一个文件里面,当所有的下载器都完成后,这个文件也就完成了。

同时开启多个下载器就需要协程了 goroutine
多个下载器写入文件,显示进度,就需要通道了 channel

创建一个 goroutine 调用的channel 消息体

type fileContent struct {
    taskId int
    con []byte
    start int
    isEnd bool
}

单独把下载器提取出来 下载链接 下载开始索引 下载长度 任务id channel消息体

func task(url string, downloadStart, downloadLen, taskId int, contentChan chan fileContent) {
    request, err := http.NewRequest("GET", url, nil)
    if err != nil {
        panic(err)
    }

    request.Header.Add("Range", fmt.Sprintf("bytes=%d-%d", downloadStart, downloadStart + downloadLen - 1))
    client := http.Client{}
    response, err := client.Do(request)
    if err != nil {
        panic(err)
    }

    defer response.Body.Close()

    content := make([]byte, 1024 * 4)
    start := downloadStart
    isEnd := false
    for {
        read, err := response.Body.Read(content)

        if err != nil {
            if err != io.EOF {
                panic(err)
            }
            isEnd = true
        }

        if read > 0 {
            copyContent := make([]byte, read)
            copy(copyContent, content[0:read])
            contentChan<-fileContent{taskId, copyContent, start, isEnd}
            start += read
        }

        if isEnd {
            break
        }
    }
}

再写一个处理channel消息体的方法,来写入文件,显示进度

func writeFile(downloadFile *os.File, contentChan chan fileContent) {
    info, _ := downloadFile.Stat()
    downloadSize := 0
    for {
        select {
        case c := <-contentChan:
            downloadSize += len(c.con)
            downloadFile.WriteAt(c.con, int64(c.start))
            if c.isEnd {
                dwg.Done()
            }
            fmt.Printf("\r当前下载量 %d/%d", downloadSize, info.Size())
        }
    }
}

注意这些操作都是在协程里面进行的,为防止主进程提前结束,用 sync.WaitGroup 来限制一下
然后整和一下代码,齐活!!!

package main

import (
    "errors"
    "fmt"
    "io"
    "math"
    "net/http"
    "os"
    "path"
    "strconv"
    "sync"
)

type fileContent struct {
    taskId int
    con []byte
    start int
    isEnd bool
}

var dwg sync.WaitGroup

//并发下载数
var maxGo = 2

func main() {
    url := "http://ybook.iapi.im/header-leg.jpg"
    request, err := http.NewRequest("HEAD", url, nil)
    if err != nil{
        panic(err)
    }

    client := http.Client{}
    response, err := client.Do(request)
    if err != nil{
        panic(err)
    }

    fmt.Println(response.Header)
    //map[Accept-Ranges:[bytes] Cache-Control:[max-age=2592000] Connection:[keep-alive] Content-Length:[572356] Content-Type:[image/jpeg] Date:[Tue, 23 Feb 2021 12:00:57 GMT] Etag:["6032775a-8bbc4"] Ex
    //pires:[Thu, 25 Mar 2021 12:00:57 GMT] Last-Modified:[Sun, 21 Feb 2021 15:08:10 GMT] Server:[nginx]]
    if response.Header.Get("Accept-Ranges") != "bytes" {
        panic(errors.New("当前文件不支持下载"))
    }

    fileName := path.Base(url)
    fileSize, _ := strconv.Atoi(response.Header.Get("Content-Length"))

    fmt.Printf("文件名称:%s, 文件大小:%d \n", fileName, fileSize)

    downloadFile, err := os.OpenFile("./" + fileName, os.O_CREATE|os.O_WRONLY, 0666)
    if err != nil {
        panic(err)
    }

    fileInfo, err := downloadFile.Stat()
    if err != nil {
        panic(err)
    }

    if fileInfo.Size() == int64(fileSize) {
        panic(errors.New("当前文件已经下载完成"))
    }

    downloadFile.Seek(int64(fileSize - 1), 0)
    downloadFile.Write([]byte{0})



    //每段平均下载数据量,最后一段会有偏差
    avg := int(math.Ceil(float64(fileSize / maxGo)))

    contentChan := make(chan fileContent, maxGo)

    dwg.Add(maxGo)
    for i := 0; i < maxGo;i++ {
        downloadStart := avg * i
        downloadLen := avg
        if fileSize < (downloadStart + avg) {
            downloadLen = fileSize - downloadStart
        }

        go task(url, downloadStart, downloadLen, i, contentChan)
    }

    go writeFile(downloadFile, contentChan)

    dwg.Wait()
    fmt.Println("\n下载完成")
}

func writeFile(downloadFile *os.File, contentChan chan fileContent) {
    info, _ := downloadFile.Stat()
    downloadSize := 0
    for {
        select {
        case c := <-contentChan:
            downloadSize += len(c.con)
            downloadFile.WriteAt(c.con, int64(c.start))
            if c.isEnd {
                dwg.Done()
            }
            fmt.Printf("\r当前下载量 %d/%d", downloadSize, info.Size())
        }
    }
}

func task(url string, downloadStart, downloadLen, taskId int, contentChan chan fileContent) {
    request, err := http.NewRequest("GET", url, nil)
    if err != nil {
        panic(err)
    }

    request.Header.Add("Range", fmt.Sprintf("bytes=%d-%d", downloadStart, downloadStart + downloadLen - 1))
    client := http.Client{}
    response, err := client.Do(request)
    if err != nil {
        panic(err)
    }

    defer response.Body.Close()

    content := make([]byte, 1024 * 4)
    start := downloadStart
    isEnd := false
    for {
        read, err := response.Body.Read(content)

        if err != nil {
            if err != io.EOF {
                panic(err)
            }
            isEnd = true
        }

        if read > 0 {
            copyContent := make([]byte, read)
            copy(copyContent, content[0:read])
            contentChan<-fileContent{taskId, copyContent, start, isEnd}
            start += read
        }

        if isEnd {
            break
        }
    }
}

本站文章基于国际协议BY-NA-SA 4.0协议共享;
如未特殊说明,本站文章皆为原创文章,请规范转载。

0

一盏灯 一座城 找一人 一路的颠沛流离