判断服务器是否支持断点续传,关键看响应中是否存在Content-Range头或Accept-Ranges: bytes;若返回206 Partial Content且含合法Content-Range(如bytes 0-1000/175690767),即支持。

如何判断客户端是否发起了断点续传请求
关键看请求头里有没有 Range,且格式为 bytes=N- 或 bytes=N-M。PHP 中用 $_SERVER['HTTP_RANGE'] 获取,但注意:Apache 某些配置下该值可能被过滤,建议同时检查 getallheaders() 返回结果(需确保未禁用)。
常见错误是直接用 isset($_SERVER['HTTP_RANGE']) 就认为支持断点——其实得进一步正则匹配,否则伪造的 Range: abc 会导致后续 fseek 失败。
- 推荐校验写法:
preg_match('/^bytes=(\d+)-(\d+)?$/i', $rangeHeader, $matches) - 若只有起始位置(如
bytes=100-),说明是续传开始,结束位置应设为文件末尾 - 若范围超出文件大小(如请求 500–1000 字节但文件仅 300 字节),需返回
416 Requested Range Not Satisfiable
设置正确的HTTP响应头组合
断点续传不是只加 Accept-Ranges: bytes 就行。服务端必须明确告知客户端当前响应是部分内容,并给出完整文件大小和当前片段边界。
容易漏掉的是 Content-Range 的格式必须严格匹配 RFC 7233:例如文件总长 1234 字节、本次发送 500–999 字节,则头应为 Content-Range: bytes 500-999/1234;末尾的总长度不能写成 *(那是不支持确定长度时的兜底写法,影响客户端解析)。
立即学习“PHP免费学习笔记(深入)”;
- 必须设置:
HTTP/1.1 206 Partial Content - 必须设置:
Accept-Ranges: bytes - 必须设置:
Content-Range(含准确总长度) -
Content-Length必须是本次传输的字节数,不是整个文件大小 - 避免设置
Content-Encoding: gzip—— 压缩后Range定位失效
fseek 定位与分块读取的实际写法
fseek 本身不校验偏移是否越界,它只是把文件指针移到指定位置。如果 Range 起始值大于文件长度,fseek 不报错,但后续 fread 会返回空,导致响应体为空却返回 206 状态,客户端卡住。
所以 fseek 前必须先 fstat($fp)['size'] 获取真实大小,并做边界判断。另外,大文件别用 fread($fp, filesize($file)) 一次性读入内存——内存爆掉比下载慢更糟。
- 安全做法:用
fseek($fp, $start)后,循环fread($fp, 8192)分块输出 - 每次
fread后检查返回值是否为false(读取出错)或''(到 EOF) - 用
ob_flush()+flush()强制输出缓冲(但需确认 PHP 未启用 zlib.output_compression) - 别忘了
fclose($fp),尤其在异常分支里
浏览器兼容性与调试要点
Chrome 和 Firefox 对 Range 请求处理较规范,但 iOS Safari(尤其旧版本)在后台切换或网络中断后,有时会丢弃已缓存的 Range 上下文,重新发起全量请求,却不带 If-Range 头。这时候服务端若只支持断点、拒绝全量响应,就会卡死。
更隐蔽的问题是:Nginx 默认开启 sendfile,而 sendfile 不支持部分响应(会忽略 Range 直接发整个文件)。若你用 Nginx + PHP-FPM,必须关掉 fastcgi_max_temp_file_size 0 并确保 sendfile off,否则所有断点逻辑都白写。
- 调试时用
curl -v -H "Range: bytes=100-199" http://yoursite/file.zip最直接 - 响应状态码必须是
206,不是200 - 查看响应头里的
Content-Range是否与请求范围一致 - 用 Wireshark 或 Chrome DevTools 的 Network → Headers 面板验证,别只信 PHP 日志



















