Go語言實(shí)現(xiàn)多協(xié)程文件下載器的過程詳解
前言
你好,我是醉墨居士,最近在開發(fā)文件傳輸相關(guān)的項(xiàng)目,然后順手寫了一個(gè)多協(xié)程文件下載器,代碼非常精簡(jiǎn),核心代碼只有100行左右,適合分享給大家學(xué)習(xí)使用
流程圖

主函數(shù)
func main() {
fileURL := flag.String("u", "", "downloade url of the file")
flag.Parse()
if *fileURL == "" {
log.Println("Please input a download url")
flag.Usage()
return
}
fileDir, err := os.Getwd()
if err != nil {
log.Println(err)
return
}
// 下載文件保存路徑
filePath := filepath.Join(fileDir, filepath.Base(*fileURL))
err = downloadFile(*fileURL, filePath)
if err != nil {
log.Println(err)
return
}
log.Println("download file success:", filePath)
}下載文件
// 下載文件
func downloadFile(fileURL string, filePath string) error {
log.Println("downloading file:", fileURL, "to", filePath)
taskCh := make(chan [2]int64, runtime.NumCPU())
wg := new(sync.WaitGroup)
// 創(chuàng)建執(zhí)行下載任務(wù)的 worker
err := initWorker(fileURL, filePath, taskCh, wg)
if err != nil {
return fmt.Errorf("init worker failed: %v", err)
}
// 分發(fā)下載任務(wù)
err = dispatchTask(fileURL, taskCh)
if err != nil {
return fmt.Errorf("dispacth task failed: %v", err)
}
// 等待所有下載任務(wù)完成
wg.Wait()
return nil
}初始化分片下載worker
// 初始化 下載 worker
func initWorker(url string, filePath string, taskCh chan [2]int64, wg *sync.WaitGroup) error {
for i := 0; i < runtime.NumCPU(); i++ {
// 打開文件句柄
file, err := os.OpenFile(filePath, os.O_CREATE|os.O_RDWR, 0644)
if err != nil {
return err
}
wg.Add(1)
go func(file *os.File, taskCh chan [2]int64) {
defer wg.Done()
defer file.Close()
// 循環(huán)從 taskCh 中獲取下載任務(wù)并下載
for part := range taskCh {
log.Printf("downloading part, start offset: %d, end offset: %d", part[0], part[1])
// 重試下載,最大重試次數(shù)為 10 次,每次下載失敗后等待 1 秒
err := retryWithWaitTime(10, func() error {
return downloadPart(url, file, part[0], part[1])
}, time.Second)
if err != nil {
log.Printf("download part %d failed: %v", part, err)
}
}
}(file, taskCh)
}
return nil
}分發(fā)下載任務(wù)
// 分發(fā)下載任務(wù)
func dispatchTask(url string, taskCh chan [2]int64) error {
defer close(taskCh)
fileSize, err := getFileSize(url)
if err != nil {
return err
}
// 分片大小 1MB
const chunkSize = 1024 * 1024
parts := fileSize / chunkSize
log.Println("file size:", fileSize, "parts:", parts, "chunk size:", chunkSize)
for i := int64(0); i < parts; i++ {
// 計(jì)算分片的起始和結(jié)束位置
startOffset := i * chunkSize
endOffset := startOffset + chunkSize - 1
// 發(fā)送下載任務(wù)
taskCh <- [2]int64{startOffset, endOffset}
}
// 發(fā)送最后一個(gè)分片的下載任務(wù)
if fileSize % chunkSize != 0 {
taskCh <- [2]int64{parts * chunkSize, fileSize - 1}
}
return nil
}獲取下載文件的大小
// 獲取文件大小
func getFileSize(url string) (int64, error) {
resp, err := http.Head(url)
if err != nil {
return 0, err
}
defer resp.Body.Close()
return resp.ContentLength, nil
}下載文件分片
// 下載文件分片
func downloadPart(url string, file *os.File, startPos, endPos int64) error {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return err
}
// 設(shè)置文件分片區(qū)間的請(qǐng)求頭
req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", startPos, endPos))
resp, err := http.DefaultTransport.RoundTrip(req)
if err != nil {
return err
}
defer resp.Body.Close()
// 如果服務(wù)器返回的狀態(tài)碼不是 206 Partial Content,則說明下載失敗
if resp.StatusCode != http.StatusPartialContent {
data, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
log.Println("unexpected data:", string(data))
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
// 文件指針移動(dòng)到分片的起始位置
_, err = file.Seek(startPos, 0)
if err != nil {
return err
}
// 寫入分片數(shù)據(jù)到文件
_, err = io.Copy(file, resp.Body)
if err != nil {
return err
}
return nil
}錯(cuò)誤重試
// 重試函數(shù)
func retryWithWaitTime(retryCount int, fn func() error, waitTime time.Duration) error {
var err error
for i := 0; i < retryCount; i++ {
e := fn()
if e != nil {
errors.Join(err, e)
time.Sleep(waitTime)
continue
}
return nil
}
return err
}項(xiàng)目演示


最后
到此這篇關(guān)于Go語言實(shí)現(xiàn)多協(xié)程文件下載器的文章就介紹到這了,更多相關(guān)Go多協(xié)程下載器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang 實(shí)現(xiàn)tcp server端和client端,并計(jì)算RTT時(shí)間操作
這篇文章主要介紹了golang 實(shí)現(xiàn)tcp server端和client端,并計(jì)算RTT時(shí)間操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-12-12
Golang實(shí)現(xiàn)SSH、SFTP操作小結(jié)
在日常的一些開發(fā)場(chǎng)景中,我們需要去和遠(yuǎn)程服務(wù)器進(jìn)行一些通信,本文主要介紹了Golang實(shí)現(xiàn)SSH、SFTP操作小結(jié),具有一定的參考價(jià)值,感興趣的可以了解一下2024-04-04
Go Time庫中時(shí)間和日期相關(guān)的操作方法整理
這篇文章主要為大家整理了Go語言中的time庫,包括時(shí)間、日期和時(shí)區(qū)等相關(guān)概念及使用方法,希望通過掌握這些知識(shí),大家可以更好地處理時(shí)間、日期和時(shí)區(qū)相關(guān)的問題2023-08-08
go語言如何使用gin庫實(shí)現(xiàn)SSE長(zhǎng)連接
所謂長(zhǎng)連接指在一個(gè)TCP連接上可以連續(xù)發(fā)送多個(gè)數(shù)據(jù)包,在TCP連接保持期間,如果沒有數(shù)據(jù)包發(fā)送,需要雙方發(fā)檢測(cè)包以維持此連接,一般需要自己做在線維持,下面這篇文章主要給大家介紹了關(guān)于go語言如何使用gin庫實(shí)現(xiàn)SSE長(zhǎng)連接的相關(guān)資料,需要的朋友可以參考下2023-06-06

