Range 让客户端只取资源的一部分:断了可以接着下,大文件可以多线程一起下。这篇在真实服务器上把 Range 的各种写法、416、If-Range 逐个试一遍,实际做一次断点续传并校验哈希,再用 netlab 造一条 100 ms 往返的链路,看并行分段下载到底快多少。
Range: bytes=a-b → 206 + Content-Range: bytes a-b/总大小;后缀写法 bytes=-n 取最后 n 字节;终点超出会被截断。
Range: bytes=0-99 → 206,Content-Range = bytes 0-99/15770,Content-Length = 100,收到 100 字节,内容一致 Range: bytes=1000-1999 → 206,Content-Range = bytes 1000-1999/15770,Content-Length = 1000,收到 1000 字节,内容一致 Range: bytes=-100(最后 100 字节) → 206,Content-Range = bytes 15670-15769/15770,Content-Length = 100,收到 100 字节,内容一致 Range: bytes=15270-(从某处到结尾) → 206,Content-Range = bytes 15270-15769/15770,Content-Length = 500,收到 500 字节,内容一致 Range: bytes=0-31540(终点超过大小) → 206,Content-Range = bytes 0-15769/15770,Content-Length = 15770,收到 15770 字节,内容一致 Range: bytes=15780-15790(起点超出资源)→ 416,Content-Range = bytes */15770,消息体 0 字节 Range: bytes=0-99,200-299,1000-1099(三段)→ 200,Content-Type = image/png,消息体 15770 字节;开头 b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x02D\x00\x00\x00\xa4\x08\x06\x00\x00\x00v;1\xf1\x00\x00\x00\tpHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00 \x00ID' Range: items=0-5(不认识的单位)→ 200,消息体 15770 字节
起点超出 → 416 并告知总大小;多段范围和未知单位这台服务器直接返回 200 完整内容。
If-Range: <当前 ETag> + Range → 206,收到 100 字节 If-Range: "old-etag"(资源已变)+ Range → 200,收到 15770 字节(服务器忽略 Range,返回完整新资源)
ETag 匹配才给片段,不匹配返回完整新资源,防止拼出损坏的文件。
第一次:状态 200,只读了 6000 字节就断开(完整大小 15770) 续传:Range: bytes=6000- → 206,Content-Range = bytes 6000-15769/15770,收到 9770 字节;拼起来共 15770 字节,SHA-256 前 12 位 9c121e619bfe,与完整下载一致 对比从头重来:要再传 15770 字节;续传只传了 9770 字节,省了 38%(这里被省掉的只是 6000 字节)
读 6000 字节中断后 Range: bytes=6000- 取剩下的,拼接与完整下载的 SHA-256 一致。
1 条连接:3.23 秒,2.5 MB/s,拼接结果正确 4 条连接:0.91 秒,8.8 MB/s,拼接结果正确 8 条连接:0.51 秒,15.6 MB/s,拼接结果正确 4 条连接比 1 条快 3.5 倍,8 条比 1 条快 6.3 倍(窗口受限的单条连接吞吐 ≈ 256 KiB ÷ 100 ms = 2.6 MB/s;多连接相当于把窗口加起来)
窗口受限的链路上,4 / 8 条连接分段并行比单连接快 3.5 / 6.3 倍。