开始并发版下载功能
并发版相当于同时下载这个文件,将这个文件平分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
}
}
}
COMMENTS | NOTHING