大家好,我是 polarisxu。
明天为大家带来一个实战项目。建议你一定要入手实际。
在往下看之前,你不妨思索下,用 Go 如何完成一个并发下载器。
01 原理关于效劳器上的某个文件,我们要并发下载到本地,很容易想到,应该将文件分红多个部分,然后开多个 goroutine 并发地去下载,最后将这多个部分兼并成一个文件,完成并发下载的目的。
如今的成绩是,效劳器上的一个文件,我们怎样做到分红多个呢?
这需求 HTTP 协议相关知识了。
HTTP 协议有一个照应头:Accept-Ranges,效劳器经过该头来标识本身支持部分央求(partial requests),也叫范围央求。假设效劳端支持部分央求,我们就可以完成并发下载。该头有两个能够的值:
Accept-Ranges: bytes
Accept-Ranges: none
none:不支持任何部分央求单位,由于其同等于没有前往此头部,因此很少运用。不过一些阅读器,比如 IE9,会依据该头部去禁用或许移除下载管理器的暂停按钮。
bytes:部分央求的单位是 bytes (字节)。
所以,我们在并发下载之前,应该先发起一个 Head 央求,来确认效劳端能否支持部分央求。比如:
resp, err := http.Head("https://studygolang.com/dl/golang/go1.16.5.src.tar.gz")
if err != nil {
return err
}
if resp.StatusCode == http.StatusOK && resp.Header.Get("Accept-Ranges") == "bytes" {
// 支持部分央求
}
确认了效劳器支持部分央求,接上去就是如何停止部分央求。
这就用到 HTTP 的一个央求头部:Range。(概略参考: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Range )
Range 告知效劳器前往文件的哪一部分。在一个 Range 头部中,可以一次性央求多个部分,效劳器会以 multipart 文件的方式将其前往。假设效劳器前往的是范围照应,需求运用 206 Partial Content 形状码。假设所央求的范围不合法,那么效劳器会前往 416 Range Not Satisfiable 形状码,表示客户端错误。效劳器允许疏忽 Range 首部,从而前往整个文件,形状码用 200。
详细语法:
Range: <unit>=<range-start>-
Range: <unit>=<range-start>-<range-end>
Range: <unit>=<range-start>-<range-end>, <range-start>-<range-end>
Range: <unit>=<range-start>-<range-end>, <range-start>-<range-end>, <range-start>-<range-end>
<unit>
范围所采用的单位,通常是字节(bytes)。
<range-start>
一个整数,表示在特定单位下,范围的起始值。
<range-end>
一个整数,表示在特定单位下,范围的完毕值。这个值是可选的,假设不存在,表示此范围不断延伸到文档完毕。
例如:
Range: bytes=200-1000, 2000-6576, 19000-
掌握了以上知识点,最后要做的就是将下载上去的各个部分兼并成一个文件。需求留意各个部分的顺序,比如依据顺序,按 1、2、3 等编号。
02 入手完成一个知道了原理不代表你真的就会了,我们应该实践入手完成一个,加深了解。
在本地某个目录下创立目录:downloader。
$ mkdir downloader
$ cd downloader
$ go mod init github.com/polaris1119/downloader
命令行参数控制为了让工具更好用,我们应该支持命令行参数,而不是代码写死一个,比如要下载的 URL、并发数、输入的文件名等。关于命令行参数控制,除了运用标准库 flag,我比较喜欢 github.com/urfave/cli,最新版本 v2。
创立一个文件 main.go,内容如下:
package main
import (
"log"
"os"
"runtime"
"github.com/urfave/cli/v2"
)
func main() {
// 默许并发数
concurrencyN := runtime.NumCPU()
app := &cli.App{
Name: "downloader",
Usage: "File concurrency downloader",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "url",
Aliases: []string{"u"},
Usage: "`URL` to download",
Required: true,
},
&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "Output `filename`",
},
&cli.IntFlag{
Name: "concurrency",
Aliases: []string{"n"},
Value: concurrencyN,
Usage: "Concurrency `number`",
},
},
Action: func(c *cli.Context) error {
return nil
},
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
执行 go mod tidy,下载必要的包。然后执行:
$ go run main.go -h
NAME:
downloader - File concurrency downloader
USAGE:
downloader [global options] command [command options] [arguments...]
COMMANDS:
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--url URL, -u URL URL to download
--output filename, -o filename Output filename
--concurrency number, -n number Concurrency number (default: 8)
(责任编辑:admin)