使用 Go 语言开发一个并发文件下载器

开发 后端
Go 语言是一门了不起的语言,尽管它非常简单,与 Koltin 和 Scala 等其他现代语言相比,它的功能很少,但它具有强大的并发能力。这篇文章,我们将会看到使用 Go 语言如何编写一个完整的并发文件下载器。

[[421486]]

本文转载自微信公众号「Golang来啦」,作者Seekload。转载本文请联系Golang来啦公众号。

今天给大家分享一个实战项目,涉及到的知识点还挺多,文末也有源码地址!!

原文如下:

Go 语言是一门了不起的语言,尽管它非常简单,与 Koltin 和 Scala 等其他现代语言相比,它的功能很少,但它具有强大的并发能力。这篇文章,我们将会看到使用 Go 语言如何编写一个完整的并发文件下载器。完整的代码在这里[1]。

检查服务器是否支持并发下载

如何之前使用过类似 IDM 的下载工具,你可能会注意到它支持并发下载文件。

可以看到下载文件的时候启动了 8 个进程。

实现并发下载,我们必须确保服务器支持范围请求。怎么确认呢?我们可以发送 HEAD 请求,如果响应头的 Accept-Ranges 返回的值是 bytes,我们就能确定服务器支持此功能。

  1. res, err := http.Head("http://some.domain/some.file"
  2. if err != nil { 
  3.    log.Fatal(err) 
  4.  
  5. if res.StatusCode == http.StatusOK && res.Header.Get("Accept-Ranges") == "bytes" { 
  6.    // Yeh, server supports partial request 

如何下载文件的其中一部分

设想服务器支持范围请求,我们知道文件大小是 4000 字节(文件大小从响应头的 Content-Length 获取)。要仅下载 2000 到 3000 字节的文件的一部分,我们可以发送 HTTP GET 请求,并在 header 头设置 Range 参数:

  1. curl -X GET -H "Range: bytes=2000-3000" -o OUTPUT_FILE http://some.domain/some.file 

实现相同功能的代码如下:

  1. req, err := http.NewRequest("GET""http://some.domain/some.file", nil) 
  2. if err != nil { 
  3.     log.Fatal(err) 
  4. rangeStart := 2000 
  5. rangeStop := 3000 
  6. req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", rangeStart, rangeStop)) 
  7.  
  8. // make a request 
  9. res, err := http.DefaultClient.Do(req) 

将响应保存在文件中

为了支持断点续传功能,我们不会将请求响应保存在内存里,而是会持久化在文件中。举个例子,如果我们把并发级别设置成 4,在输出目录将会有 4 个临时文件。下面的代码,我们只是简单地读取 HTTP 响应体并将它写入一个文件中:

  1. f, err := os.OpenFile(outputPath, flags, 0644) 
  2.     if err != nil { 
  3.         log.Fatal(err) 
  4.     } 
  5. defer f.Close() 
  6.  
  7. _, err = io.Copy(f, res.Body) 

暂停下载

不知道大家注意到没有,上面代码有个问题,使用时不支持 CTRL+C 暂停下载。如果下载的文件过大,或者网络慢,下载需要花费很长时间。因为 io.Copy 复制文件时遇到 EOF 或者发生错误才结束。为了解决这个问题,我们使用 io.CopyN 和 cancel channel 组合:

  1. // copy to output file 
  2. for { 
  3.   select { 
  4.     case <- context.Done(): 
  5.        // user canceled the download 
  6.         return 
  7.     default
  8.       _, err = io.CopyN(f, res.Body, BUFFER_SIZE)) 
  9.       if err != nil { 
  10.         if err == io.EOF { 
  11.             return 
  12.         } else { 
  13.             log.Fatal(err) 
  14.         } 
  15.       } 
  16.     } 
  17.   } 

其他功能参见完整源代码

这篇文章只提到了代码中最重要的部分,但是通过阅读代码你可以了解其他功能是怎么实现的,比如:进度条的工作方式、如何使用 sync 包实现部分下载的同步、如何合并临时文件以及如何实现恢复功能等。所以可以通过阅读仓库代码[2]获取更多信息。

参考资料

[1]这里: https://github.com/mostafa-asg/go-dl

[2]仓库代码: https://github.com/mostafa-asg/go-dl

via:

https://returnfn.com/lets-build-a-concurrent-file-downloader-in-go

作者:Mostafa Asgari

 

责任编辑:武晓燕 来源: Golang来啦
相关推荐

2021-07-05 10:33:55

开发技能Go

2023-04-03 06:53:04

Go开发架构

2024-05-09 10:26:14

2021-01-14 09:49:10

安全漏洞开发

2022-01-05 08:58:08

Python解释器编程语言

2015-10-12 15:50:07

PaaS云平台开发go

2021-02-25 22:17:19

开发技术编程

2014-04-09 09:32:24

Go并发

2023-03-05 23:11:07

Go语言服务

2021-06-24 06:35:00

Go语言进程

2017-11-14 11:12:50

Go语言编译器

2019-07-05 08:39:39

GoSQL解析器

2024-01-08 13:40:00

并发安全• 数量

2021-07-26 10:14:38

Go语言工具

2013-05-28 09:43:38

GoGo语言并发模式

2023-02-10 09:40:36

Go语言并发

2021-07-15 23:18:48

Go语言并发

2023-12-21 07:09:32

Go语言任务

2021-07-30 07:28:15

WorkerPoolGo语言

2021-04-13 06:35:13

Elixir语言编程语言软件开发
点赞
收藏

51CTO技术栈公众号