file_get_contents 请求大文件必然出问题,因其一次性将整个响应体读入内存,无流式处理能力,易致内存溢出或超时;应改用 cURL 流式写入或原生 socket 手动解析 HTTP。

会卡,而且非常容易内存溢出或超时,根本不是“卡不卡”的问题,而是设计上就不该这么用。
为什么 file_get_contents 请求大文件必然出问题
它是一次性把整个响应体读进内存,没有流式处理能力。哪怕只是下载一个 100MB 的文件,PHP 进程就得申请至少 100MB 内存(实际可能翻倍),而默认的 memory_limit 通常只有 128M 或更小——结果就是直接报 Fatal error: Allowed memory size exhausted。
另外,file_get_contents 默认无超时控制(除非显式设 stream_context_create),遇到网络慢、服务端响应延迟,进程就挂在那里干等。
- HTTP 响应头都还没收全,内存就爆了
- 服务端分块传输(chunked)时,它仍坚持攒满再返回,毫无缓冲意识
- 无法中断、无法进度回调、无法复用连接
替代方案:用 cURL 流式写入文件
这是最常用也最稳妥的落地做法——边收边写,内存占用恒定在几 KB 级别。
立即学习“PHP免费学习笔记(深入)”;
$ch = curl_init('https://example.com/big-file.zip');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, false); // 关键:不返回字符串
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 300);
curl_setopt($ch, CURLOPT_FILE, fopen('/tmp/big-file.zip', 'w'));
if (curl_exec($ch) === false) {
echo 'cURL error: ' . curl_error($ch);
}
curl_close($ch);
-
CURLOPT_RETURNTRANSFER => false是核心,否则又回到内存加载老路 -
CURLOPT_FILE必须传一个已打开的资源句柄(fopen),不能传路径字符串 - 记得检查
curl_exec返回值,false表示失败,不是空字符串 - 如需 HTTPS,确保
curl编译时带 OpenSSL,且证书路径正确(必要时加CURLOPT_CAINFO)
更可控的场景:用 stream_socket_client + 手动解析 HTTP
极少数需要精细控制(比如跳过重定向、自定义 header 解析、断点续传)时,可绕过 cURL,用原生 socket 搭配 fgets/fread 流式读取。但这要求你手动处理状态行、header 分隔、chunked 编码、content-length 校验等。
- HTTP/1.1 的
Transfer-Encoding: chunked必须自己解包,fread($fp, $n)不会自动跳 chunk 头 - 响应头和响应体之间是
\r\n\r\n,不能简单fgets一行行读到底 - 建议只在已有成熟 HTTP parser(如
react/http)基础上扩展,不要从零手撸
顺手检查的几个关键配置项
即使换了 cURL,以下 PHP 配置没调好,照样会跪:
-
max_execution_time:下载大文件必须调高,比如设为0(不限时)或 600 -
default_socket_timeout:影响fsockopen和部分 stream 操作,默认 60 秒太短 -
opcache.enable:开启能减少脚本重复编译开销,但对下载本身无直接影响 - 如果用
fopen('http://...')方式(不推荐),还要确认allow_url_fopen=On,且底层仍是file_get_contents那套逻辑,同样会爆内存
真正要避开卡顿,不是调某个函数参数,而是放弃“一次性加载”这个思维惯性。流式处理不是优化技巧,是处理大响应的唯一合理路径。











