本文目录导读:

在 PHP 中实现流式上传(Streaming Upload),主要目的是减少内存占用,特别是在处理大文件时,不必将整个文件一次性读入内存。
PHP 提供了多种方式实现流式上传,核心思路是读取一段,处理/发送一段,然后丢弃。
以下是几种常见的实现方式,按场景分类:
流式接收(服务器端处理上传)
如果你是在写服务器端代码,接收客户端上传的大文件,核心是配合 php://input 流进行读取。
<?php
// 处理 PUT 请求或非标准表单上传的场景
// 目标文件路径
$targetFile = '/path/to/save/uploaded_file.bin';
// 打开输入流(只读)
$input = fopen('php://input', 'r');
// 打开输出流(写入目标文件)
$output = fopen($targetFile, 'w');
if ($input && $output) {
// 每次读取 1MB(1024 * 1024 字节)
$chunkSize = 1024 * 1024;
while (!feof($input)) {
// 从输入流读取一个块
$chunk = fread($input, $chunkSize);
// 写入到输出流
fwrite($output, $chunk);
// 可选:清空缓冲区,避免阻塞
flush();
}
fclose($input);
fclose($output);
echo "文件上传成功";
} else {
http_response_code(500);
echo "无法打开输入/输出流";
}
注意:如果使用 multipart/form-data 标准表单上传,PHP 会处理到临时文件,此时用 move_uploaded_file() 即可,不涉及流式读取。
流式发送(API 请求转发)
如果你需要把上传的文件从 PHP 转发到另一个服务(例如第三方 API),避免在中间层占用大量内存。
<?php
// 假设文件已经上传到服务器临时目录 $temp_path
$temp_path = $_FILES['file']['tmp_name'];
$target_url = 'https://example.com/api/upload'; // 目标 API
$curl = curl_init($target_url);
$cfile = new CURLFile($temp_path, $_FILES['file']['type'], $_FILES['file']['name']);
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => ['file' => $cfile],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => false,
// 关键:设置超时为较长时间,避免大文件超时
CURLOPT_TIMEOUT => 300,
]);
$response = curl_exec($curl);
curl_close($curl);
// 处理 $response...
关键点:在使用 CURLFile 时,cURL 扩展会自己处理文件流的发送,不会把整个文件加载到 PHP 内存中。
流式下载(将文件流式输出到浏览器)
如果你需要将一个大文件(如视频、备份文件)流式输出到浏览器,而不占用内存:
<?php
$filepath = '/path/to/large/video.mp4';
// 处理 Range 请求(支持视频拖拽)略...
header('Content-Type: video/mp4');
header('Content-Length: ' . filesize($filepath)); // 注意:如果支持 Range,需要动态计算长度
// 关键:关闭输出缓冲
while (ob_get_level() > 0) {
ob_end_flush();
}
$handle = fopen($filepath, 'rb');
while (!feof($handle)) {
// 读取 1MB
echo fread($handle, 1024 * 1024);
// 强制将缓冲区输出给客户端
flush();
// 如果客户端断开了连接
if (connection_aborted()) {
break;
}
}
fclose($handle);
使用现成的库(推荐)
如果项目允许引入第三方库,使用 Guzzle 是个好选择,它原生支持流。
<?php
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Request;
$client = new Client();
// 方式一:使用 Psr7\Utils::streamFor 包装资源
$stream = fopen('/path/to/large/file.txt', 'r');
$request = new Request('POST', 'https://api.example.com/upload', [],
\GuzzleHttp\Psr7\Utils::streamFor($stream)
);
$response = $client->send($request);
fclose($stream);
注意事项与最佳实践
| 要点 | 说明 |
|---|---|
| 内存限制 | 调整 memory_limit 可能会限制,但流式上传不应该依赖它。 |
| 超时设置 | 大文件处理务必设置较长的 max_execution_time(CLI 下默认不限)。 |
| 错误处理 | 检查 fopen 返回,处理好权限问题。 |
| 安全 | 不要使用 $_FILES 中的原始文件名直接保存,需进行过滤,防路径注入。 |
move_uploaded_file 例外 |
如果空间允许,官方推荐的 move_uploaded_file() 性能最高,因为它本质是 rename() 操作,不涉及流复制,仅在需要处理时用流。 |
fcgi 环境 |
在使用 fread + echo 时,flush() 可能不生效,需要配合 fastcgi_finish_request() 或在框架层处理。 |
总结建议
- 如果是接收标准 POST 上传:不需要流式,直接用
move_uploaded_file。 - PHP 是中间代理:推荐 cURL +
CURLFile。 - PHP 是接收方:使用
php://input流。 - 如果是纯 CLI 脚本:使用
fopen+fread循环,并注意yield或进程管理。
好的编码习惯是:能用流操作,就不用一次性加载,这是处理大文件的不二法门。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播